#[inline]
pub fn crc32c_raw(seed: u32, data: &[u8]) -> u32 {
!crc32c::crc32c_append(!seed, data)
}
#[inline]
pub fn fs_seed(uuid: &[u8; 16]) -> u32 {
crc32c_raw(!0u32, uuid)
}
#[inline]
pub fn inode_seed(fs_seed: u32, ino: u32, generation: u32) -> u32 {
let c = crc32c_raw(fs_seed, &ino.to_le_bytes());
crc32c_raw(c, &generation.to_le_bytes())
}
pub fn superblock(sb: &[u8]) -> u32 {
crc32c_raw(!0u32, &sb[..0x3FC])
}
pub fn group_desc(fs_seed: u32, group: u32, desc: &[u8]) -> u16 {
let c = crc32c_raw(fs_seed, &group.to_le_bytes());
let c = crc32c_raw(c, &desc[..0x1E]);
let c = crc32c_raw(c, &[0u8; 2]);
c as u16
}
pub fn block_bitmap(fs_seed: u32, bitmap_block: &[u8]) -> u16 {
crc32c_raw(fs_seed, bitmap_block) as u16
}
pub fn inode_bitmap(fs_seed: u32, bitmap_block: &[u8], inodes_per_group: u32) -> u16 {
let len = (inodes_per_group as usize).div_ceil(8);
crc32c_raw(fs_seed, &bitmap_block[..len]) as u16
}
pub fn inode(fs_seed: u32, ino: u32, generation: u32, inode: &[u8]) -> u32 {
let extra_isize = if inode.len() >= 0x82 {
u16::from_le_bytes([inode[0x80], inode[0x81]])
} else {
0
};
let has_hi = extra_isize >= 4 && inode.len() >= 0x84;
let c = inode_seed(fs_seed, ino, generation);
let c = crc32c_raw(c, &inode[..0x7C]);
let c = crc32c_raw(c, &[0u8; 2]); let c = crc32c_raw(c, &inode[0x7E..inode.len().min(0x82)]);
if has_hi {
let c = crc32c_raw(c, &[0u8; 2]); crc32c_raw(c, &inode[0x84..])
} else if inode.len() > 0x82 {
crc32c_raw(c, &inode[0x82..])
} else {
c
}
}
pub fn dirent_block(inode_seed: u32, block: &[u8]) -> u32 {
crc32c_raw(inode_seed, &block[..block.len() - 12])
}
pub fn extent_block(inode_seed: u32, block: &[u8]) -> u32 {
crc32c_raw(inode_seed, &block[..block.len() - 4])
}
pub fn dx_block(inode_seed: u32, block: &[u8], count_offset: usize, count: u16, limit: u16) -> u32 {
let tail = count_offset + limit as usize * 8;
let c = crc32c_raw(inode_seed, &block[..count_offset + count as usize * 8]);
let c = crc32c_raw(c, &block[tail..tail + 4]); crc32c_raw(c, &[0u8; 4]) }
pub fn xattr_block(fs_seed: u32, block_nr: u64, block: &[u8]) -> u32 {
let c = crc32c_raw(fs_seed, &block_nr.to_le_bytes());
let c = crc32c_raw(c, &block[..0x10]);
let c = crc32c_raw(c, &[0u8; 4]);
crc32c_raw(c, &block[0x14..])
}
pub fn xattr_entry_hash(name: &[u8], value: &[u8]) -> u32 {
let mut h: u32 = 0;
for &b in name {
h = (h << 5) ^ (h >> 27) ^ u32::from(b);
}
let mut chunks = value.chunks_exact(4);
for w in &mut chunks {
h = (h << 16) ^ (h >> 16) ^ u32::from_le_bytes(w.try_into().unwrap());
}
let rem = chunks.remainder();
if !rem.is_empty() {
let mut w = [0u8; 4];
w[..rem.len()].copy_from_slice(rem);
h = (h << 16) ^ (h >> 16) ^ u32::from_le_bytes(w);
}
h
}
pub fn xattr_block_hash(entry_hashes: impl IntoIterator<Item = u32>) -> u32 {
let mut h: u32 = 0;
for eh in entry_hashes {
if eh == 0 {
return 0;
}
h = (h << 16) ^ (h >> 16) ^ eh;
}
h
}
#[cfg(test)]
mod tests {
use super::*;
const UUID: [u8; 16] = [
0xd0, 0xd0, 0xca, 0xca, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
];
fn vector(name: &str) -> Vec<u8> {
std::fs::read(format!(
"{}/testdata/vectors/{name}",
env!("CARGO_MANIFEST_DIR")
))
.unwrap_or_else(|e| panic!("vector {name}: {e}"))
}
fn le16(b: &[u8], off: usize) -> u16 {
u16::from_le_bytes(b[off..off + 2].try_into().unwrap())
}
fn le32(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes(b[off..off + 4].try_into().unwrap())
}
#[test]
fn fs_seed_matches_known_value() {
assert_eq!(fs_seed(&UUID), 0x4216_ab51);
}
#[test]
fn superblock_checksum() {
for name in ["sb_primary.bin", "sb_backup_g1.bin"] {
let sb = vector(name);
assert_eq!(superblock(&sb), le32(&sb, 0x3FC), "{name}");
}
}
#[test]
fn group_desc_checksums() {
let gdt = vector("gdt.bin");
let seed = fs_seed(&UUID);
for (g, desc) in gdt.chunks_exact(32).enumerate() {
assert_eq!(
group_desc(seed, g as u32, desc),
le16(desc, 0x1E),
"group {g}"
);
}
}
#[test]
fn bitmap_checksums() {
let gdt = vector("gdt.bin");
let seed = fs_seed(&UUID);
let desc = &gdt[..32];
let bb = vector("block_bitmap_g0.bin");
assert_eq!(block_bitmap(seed, &bb), le16(desc, 0x18));
let ib = vector("inode_bitmap_g0.bin");
assert_eq!(inode_bitmap(seed, &ib, 8192), le16(desc, 0x1A));
assert_ne!(crc32c_raw(seed, &ib) as u16, le16(desc, 0x1A));
}
#[test]
fn inode_checksums() {
let seed = fs_seed(&UUID);
for (name, ino) in [
("inode_1_bad_blocks.bin", 1u32),
("inode_2_root.bin", 2),
("inode_8_journal.bin", 8),
("inode_11_lost_found.bin", 11),
] {
let raw = vector(name);
let gen = le32(&raw, 0x64);
let got = inode(seed, ino, gen, &raw);
let want_lo = le16(&raw, 0x7C);
let extra = le16(&raw, 0x80);
if extra >= 4 {
let want = u32::from(want_lo) | (u32::from(le16(&raw, 0x82)) << 16);
assert_eq!(got, want, "{name}");
} else {
assert_eq!(got as u16, want_lo, "{name}");
}
}
}
#[test]
fn dirent_block_checksum() {
let blk = vector("dirblock_root.bin");
let seed = inode_seed(fs_seed(&UUID), 2, 0);
assert_eq!(dirent_block(seed, &blk), le32(&blk, 4096 - 4));
}
#[test]
fn dx_root_checksum() {
let blk = vector("dx_root_bigdir.bin");
let ino_raw = vector("inode_bigdir_dx.bin");
let gen = le32(&ino_raw, 0x64);
let limit = le16(&blk, 0x20);
let count = le16(&blk, 0x22);
let tail = 0x20 + limit as usize * 8;
let want = le32(&blk, tail + 4);
let found = (11..64).any(|ino| {
dx_block(
inode_seed(fs_seed(&UUID), ino, gen),
&blk,
0x20,
count,
limit,
) == want
});
assert!(found, "no inode in 11..64 authenticates the dx_root block");
}
#[test]
fn xattr_block_checksum_and_hashes() {
let blk = vector("xattr_block.bin");
let seed = fs_seed(&UUID);
let want = le32(&blk, 0x10);
let blocknr = (0..200_000u64).find(|&nr| xattr_block(seed, nr, &blk) == want);
assert!(
blocknr.is_some(),
"no block number authenticates the xattr block"
);
let name_len = blk[0x20] as usize;
let value_off = le16(&blk, 0x22) as usize;
let value_size = le32(&blk, 0x28) as usize;
let name = &blk[0x30..0x30 + name_len];
let value = &blk[value_off..value_off + value_size];
assert_eq!(xattr_entry_hash(name, value), le32(&blk, 0x2C));
}
#[test]
fn xattr_block_hash_fold() {
assert_eq!(xattr_block_hash([0x18547]), 0x18547);
assert_eq!(xattr_block_hash([]), 0);
assert_eq!(xattr_block_hash([1, 0]), 0); }
}