Skip to main content

wafrift_evolution/
lineage.rs

1//! Lineage tracking for replayable bypass discovery.
2
3use crate::evolution::Chromosome;
4use serde::{Deserialize, Serialize};
5use std::sync::Arc;
6
7/// A single mutation operation log entry.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct MutationOp {
10    /// Gene name that was mutated.
11    pub gene_name: String,
12    /// Previous value.
13    pub from: String,
14    /// New value.
15    pub to: String,
16    /// Mutation operator name.
17    pub operator: String,
18}
19
20/// Lineage of a chromosome: how it was derived from seeds.
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub enum Lineage {
23    /// Original randomly-generated chromosome.
24    Genesis {
25        /// Generation when created.
26        generation: u32,
27    },
28    /// Created via crossover of two parents.
29    Crossover {
30        /// Parent A snapshot.
31        parent_a: Arc<Chromosome>,
32        /// Parent B snapshot.
33        parent_b: Arc<Chromosome>,
34        /// Strategy used.
35        strategy: String,
36        /// Generation when created.
37        generation: u32,
38    },
39    /// Created via mutation of a single parent.
40    Mutation {
41        /// Parent snapshot.
42        parent: Arc<Chromosome>,
43        /// Log of applied mutation operations.
44        log: Vec<MutationOp>,
45        /// Generation when created.
46        generation: u32,
47    },
48}
49
50impl Lineage {
51    /// Create a genesis lineage.
52    #[must_use]
53    pub fn genesis(generation: u32) -> Self {
54        Self::Genesis { generation }
55    }
56
57    /// Create a crossover lineage.
58    #[must_use]
59    pub fn crossover(
60        parent_a: &Chromosome,
61        parent_b: &Chromosome,
62        strategy: &str,
63        generation: u32,
64    ) -> Self {
65        Self::Crossover {
66            parent_a: Arc::new(parent_a.clone()),
67            parent_b: Arc::new(parent_b.clone()),
68            strategy: strategy.to_string(),
69            generation,
70        }
71    }
72
73    /// Create a mutation lineage.
74    #[must_use]
75    pub fn mutation(parent: &Chromosome, log: Vec<MutationOp>, generation: u32) -> Self {
76        Self::Mutation {
77            parent: Arc::new(parent.clone()),
78            log,
79            generation,
80        }
81    }
82
83    /// Serialize lineage to a compact string representation.
84    #[must_use]
85    pub fn to_trace(&self) -> String {
86        match self {
87            Self::Genesis { generation } => format!("genesis[gen={generation}]"),
88            Self::Crossover {
89                parent_a,
90                parent_b,
91                strategy,
92                generation,
93            } => {
94                format!(
95                    "crossover[gen={generation},strategy={strategy},a={{{}}},b={{{}}}]",
96                    genes_to_string(&parent_a.genes),
97                    genes_to_string(&parent_b.genes)
98                )
99            }
100            Self::Mutation {
101                parent,
102                log,
103                generation,
104            } => {
105                let ops: Vec<String> = log
106                    .iter()
107                    .map(|op| format!("{}:{}->{}[{}]", op.gene_name, op.from, op.to, op.operator))
108                    .collect();
109                format!(
110                    "mutation[gen={generation},parent={{{}}},ops=[{}]]",
111                    genes_to_string(&parent.genes),
112                    ops.join(",")
113                )
114            }
115        }
116    }
117}
118
119fn genes_to_string(genes: &[(String, String)]) -> String {
120    genes
121        .iter()
122        .map(|(n, v)| format!("{n}={v}"))
123        .collect::<Vec<_>>()
124        .join(",")
125}
126
127/// Serialize a bypass corpus including full lineage trees.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct BypassEntry {
130    /// Payload hash (SHA-256 hex of serialized genes).
131    pub payload_hash: String,
132    /// Genes that produced the bypass.
133    pub genes: Vec<(String, String)>,
134    /// Full lineage trace.
135    pub lineage_trace: String,
136    /// Final fitness score.
137    pub fitness: f64,
138    /// Number of evaluations.
139    pub evaluations: u32,
140    /// Target WAF identifier (optional).
141    pub target_waf: Option<String>,
142    /// Whether this bypass was verified.
143    pub verified: bool,
144    /// Schema version for forward/backward compatibility.
145    pub schema_version: u32,
146}
147
148impl BypassEntry {
149    pub const CURRENT_SCHEMA: u32 = 1;
150
151    #[must_use]
152    pub fn from_chromosome(chromosome: &Chromosome, target_waf: Option<String>) -> Self {
153        use std::collections::hash_map::DefaultHasher;
154        use std::hash::{Hash, Hasher};
155        let mut hasher = DefaultHasher::new();
156        chromosome.genes.hash(&mut hasher);
157        let hash = hasher.finish();
158
159        Self {
160            payload_hash: format!("{:016x}", hash),
161            genes: chromosome.genes.clone(),
162            lineage_trace: chromosome.lineage.to_trace(),
163            fitness: chromosome.fitness,
164            evaluations: chromosome.evaluations,
165            target_waf,
166            verified: true,
167            schema_version: Self::CURRENT_SCHEMA,
168        }
169    }
170}
171
172/// A serializable bypass corpus.
173#[derive(Debug, Clone, Default, Serialize, Deserialize)]
174pub struct BypassCorpus {
175    pub entries: Vec<BypassEntry>,
176    pub schema_version: u32,
177}
178
179impl BypassCorpus {
180    pub const CURRENT_SCHEMA: u32 = 1;
181
182    #[must_use]
183    pub fn new() -> Self {
184        Self {
185            entries: Vec::new(),
186            schema_version: Self::CURRENT_SCHEMA,
187        }
188    }
189
190    /// Add a bypass entry.
191    pub fn add(&mut self, entry: BypassEntry) {
192        // Deduplicate by payload hash
193        if !self
194            .entries
195            .iter()
196            .any(|e| e.payload_hash == entry.payload_hash)
197        {
198            self.entries.push(entry);
199        }
200    }
201
202    /// Save corpus to disk as JSONL (one JSON object per line).
203    pub fn save(&self, path: &std::path::Path) -> Result<(), crate::types::EvolutionError> {
204        use crate::types::EvolutionError;
205        let mut lines = Vec::new();
206        for entry in &self.entries {
207            let json = serde_json::to_string(entry)
208                .map_err(|e| EvolutionError::SerializationFailed(e.to_string()))?;
209            lines.push(json);
210        }
211        std::fs::write(path, lines.join("\n"))
212            .map_err(|e| EvolutionError::SerializationFailed(e.to_string()))?;
213        Ok(())
214    }
215
216    /// Load corpus from JSONL.
217    pub fn load(path: &std::path::Path) -> Result<Self, crate::types::EvolutionError> {
218        use crate::types::EvolutionError;
219        let content = std::fs::read_to_string(path)
220            .map_err(|e| EvolutionError::DeserializationFailed(e.to_string()))?;
221        let mut entries = Vec::new();
222        for line in content.lines().filter(|l| !l.trim().is_empty()) {
223            let entry: BypassEntry = serde_json::from_str(line)
224                .map_err(|e| EvolutionError::DeserializationFailed(e.to_string()))?;
225            entries.push(entry);
226        }
227        Ok(Self {
228            entries,
229            schema_version: Self::CURRENT_SCHEMA,
230        })
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use crate::evolution::Chromosome;
238
239    #[test]
240    fn bypass_entry_deduplicates() {
241        let mut corpus = BypassCorpus::new();
242        let chrom = Chromosome::new(vec![("encoding".into(), "UrlEncode".into())]);
243        let entry = BypassEntry::from_chromosome(&chrom, None);
244        corpus.add(entry.clone());
245        corpus.add(entry);
246        assert_eq!(corpus.entries.len(), 1);
247    }
248
249    #[test]
250    fn lineage_trace_roundtrips() {
251        let chrom = Chromosome::new(vec![("a".into(), "1".into())]);
252        let lineage = Lineage::genesis(0);
253        assert!(lineage.to_trace().contains("genesis"));
254
255        let cross = Lineage::crossover(&chrom, &chrom, "uniform", 1);
256        assert!(cross.to_trace().contains("crossover"));
257
258        let mutation = Lineage::mutation(&chrom, vec![], 2);
259        assert!(mutation.to_trace().contains("mutation"));
260    }
261}