#![allow(clippy::arithmetic_side_effects, clippy::integer_division)]
#![allow(clippy::cast_possible_truncation, clippy::indexing_slicing)]
use omy_core::crypto::{Argon2Params, Kek};
use omy_core::file::{EncryptOptions, RandomMaterial, encrypt, is_omy_file, open, peek_header};
use omy_core::header::MIN_CHUNK_SIZE;
use omy_core::shard::{ShardHeader, analyze_coverage, merge, recover_header_from_shard};
use omy_core::tlv::{TlvSet, decode_compression_index, unpad_filename};
struct Rng(u64);
impl Rng {
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn below(&mut self, n: usize) -> usize {
if n == 0 { 0 } else { (self.next_u64() % n as u64) as usize }
}
fn byte(&mut self) -> u8 {
(self.next_u64() & 0xFF) as u8
}
}
fn main() {
let iters: usize = std::env::args()
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(20_000);
let seed: u64 = std::env::args()
.nth(2)
.and_then(|s| s.parse().ok())
.unwrap_or(0x1234_5678_9ABC_DEF0);
let salt = [0x11u8; 16];
let params = Argon2Params::TEST_WEAK;
let kek = Kek::from_password(b"fuzz", &salt, params).expect("KEK 派生失败");
let opts = EncryptOptions {
filename: Some("fuzz-target.bin".into()),
chunk_size: MIN_CHUNK_SIZE,
argon2: params,
..EncryptOptions::default()
};
let base = encrypt(
&vec![0x42u8; 3000],
&[kek],
&salt,
&opts,
&RandomMaterial::generate(),
)
.expect("加密失败")
.bytes;
println!("基底文件 {} 字节,迭代 {iters} 次,seed = 0x{seed:016X}", base.len());
let mut rng = Rng(seed);
let mut stats = [0usize; 6];
for i in 0..iters {
let mut data = base.clone();
match rng.below(8) {
0 => {
let p = rng.below(data.len());
if let Some(b) = data.get_mut(p) {
*b ^= 1u8 << rng.below(8);
}
}
1 => {
let n = rng.below(data.len().max(1));
data.truncate(n);
}
2 => {
let start = rng.below(data.len());
let len = rng.below(64).min(data.len().saturating_sub(start));
for k in 0..len {
if let Some(b) = data.get_mut(start + k) {
*b = rng.byte();
}
}
}
3 => {
let p = rng.below(data.len().saturating_sub(4).max(1));
let v: [u8; 4] = match rng.below(4) {
0 => u32::MAX.to_le_bytes(),
1 => 0u32.to_le_bytes(),
2 => (u32::MAX / 2).to_le_bytes(),
_ => 0xDEAD_BEEFu32.to_le_bytes(),
};
for (k, vb) in v.iter().enumerate() {
if let Some(b) = data.get_mut(p + k) {
*b = *vb;
}
}
}
4 => {
let n = rng.below(200);
for _ in 0..n {
data.push(rng.byte());
}
}
5 => {
let n = rng.below(1200);
data = (0..n).map(|_| rng.byte()).collect();
}
6 => {
let n = rng.below(1200).max(8);
data = (0..n).map(|_| rng.byte()).collect();
if data.len() >= 8 {
data[..8].copy_from_slice(&omy_core::MAGIC_FILE);
}
}
_ => {
data.truncate(rng.below(16));
}
}
let _ = is_omy_file(&data);
match peek_header(&data) {
Ok(_) => stats[1] += 1,
Err(_) => stats[0] += 1,
}
let k2 = Kek::from_password(b"fuzz", &salt, params).expect("KEK 派生失败");
match open(&data, &[k2]) {
Ok(opened) => {
stats[3] += 1;
let _ = opened.filename();
let _ = opened.thumbnail();
let _ = opened.compression_index();
let _ = opened.stored_content_hash();
let _ = opened.decrypt_all(&data);
}
Err(_) => stats[2] += 1,
}
if TlvSet::parse(&data).is_ok() {
stats[4] += 1;
}
let _ = decode_compression_index(&data);
let _ = unpad_filename(&data);
if ShardHeader::parse(&data).is_ok() {
stats[5] += 1;
}
let _ = recover_header_from_shard(&data);
let _ = merge(&[data.clone()]);
let _ = analyze_coverage(&[data.clone()]);
if i % 5000 == 0 && i > 0 {
println!(" ...{i} 次");
}
}
println!("\n完成 {iters} 次变异,无 panic");
println!(" peek_header: {} Err / {} Ok", stats[0], stats[1]);
println!(" open: {} Err / {} Ok", stats[2], stats[3]);
println!(" TLV 可解析: {}", stats[4]);
println!(" 分片头可解析: {}", stats[5]);
}