use std::sync::LazyLock;
use anyhow::{Context, Result};
use clap::Subcommand;
#[derive(Subcommand)]
pub enum Crc32ForgeAction {
#[command(about = "Compute 4 bytes to append to data so the result has a target CRC32")]
Forge {
#[arg(help = "Input data (hex-encoded)")]
input: String,
#[arg(long, help = "Target CRC32 checksum (hex, e.g. 'deadbeef')")]
target: String,
},
#[command(about = "Verify that appending the forge bytes produces the target CRC32")]
Verify {
#[arg(help = "Original data (hex-encoded)")]
input: String,
#[arg(long, help = "Forge bytes to append (hex-encoded, 4 bytes)")]
suffix: String,
#[arg(long, help = "Expected CRC32 checksum (hex)")]
target: String,
},
}
pub fn run(action: Crc32ForgeAction) -> Result<()> {
match action {
Crc32ForgeAction::Forge { input, target } => {
let data = hex::decode(&input).context("Invalid hex input")?;
let target_crc =
u32::from_str_radix(&target, 16).context("Invalid hex target CRC32")?;
let forge_bytes = forge_crc32(&data, target_crc);
println!("{}", hex::encode(forge_bytes));
}
Crc32ForgeAction::Verify {
input,
suffix,
target,
} => {
let data = hex::decode(&input).context("Invalid hex input")?;
let suffix_bytes = hex::decode(&suffix).context("Invalid hex suffix")?;
let target_crc =
u32::from_str_radix(&target, 16).context("Invalid hex target CRC32")?;
let mut combined = data;
combined.extend_from_slice(&suffix_bytes);
let actual = crc32_compute(&combined);
if actual == target_crc {
println!("Match");
} else {
println!("Mismatch (expected {:08x}, got {:08x})", target_crc, actual);
}
}
}
Ok(())
}
const CRC32_POLY: u32 = 0xEDB88320;
static CRC32_TABLE: LazyLock<[u32; 256]> = LazyLock::new(|| {
let mut table = [0u32; 256];
for i in 0..256u32 {
let mut crc = i;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ CRC32_POLY;
} else {
crc >>= 1;
}
}
table[i as usize] = crc;
}
table
});
static INV_TABLE: LazyLock<[u8; 256]> = LazyLock::new(|| {
let table = &*CRC32_TABLE;
let mut inv = [0u8; 256];
for (i, &entry) in table.iter().enumerate() {
inv[(entry >> 24) as usize] = i as u8;
}
inv
});
pub fn crc32_compute(data: &[u8]) -> u32 {
let table = &*CRC32_TABLE;
let mut crc = 0xFFFF_FFFFu32;
for &byte in data {
let index = ((crc ^ u32::from(byte)) & 0xFF) as usize;
crc = (crc >> 8) ^ table[index];
}
crc ^ 0xFFFF_FFFF
}
pub fn forge_crc32(data: &[u8], target_crc: u32) -> [u8; 4] {
let table = &*CRC32_TABLE;
let mut crc_after_data = 0xFFFF_FFFFu32;
for &byte in data {
let index = ((crc_after_data ^ u32::from(byte)) & 0xFF) as usize;
crc_after_data = (crc_after_data >> 8) ^ table[index];
}
let target_state = target_crc ^ 0xFFFF_FFFF;
let inv = &*INV_TABLE;
let i3 = inv[(target_state >> 24) as usize];
let base3 = (target_state ^ table[i3 as usize]) << 8;
let i2 = inv[(base3 >> 24) as usize];
let base2 = (base3 ^ table[i2 as usize]) << 8;
let i1 = inv[(base2 >> 24) as usize];
let base1 = (base2 ^ table[i1 as usize]) << 8;
let i0 = inv[(base1 >> 24) as usize];
let base0 = (base1 ^ table[i0 as usize]) << 8;
let delta = crc_after_data ^ base0;
let l3 = ((delta >> 24) & 0xFF) as u8;
let l2 = ((delta >> 16) & 0xFF) as u8;
let l1 = ((delta >> 8) & 0xFF) as u8;
let l0 = (delta & 0xFF) as u8;
[l0 ^ i0, l1 ^ i1, l2 ^ i2, l3 ^ i3]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crc32_compute_hello() {
let crc = crc32_compute(b"hello");
assert_eq!(crc, 0x3610a686);
}
#[test]
fn test_forge_and_verify() {
let data = b"Hello, World!";
let target_crc = 0xDEADBEEF_u32;
let forge_bytes = forge_crc32(data, target_crc);
let mut combined = data.to_vec();
combined.extend_from_slice(&forge_bytes);
assert_eq!(crc32_compute(&combined), target_crc);
}
}