Skip to main content

typhoon/flow/
config.rs

1#[cfg(test)]
2#[path = "../../tests/flow/config.rs"]
3mod tests;
4
5use std::cmp::min;
6
7use log::info;
8use rand::Rng;
9use rand::distributions::Standard;
10use rand::prelude::Distribution;
11
12use crate::bytes::{ByteBufferMut, DynamicByteBuffer};
13use crate::flow::error::FlowControllerError;
14use crate::settings::{Settings, keys};
15use crate::utils::random::get_rng;
16use crate::utils::sync::AsyncExecutor;
17use crate::utils::unix_timestamp_ms;
18use crate::weighted_random;
19
20/// Fake body generation mode.
21///
22/// Each mode defines how fake body content is generated to pad packets.
23/// Uses Vec<u8> internally for Sync compatibility.
24#[derive(Debug, Clone)]
25pub enum FakeBodyMode {
26    /// Empty: no fake body added.
27    Empty,
28    /// Random: random bytes of random length.
29    Random {
30        min_length: usize,
31        max_length: usize,
32        service: bool,
33    },
34    /// Constant: fixed content across all packets.
35    Constant {
36        packet_length: usize,
37    },
38}
39
40impl FakeBodyMode {
41    /// Human-readable description of this mode for capture log records.
42    #[inline]
43    pub(crate) fn description(&self) -> String {
44        match self {
45            FakeBodyMode::Empty => "Empty".to_string(),
46            FakeBodyMode::Random {
47                min_length,
48                max_length,
49                service,
50            } => format!("Random({min_length}..{max_length},svc={service})"),
51            FakeBodyMode::Constant {
52                packet_length,
53            } => format!("Constant({packet_length})"),
54        }
55    }
56
57    /// Maximum fake body length this mode can produce — used to bound MTU calculations.
58    pub fn max_len(&self) -> usize {
59        match self {
60            FakeBodyMode::Empty => 0,
61            FakeBodyMode::Random {
62                max_length,
63                ..
64            } => *max_length,
65            FakeBodyMode::Constant {
66                packet_length,
67            } => *packet_length,
68        }
69    }
70
71    pub fn get_length(&self, max_packet_size: usize, taken_packet_size: usize, is_service: bool) -> usize {
72        match self {
73            FakeBodyMode::Empty => 0,
74            FakeBodyMode::Random {
75                min_length,
76                max_length,
77                service,
78            } => {
79                if !service || (is_service && *service) {
80                    let body_space = max_packet_size.saturating_sub(taken_packet_size);
81                    let effective_max = min(*max_length, body_space);
82                    if effective_max <= *min_length {
83                        effective_max
84                    } else {
85                        get_rng().gen_range(*min_length..effective_max)
86                    }
87                } else {
88                    0
89                }
90            }
91            FakeBodyMode::Constant {
92                packet_length,
93            } => min(max_packet_size, *packet_length).saturating_sub(taken_packet_size),
94        }
95    }
96}
97
98/// Field type for fake header generation.
99///
100/// Each field type defines how a portion of the header is generated.
101/// Uses Vec<u8> internally for Sync compatibility.
102#[derive(Debug, Clone)]
103pub enum FieldType<L> {
104    /// Random bytes on each packet.
105    Random,
106    /// Constant bytes across all packets.
107    Constant {
108        value: L,
109    },
110    /// Volatile: changes value randomly at random intervals.
111    Volatile {
112        value: L,
113        change_probability: f64,
114    },
115    /// Switching: toggles between two values.
116    Switching {
117        value: L,
118        next_switch: u128,
119        switch_timeout: u64,
120    },
121    /// Incremental: counter that increases by 1 each packet.
122    Incremental {
123        value: L,
124    },
125}
126
127trait WrappingIncrement: Copy {
128    fn wrapping_inc(self) -> Self;
129}
130macro_rules! impl_wrapping_increment {
131    ($($t:ty)*) => { $(
132        impl WrappingIncrement for $t {
133            #[inline] fn wrapping_inc(self) -> Self { self.wrapping_add(1) }
134        }
135    )* };
136}
137impl_wrapping_increment!(u8 u16 u32 u64);
138
139#[allow(private_bounds)]
140impl<L: Copy + WrappingIncrement> FieldType<L> {
141    pub fn apply(&mut self) -> L
142    where
143        Standard: Distribution<L>,
144    {
145        match self {
146            FieldType::Random => get_rng().r#gen::<L>(),
147            FieldType::Constant {
148                value,
149            } => *value,
150            FieldType::Volatile {
151                value,
152                change_probability,
153            } => {
154                if get_rng().r#gen::<f64>() > *change_probability {
155                    *value = get_rng().r#gen::<L>();
156                }
157                *value
158            }
159            FieldType::Switching {
160                value,
161                next_switch,
162                switch_timeout,
163            } => {
164                if unix_timestamp_ms() > *next_switch {
165                    *next_switch = unix_timestamp_ms() + *switch_timeout as u128;
166                    *value = get_rng().r#gen::<L>();
167                }
168                *value
169            }
170            FieldType::Incremental {
171                value,
172            } => {
173                *value = value.wrapping_inc();
174                *value
175            }
176        }
177    }
178}
179
180#[derive(Debug, Clone)]
181pub enum FieldTypeHolder {
182    U8(FieldType<u8>),
183    U16(FieldType<u16>),
184    U32(FieldType<u32>),
185    U64(FieldType<u64>),
186}
187
188/// Fake body generation mode.
189///
190/// Each mode defines how fake body content is generated to pad packets.
191/// Uses Vec<u8> internally for Sync compatibility.
192#[derive(Debug, Clone)]
193pub struct FakeHeaderConfig {
194    pattern: Vec<FieldTypeHolder>,
195}
196
197impl FakeHeaderConfig {
198    pub fn new(pattern: Vec<FieldTypeHolder>) -> Self {
199        Self {
200            pattern,
201        }
202    }
203
204    /// Create a random header configuration drawn from the default probability distributions.
205    ///
206    /// Includes a header with probability `FAKE_HEADER_PROBABILITY`; if included, a random number
207    /// of fields are packed to fill a length sampled from `[FAKE_HEADER_LENGTH_MIN, FAKE_HEADER_LENGTH_MAX]`.
208    /// Each field is independently assigned one of the five `FieldType` variants weighted by the
209    /// `FAKE_HEADER_FIELD_WEIGHT_*` settings.
210    pub fn random<AE: AsyncExecutor>(settings: &Settings<AE>) -> Self {
211        let mut rng = get_rng();
212        let header_prob = settings.get(&keys::FAKE_HEADER_PROBABILITY);
213        if rng.r#gen::<f64>() < header_prob {
214            let min_len = settings.get(&keys::FAKE_HEADER_LENGTH_MIN) as usize;
215            let max_len = settings.get(&keys::FAKE_HEADER_LENGTH_MAX) as usize;
216            let len = if min_len >= max_len {
217                max_len
218            } else {
219                rng.gen_range(min_len..=max_len)
220            };
221            let volatile_prob_min = settings.get(&keys::FAKE_HEADER_VOLATILE_CHANGE_PROB_MIN);
222            let volatile_prob_max = settings.get(&keys::FAKE_HEADER_VOLATILE_CHANGE_PROB_MAX);
223            let switching_timeout_min = settings.get(&keys::FAKE_HEADER_SWITCHING_TIMEOUT_MIN_MS);
224            let switching_timeout_max = settings.get(&keys::FAKE_HEADER_SWITCHING_TIMEOUT_MAX_MS);
225            let fields = (0..len)
226                .map(|_| {
227                    FieldTypeHolder::U8(weighted_random! {
228                        settings.get(&keys::FAKE_HEADER_FIELD_WEIGHT_RANDOM) => FieldType::Random,
229                        settings.get(&keys::FAKE_HEADER_FIELD_WEIGHT_CONSTANT) => FieldType::Constant {
230                            value: rng.r#gen::<u8>(),
231                        },
232                        settings.get(&keys::FAKE_HEADER_FIELD_WEIGHT_VOLATILE) => FieldType::Volatile {
233                            value: rng.r#gen::<u8>(),
234                            change_probability: rng.gen_range(volatile_prob_min..=volatile_prob_max),
235                        },
236                        settings.get(&keys::FAKE_HEADER_FIELD_WEIGHT_SWITCHING) => {
237                            let switch_timeout = rng.gen_range(switching_timeout_min..=switching_timeout_max);
238                            FieldType::Switching {
239                                value: rng.r#gen::<u8>(),
240                                next_switch: unix_timestamp_ms() + switch_timeout as u128,
241                                switch_timeout,
242                            }
243                        }
244                        settings.get(&keys::FAKE_HEADER_FIELD_WEIGHT_INCREMENTAL) => FieldType::Incremental {
245                            value: rng.r#gen::<u8>(),
246                        },
247                    })
248                })
249                .collect();
250            Self::new(fields)
251        } else {
252            Self::new(vec![])
253        }
254    }
255
256    pub fn len(&self) -> usize {
257        self.pattern.iter().fold(0, |a, f| {
258            a + match f {
259                FieldTypeHolder::U8(_) => size_of::<u8>(),
260                FieldTypeHolder::U16(_) => size_of::<u16>(),
261                FieldTypeHolder::U32(_) => size_of::<u32>(),
262                FieldTypeHolder::U64(_) => size_of::<u64>(),
263            }
264        })
265    }
266
267    pub fn is_empty(&self) -> bool {
268        self.len() == 0
269    }
270
271    pub fn fill(&mut self, buffer: DynamicByteBuffer) {
272        self.pattern.iter_mut().fold(0, |a, f| {
273            a + match f {
274                FieldTypeHolder::U8(holder) => {
275                    buffer.set(a, holder.apply());
276                    size_of::<u8>()
277                }
278                FieldTypeHolder::U16(holder) => {
279                    let holder_size = size_of::<u16>();
280                    let field_slice = buffer.rebuffer_both(a, a + holder_size);
281                    field_slice.slice_mut().copy_from_slice(&holder.apply().to_be_bytes());
282                    holder_size
283                }
284                FieldTypeHolder::U32(holder) => {
285                    let holder_size = size_of::<u32>();
286                    let field_slice = buffer.rebuffer_both(a, a + holder_size);
287                    field_slice.slice_mut().copy_from_slice(&holder.apply().to_be_bytes());
288                    holder_size
289                }
290                FieldTypeHolder::U64(holder) => {
291                    let holder_size = size_of::<u64>();
292                    let field_slice = buffer.rebuffer_both(a, a + holder_size);
293                    field_slice.slice_mut().copy_from_slice(&holder.apply().to_be_bytes());
294                    holder_size
295                }
296            }
297        });
298    }
299}
300
301/// Configuration for a flow.
302#[derive(Debug, Clone)]
303pub struct FlowConfig {
304    /// Whether to use fake bodies.
305    pub(super) fake_body_mode: FakeBodyMode,
306    /// Whether to use fake headers.
307    pub(super) fake_header_mode: FakeHeaderConfig,
308}
309
310impl FlowConfig {
311    pub fn new(fake_body_mode: FakeBodyMode, fake_header_mode: FakeHeaderConfig) -> Self {
312        Self {
313            fake_body_mode,
314            fake_header_mode,
315        }
316    }
317
318    /// Create a random flow configuration drawn from the default probability distributions.
319    ///
320    /// - Headers: included with probability `FAKE_HEADER_PROBABILITY`; if included, a random number
321    ///   of fields are packed to fill a length sampled from
322    ///   `[FAKE_HEADER_LENGTH_MIN, FAKE_HEADER_LENGTH_MAX]`.
323    /// - Body: chosen by the `FAKE_BODY_WEIGHT_*` settings (Empty / Random / Constant / Random{service}).
324    ///   In `Constant` mode `packet_length` is sampled **once at flow init** from
325    ///   `[FAKE_BODY_CONSTANT_LENGTH_MIN, FAKE_BODY_CONSTANT_LENGTH_MAX]` (clamped to
326    ///   `[FAKE_BODY_LENGTH_MIN, mtu]`) and then held constant for every packet
327    ///   in that flow — different flows get different constants, breaking the
328    ///   sharp single-mode wire-size spike that a global fixed-length Constant
329    ///   would produce.
330    pub fn random<AE: AsyncExecutor>(settings: &Settings<AE>) -> Self {
331        let fake_header_mode = FakeHeaderConfig::random(settings);
332
333        let min_len = settings.get(&keys::FAKE_BODY_LENGTH_MIN) as usize;
334        let max_len = settings.get(&keys::FAKE_BODY_LENGTH_MAX) as usize;
335
336        let constant_min = (settings.get(&keys::FAKE_BODY_CONSTANT_LENGTH_MIN) as usize).clamp(min_len, settings.mtu());
337        let constant_max = (settings.get(&keys::FAKE_BODY_CONSTANT_LENGTH_MAX) as usize).clamp(min_len, settings.mtu());
338        let constant_length = if constant_min >= constant_max {
339            constant_min
340        } else {
341            get_rng().gen_range(constant_min..=constant_max)
342        };
343
344        let fake_body_mode = weighted_random! {
345            settings.get(&keys::FAKE_BODY_WEIGHT_EMPTY) => FakeBodyMode::Empty,
346            settings.get(&keys::FAKE_BODY_WEIGHT_RANDOM) => FakeBodyMode::Random {
347                    min_length: min_len,
348                    max_length: max_len,
349                    service: false,
350            },
351            settings.get(&keys::FAKE_BODY_WEIGHT_CONSTANT) => FakeBodyMode::Constant {
352                packet_length: constant_length,
353            },
354            settings.get(&keys::FAKE_BODY_WEIGHT_SERVICE) => FakeBodyMode::Random {
355                min_length: min_len,
356                max_length: max_len,
357                service: true,
358            }
359        };
360
361        info!("flow_config: fake_body={:?}, fake_header_len={}", fake_body_mode, fake_header_mode.len());
362        Self {
363            fake_body_mode,
364            fake_header_mode,
365        }
366    }
367
368    /// Maximum bytes this flow config can prepend to a packet (fake header + worst-case fake body).
369    /// Used to reserve before_capacity in packet buffers. Conservative for Constant mode.
370    pub fn max_overhead(&self) -> usize {
371        self.fake_header_mode.len() + self.fake_body_mode.max_len()
372    }
373
374    /// Maximum user-data bytes per packet given MTU and the per-packet crypto/tailer overhead.
375    /// For Constant mode the wire size is fixed to `packet_length`, so the data budget is
376    /// `min(packet_length, mtu) - (fake_header + crypto + tailer)`.
377    /// For other modes it is `mtu - (fake_header + fake_body_max + crypto + tailer)`.
378    pub fn max_user_payload(&self, mtu: usize, crypto_overhead: usize, tailer_len: usize) -> usize {
379        let fixed = self.fake_header_mode.len() + crypto_overhead + tailer_len;
380        match &self.fake_body_mode {
381            FakeBodyMode::Constant {
382                packet_length,
383            } => packet_length.min(&mtu).saturating_sub(fixed),
384            _ => mtu.saturating_sub(self.max_overhead() + crypto_overhead + tailer_len),
385        }
386    }
387
388    /// Validate that the flow configuration is consistent with the given max packet size.
389    pub fn assert(&self, max_packet_size: usize) -> Result<(), FlowControllerError> {
390        match &self.fake_body_mode {
391            FakeBodyMode::Constant {
392                packet_length,
393            } => {
394                if *packet_length > max_packet_size {
395                    return Err(FlowControllerError::AssertionFailed {
396                        message: format!("constant fake body packet_length ({packet_length}) must not exceed max_packet_size ({max_packet_size})"),
397                    });
398                }
399            }
400            FakeBodyMode::Random {
401                min_length,
402                max_length,
403                ..
404            } => {
405                if min_length > max_length {
406                    return Err(FlowControllerError::AssertionFailed {
407                        message: format!("random fake body min_length ({min_length}) must be <= max_length ({max_length})"),
408                    });
409                }
410            }
411            FakeBodyMode::Empty => {}
412        }
413
414        let header_len = self.fake_header_mode.len();
415        if header_len > max_packet_size {
416            return Err(FlowControllerError::AssertionFailed {
417                message: format!("fake header length ({header_len}) must not exceed max_packet_size ({max_packet_size})"),
418            });
419        }
420
421        Ok(())
422    }
423}