use std::sync::Arc;
use std::time::Instant;
use captchaforge::detect::rules::{built_in_rules, RuleDetector};
use captchaforge::detect::DetectedCaptcha;
use captchaforge::provider::ProviderRegistry;
use captchaforge::solver::{CaptchaType, TokenCache};
fn main() {
println!("# captchaforge architecture proof\n");
println!("All numbers are wall-clock means over many iterations on this machine.");
println!("Browser-free; runs in well under a second on any modern laptop.\n");
bench_token_cache();
bench_provider_registry();
bench_rule_probe_generation();
bench_per_rule_fixture_match();
println!("## Summary\n");
println!("If the cache hit / provider lookup / rule match rows are NOT in the");
println!("microsecond range, something has regressed. Real solver attempts run");
println!("in the hundreds-of-milliseconds-to-seconds range, so the architecture");
println!("features above add zero meaningful overhead to the hot path.\n");
}
fn bench_token_cache() {
let cache = TokenCache::new();
cache.put(
"example.com",
&CaptchaType::CloudflareTurnstile,
"tok-abc".into(),
"BehavioralCaptchaSolver",
);
let captcha_type = CaptchaType::CloudflareTurnstile;
let n = 100_000;
let t0 = Instant::now();
let mut hits = 0u64;
for _ in 0..n {
if cache.get_token("example.com", &captcha_type).is_some() {
hits += 1;
}
}
let hit_ns = (t0.elapsed().as_nanos() as f64) / (n as f64);
assert_eq!(hits, n as u64, "cache hits should always succeed");
let t0 = Instant::now();
for _ in 0..n {
let _ = cache.get_token("never-seen.test", &captcha_type);
}
let miss_ns = (t0.elapsed().as_nanos() as f64) / (n as f64);
println!("## TokenCache\n");
println!("| Operation | Mean (ns) | Iterations |");
println!("|-----------|----------:|-----------:|");
println!("| `get` hit | {:>9.0} | {:>10} |", hit_ns, n);
println!("| `get` miss | {:>8.0} | {:>10} |", miss_ns, n);
println!();
}
fn bench_provider_registry() {
let reg = ProviderRegistry::with_built_in_rules().expect("bundled rules parse");
let kind = DetectedCaptcha::Custom("datadome".into());
let n = 100_000;
let t0 = Instant::now();
let mut found = 0u64;
for _ in 0..n {
if reg.find_by_kind(&kind).is_some() {
found += 1;
}
}
let lookup_ns = (t0.elapsed().as_nanos() as f64) / (n as f64);
assert_eq!(found, n as u64);
println!("## ProviderRegistry\n");
println!("| Operation | Mean (ns) | Iterations |");
println!("|-----------|----------:|-----------:|");
println!(
"| `find_by_kind` (Custom) | {:>3.0} | {:>10} |",
lookup_ns, n
);
println!(
"| Registry size: {} providers (built-in + community.toml)",
reg.providers().len()
);
println!();
}
fn bench_rule_probe_generation() {
let set = built_in_rules().expect("rules parse");
let n = 1000;
let mut total_ns = 0u128;
let mut probe_lengths = Vec::new();
for _ in 0..n {
for rule in &set.providers {
let t0 = Instant::now();
let det = RuleDetector::from(rule.clone());
total_ns += t0.elapsed().as_nanos();
probe_lengths.push(det.js().len());
}
}
let total_constructions = n * set.providers.len();
let avg_ns = (total_ns as f64) / (total_constructions as f64);
let avg_len = probe_lengths.iter().sum::<usize>() as f64 / probe_lengths.len() as f64;
println!("## RuleDetector probe generation\n");
println!("Done once at registration. Detect-time cost is `page.evaluate(&str)` only.\n");
println!("| Metric | Value |");
println!("|--------|------:|");
println!("| Mean construction time | {:>6.2} µs |", avg_ns / 1000.0);
println!("| Mean probe length | {:>6.0} bytes |", avg_len);
println!(
"| Rules generated | {} | (per iter, × {} iters)",
set.providers.len(),
n
);
println!();
}
fn bench_per_rule_fixture_match() {
use std::path::Path;
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let fixtures_root = Path::new(manifest_dir).join("tests").join("rule_fixtures");
if !fixtures_root.exists() {
println!("## Per-rule HTML fixture match\n");
println!("(skipped — `tests/rule_fixtures/` not present)\n");
return;
}
let set = built_in_rules().unwrap();
let n = 5_000;
println!("## Per-rule HTML fixture match\n");
println!("Per-rule per-fixture latency using a string-contains matcher");
println!("(the precise scraper-based matcher used for correctness lives");
println!("in the `community_rules` integration test). Numbers prove that");
println!("adding new rules to the pack does not regress detection time.\n");
println!("| Vendor | Mean match (µs) | Pos+neg fixtures |");
println!("|--------|----------------:|-----------------:|");
for rule in &set.providers {
let pos_path = fixtures_root.join(&rule.name).join("positive.html");
let neg_path = fixtures_root.join(&rule.name).join("negative.html");
if !pos_path.exists() || !neg_path.exists() {
continue;
}
let pos = std::fs::read_to_string(&pos_path).unwrap();
let neg = std::fs::read_to_string(&neg_path).unwrap();
let t0 = Instant::now();
for _ in 0..n {
let _ = rule_matches_html(&pos, rule);
let _ = rule_matches_html(&neg, rule);
}
let total_ns = t0.elapsed().as_nanos();
let per_match_ns = (total_ns as f64) / ((n * 2) as f64);
println!(
"| `{}` | {:>14.2} | 1+1 |",
rule.name,
per_match_ns / 1000.0
);
}
println!();
}
fn rule_matches_html(html: &str, rule: &captchaforge::detect::rules::ProviderRule) -> bool {
for sel in &rule.triggers.selectors {
let needle = sel.trim_start_matches(['.', '#', '[']);
if html.contains(needle) {
return true;
}
}
for needle in &rule.triggers.script_src_contains {
if html.contains(needle) {
return true;
}
}
false
}
#[allow(dead_code)]
fn use_arc() -> Arc<TokenCache> {
Arc::new(TokenCache::new())
}