use serde::{Deserialize, Serialize};
use wafrift_types::{Request, Technique};
use wafrift_types::injection_context::InjectionContext;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvasionStage {
pub technique: Technique,
pub context: Option<InjectionContext>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvasionPipeline {
pub name: String,
pub stages: Vec<EvasionStage>,
pub cost: u32,
pub success_bps: u16,
}
impl EvasionPipeline {
#[must_use]
pub fn new(name: impl Into<String>, stages: Vec<EvasionStage>, cost: u32) -> Self {
Self {
name: name.into(),
stages,
cost,
success_bps: 0,
}
}
pub fn with_success_rate(mut self, bps: u16) -> Self {
self.success_bps = bps;
self
}
#[must_use]
pub fn apply_to(&self, req: &Request) -> (Request, Vec<EvasionStage>) {
(req.clone(), self.stages.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvasionPlanOutput {
pub pipelines: Vec<EvasionPipeline>,
pub total_cost: u32,
pub waf_fingerprint: Option<String>,
pub payload_type: Option<String>,
}
impl EvasionPlanOutput {
#[must_use]
pub fn new(pipelines: Vec<EvasionPipeline>) -> Self {
let total_cost = pipelines.iter().map(|p| p.cost).sum();
Self {
pipelines,
total_cost,
waf_fingerprint: None,
payload_type: None,
}
}
#[must_use]
pub fn cheapest(&self) -> Option<&EvasionPipeline> {
self.pipelines.iter().min_by_key(|p| p.cost)
}
#[must_use]
pub fn best(&self) -> Option<&EvasionPipeline> {
self.pipelines.first()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pipeline_cost_tracking() {
let p = EvasionPipeline::new(
"test",
vec![EvasionStage {
technique: Technique::UserAgentRotation,
context: None,
}],
3,
);
assert_eq!(p.cost, 3);
}
#[test]
fn plan_total_cost() {
let plan = EvasionPlanOutput::new(vec![
EvasionPipeline::new("a", vec![], 2),
EvasionPipeline::new("b", vec![], 3),
]);
assert_eq!(plan.total_cost, 5);
}
#[test]
fn plan_cheapest() {
let plan = EvasionPlanOutput::new(vec![
EvasionPipeline::new("a", vec![], 5),
EvasionPipeline::new("b", vec![], 1),
]);
assert_eq!(plan.cheapest().unwrap().name, "b");
}
#[test]
fn plan_best_returns_first() {
let plan = EvasionPlanOutput::new(vec![
EvasionPipeline::new("first", vec![], 10),
EvasionPipeline::new("second", vec![], 1),
]);
assert_eq!(plan.best().unwrap().name, "first");
}
#[test]
fn pipeline_with_success_rate() {
let p = EvasionPipeline::new("test", vec![], 1).with_success_rate(5000);
assert_eq!(p.success_bps, 5000);
}
#[test]
fn apply_to_clones_request_and_stages() {
let req = Request::get("http://example.com/");
let pipeline = EvasionPipeline::new(
"test",
vec![EvasionStage {
technique: Technique::UserAgentRotation,
context: None,
}],
1,
);
let (cloned_req, stages) = pipeline.apply_to(&req);
assert_eq!(cloned_req.method, req.method);
assert_eq!(stages.len(), 1);
}
#[test]
fn empty_plan_has_zero_cost() {
let plan = EvasionPlanOutput::new(vec![]);
assert_eq!(plan.total_cost, 0);
assert!(plan.cheapest().is_none());
assert!(plan.best().is_none());
}
}