affs_read/
checksum.rs

1//! Checksum calculation functions.
2
3use crate::constants::BLOCK_SIZE;
4
5/// Calculate the normal checksum for a block.
6///
7/// Used for root blocks, entry blocks, etc.
8/// The checksum is calculated such that the sum of all longwords equals 0.
9#[inline]
10pub fn normal_sum(buf: &[u8; BLOCK_SIZE], checksum_offset: usize) -> u32 {
11    let mut sum: u32 = 0;
12    for i in 0..(BLOCK_SIZE / 4) {
13        if i != checksum_offset / 4 {
14            sum = sum.wrapping_add(read_u32_be(buf, i * 4));
15        }
16    }
17    (sum as i32).wrapping_neg() as u32
18}
19
20/// Calculate the boot block checksum.
21///
22/// Special checksum algorithm for the boot block.
23#[inline]
24pub fn boot_sum(buf: &[u8; 1024]) -> u32 {
25    let mut sum: u32 = 0;
26    for i in 0..256 {
27        if i != 1 {
28            let d = read_u32_be_slice(buf, i * 4);
29            let new_sum = sum.wrapping_add(d);
30            // Handle overflow (carry)
31            if new_sum < sum {
32                sum = new_sum.wrapping_add(1);
33            } else {
34                sum = new_sum;
35            }
36        }
37    }
38    !sum
39}
40
41/// Calculate bitmap block checksum.
42#[inline]
43pub fn bitmap_sum(buf: &[u8; BLOCK_SIZE]) -> u32 {
44    let mut sum: u32 = 0;
45    for i in 1..128 {
46        sum = sum.wrapping_sub(read_u32_be(buf, i * 4));
47    }
48    sum
49}
50
51/// Read a big-endian u32 from a buffer.
52#[inline]
53pub const fn read_u32_be(buf: &[u8; BLOCK_SIZE], offset: usize) -> u32 {
54    u32::from_be_bytes([
55        buf[offset],
56        buf[offset + 1],
57        buf[offset + 2],
58        buf[offset + 3],
59    ])
60}
61
62/// Read a big-endian u32 from a slice.
63#[inline]
64pub const fn read_u32_be_slice(buf: &[u8], offset: usize) -> u32 {
65    u32::from_be_bytes([
66        buf[offset],
67        buf[offset + 1],
68        buf[offset + 2],
69        buf[offset + 3],
70    ])
71}
72
73/// Read a big-endian i32 from a buffer.
74#[inline]
75pub const fn read_i32_be(buf: &[u8; BLOCK_SIZE], offset: usize) -> i32 {
76    i32::from_be_bytes([
77        buf[offset],
78        buf[offset + 1],
79        buf[offset + 2],
80        buf[offset + 3],
81    ])
82}
83
84/// Read a big-endian u16 from a buffer.
85#[inline]
86pub const fn read_u16_be(buf: &[u8; BLOCK_SIZE], offset: usize) -> u16 {
87    u16::from_be_bytes([buf[offset], buf[offset + 1]])
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_read_u32_be() {
96        let mut buf = [0u8; BLOCK_SIZE];
97        buf[0] = 0x12;
98        buf[1] = 0x34;
99        buf[2] = 0x56;
100        buf[3] = 0x78;
101        assert_eq!(read_u32_be(&buf, 0), 0x12345678);
102    }
103
104    #[test]
105    fn test_read_i32_be() {
106        let mut buf = [0u8; BLOCK_SIZE];
107        buf[0] = 0xFF;
108        buf[1] = 0xFF;
109        buf[2] = 0xFF;
110        buf[3] = 0xFD;
111        assert_eq!(read_i32_be(&buf, 0), -3);
112    }
113
114    #[test]
115    fn test_normal_sum_overflow() {
116        // Test the edge case that caused the overflow bug:
117        // When sum == 0x80000000, negating as i32 would overflow
118        // This happens when the block contains values that sum to i32::MIN
119        let mut buf = [0u8; BLOCK_SIZE];
120        // Put 0x80000000 in the first longword
121        buf[0] = 0x80;
122        buf[1] = 0x00;
123        buf[2] = 0x00;
124        buf[3] = 0x00;
125        // This should not panic with wrapping_neg()
126        let result = normal_sum(&buf, 20); // checksum_offset at 20 (not at 0)
127        // The expected result: wrapping_neg of 0x80000000 is 0x80000000
128        assert_eq!(result, 0x80000000);
129    }
130}