Skip to main content

assay_sim/mutators/
bitflip.rs

1use super::Mutator;
2use anyhow::Result;
3use rand::rngs::StdRng;
4use rand::{Rng, SeedableRng};
5
6pub struct BitFlip {
7    pub count: usize,
8    /// Optional seed for deterministic mutations. Uses thread_rng if None.
9    pub seed: Option<u64>,
10}
11
12impl Mutator for BitFlip {
13    fn mutate(&self, data: &[u8]) -> Result<Vec<u8>> {
14        let mut corrupted = data.to_vec();
15
16        let flip = |rng: &mut dyn rand::RngCore, buf: &mut Vec<u8>, count: usize| {
17            for _ in 0..count {
18                if buf.is_empty() {
19                    break;
20                }
21                let idx = rng.gen_range(0..buf.len());
22                buf[idx] ^= 1 << rng.gen_range(0..8u32);
23            }
24        };
25
26        if let Some(seed) = self.seed {
27            let mut rng = StdRng::seed_from_u64(seed);
28            flip(&mut rng, &mut corrupted, self.count);
29        } else {
30            let mut rng = rand::thread_rng();
31            flip(&mut rng, &mut corrupted, self.count);
32        }
33
34        Ok(corrupted)
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_seeded_bitflip_is_deterministic() {
44        let data = vec![0u8; 100];
45        let bf = BitFlip {
46            count: 5,
47            seed: Some(42),
48        };
49
50        let result1 = bf.mutate(&data).unwrap();
51        let result2 = bf.mutate(&data).unwrap();
52
53        assert_eq!(
54            result1, result2,
55            "same seed must produce identical mutations"
56        );
57        assert_ne!(result1, data, "mutations should change the data");
58    }
59
60    #[test]
61    fn test_different_seeds_produce_different_mutations() {
62        let data = vec![0u8; 100];
63        let bf1 = BitFlip {
64            count: 5,
65            seed: Some(42),
66        };
67        let bf2 = BitFlip {
68            count: 5,
69            seed: Some(99),
70        };
71
72        let result1 = bf1.mutate(&data).unwrap();
73        let result2 = bf2.mutate(&data).unwrap();
74
75        assert_ne!(
76            result1, result2,
77            "different seeds should produce different mutations"
78        );
79    }
80
81    #[test]
82    fn test_seeded_bitflip_exact_bytes() {
83        // Verify the exact same bytes are flipped across runs
84        let data = vec![0xAA; 50];
85        let bf = BitFlip {
86            count: 3,
87            seed: Some(12345),
88        };
89
90        let result1 = bf.mutate(&data).unwrap();
91        let result2 = bf.mutate(&data).unwrap();
92
93        // Find which positions differ
94        let diffs1: Vec<usize> = result1
95            .iter()
96            .enumerate()
97            .filter(|(i, b)| **b != data[*i])
98            .map(|(i, _)| i)
99            .collect();
100        let diffs2: Vec<usize> = result2
101            .iter()
102            .enumerate()
103            .filter(|(i, b)| **b != data[*i])
104            .map(|(i, _)| i)
105            .collect();
106
107        assert_eq!(diffs1, diffs2, "exact same positions must be flipped");
108        assert!(!diffs1.is_empty(), "at least one bit should be flipped");
109    }
110}