cloudscraper_rs/challenges/core/
timing.rs1use std::cmp::Ordering;
7use std::time::Duration;
8
9#[derive(Debug, Clone, Copy)]
11pub enum TimingFeedback {
12 Success,
13 Failure,
14 RateLimited,
15}
16
17#[derive(Debug, Clone)]
19pub struct DelayStrategy {
20 base_delay_ms: u64,
21 min_delay_ms: u64,
22 max_delay_ms: u64,
23 variance_pct: f64,
24 recent_failures: u32,
25}
26
27impl DelayStrategy {
28 pub fn new(base_delay_ms: u64) -> Self {
29 Self {
30 base_delay_ms,
31 min_delay_ms: base_delay_ms / 2,
32 max_delay_ms: base_delay_ms * 2,
33 variance_pct: 0.25,
34 recent_failures: 0,
35 }
36 }
37
38 pub fn with_bounds(mut self, min_delay_ms: u64, max_delay_ms: u64) -> Self {
39 self.min_delay_ms = min_delay_ms;
40 self.max_delay_ms = max_delay_ms;
41 self
42 }
43
44 pub fn with_variance(mut self, variance_pct: f64) -> Self {
45 self.variance_pct = variance_pct;
46 self
47 }
48
49 pub fn register_feedback(&mut self, feedback: TimingFeedback) {
50 match feedback {
51 TimingFeedback::Success => {
52 self.recent_failures = self.recent_failures.saturating_sub(1);
53 }
54 TimingFeedback::Failure => {
55 self.recent_failures = self.recent_failures.saturating_add(1);
56 }
57 TimingFeedback::RateLimited => {
58 self.recent_failures = self.recent_failures.saturating_add(2);
59 }
60 }
61 }
62
63 pub fn next_delay(&self) -> Duration {
64 let mut delay = self.base_delay_ms as f64;
65
66 match self.recent_failures.cmp(&2) {
67 Ordering::Less => {}
68 Ordering::Equal => delay *= 1.5,
69 Ordering::Greater => delay *= 2.0,
70 }
71
72 let variance = delay * self.variance_pct;
73 let jitter = rand::random::<f64>() * variance - (variance / 2.0);
74 delay = (delay + jitter).clamp(self.min_delay_ms as f64, self.max_delay_ms as f64);
75 Duration::from_millis(delay.max(0.0) as u64)
76 }
77}