#![no_main] use libfuzzer_sys::fuzz_target; use soliton::streaming::{stream_decrypt_init, stream_encrypt_init}; fuzz_target!(|data: &[u8]| { // Fuzz encrypt_chunk_at: encrypt all chunks via the random-access API, // assemble in index order, then decrypt sequentially and verify round-trip. // // Exercises the index-derived nonce/AAD construction under adversarial // plaintext, validates that out-of-order encryption produces valid streams, // and confirms that size validation rejects malformed inputs without panic. if data.is_empty() { return; } // First byte: compression flag (bit 0), encrypt order (bit 1 = reversed). let compress = data[0] & 0x01 != 0; let reversed = data[0] & 0x02 != 0; let plaintext = &data[1..]; let key = [0x42u8; 32]; let aad = b"fuzz-at"; let enc = match stream_encrypt_init(&key, aad, compress) { Ok(e) => e, Err(_) => return, }; let header = enc.header(); let chunk_size = soliton::constants::STREAM_CHUNK_SIZE; // Compute chunk boundaries. let full_count = plaintext.len() / chunk_size; let total_chunks = full_count + 1; let mut index_order: Vec = (0..total_chunks).collect(); if reversed { index_order.reverse(); } // Encrypt each chunk via encrypt_chunk_at in the selected order. let mut chunks: Vec>> = vec![None; total_chunks]; for &i in &index_order { let is_last = i == full_count; let slice = if i < full_count { &plaintext[i * chunk_size..(i + 1) * chunk_size] } else { &plaintext[full_count * chunk_size..] }; match enc.encrypt_chunk_at(i as u64, is_last, slice) { Ok(ct) => chunks[i] = Some(ct), Err(_) => return, } } // Assemble in index order and decrypt sequentially. let mut dec = match stream_decrypt_init(&key, &header, aad) { Ok(d) => d, Err(_) => return, }; for (i, maybe_ct) in chunks.iter().enumerate() { let ct = match maybe_ct { Some(c) => c, None => return, }; let is_last_expected = i == full_count; match dec.decrypt_chunk(ct) { Ok((pt, is_last)) => { // Plaintext must match the original slice. let expected = if i < full_count { &plaintext[i * chunk_size..(i + 1) * chunk_size] } else { &plaintext[full_count * chunk_size..] }; assert_eq!(&*pt, expected, "round-trip mismatch at chunk {i}"); assert_eq!( is_last, is_last_expected, "is_last mismatch at chunk {i}" ); if is_last { return; } } Err(_) => return, } } });