s2n_quic_core/packet/interceptor/
loss.rs1use super::{havoc, Interceptor};
5use core::ops::Range;
6
7#[derive(Debug)]
8struct Direction {
9 loss: Range<u64>,
10 pass: Range<u64>,
11 mode: Mode,
12}
13
14impl Default for Direction {
15 fn default() -> Self {
16 Self {
17 loss: 0..0,
18 pass: u64::MAX..u64::MAX,
19 mode: Mode::Loss { remaining: 0 },
20 }
21 }
22}
23
24impl Direction {
25 #[inline]
26 fn should_pass<R: havoc::Random>(&mut self, random: &mut R) -> bool {
27 let (remaining, mut is_pass) = match &mut self.mode {
28 Mode::Pass { remaining } => (remaining, true),
29 Mode::Loss { remaining } => (remaining, false),
30 };
31
32 if let Some(new_value) = remaining.checked_sub(1) {
34 *remaining = new_value;
35 return is_pass;
36 }
37
38 for _ in 0..3 {
41 is_pass = !is_pass;
43
44 let remaining = if is_pass {
45 Self::gen_range(&self.pass, random)
46 } else {
47 Self::gen_range(&self.loss, random)
48 };
49
50 if remaining == 0 {
52 continue;
53 }
54
55 if is_pass {
56 self.mode = Mode::Pass { remaining };
57 } else {
58 self.mode = Mode::Loss { remaining };
59 }
60 }
61
62 is_pass
63 }
64
65 #[inline]
66 fn gen_range<R: havoc::Random>(range: &Range<u64>, random: &mut R) -> u64 {
67 if range.start == range.end {
68 return range.start;
69 }
70
71 random.gen_range(range.clone())
72 }
73}
74
75#[derive(Debug)]
76enum Mode {
77 Loss { remaining: u64 },
78 Pass { remaining: u64 },
79}
80
81#[derive(Debug, Default)]
82pub struct Builder<R> {
83 tx: Direction,
84 rx: Direction,
85 random: R,
86}
87
88impl<R> Builder<R>
89where
90 R: 'static + Send + havoc::Random,
91{
92 pub fn new(random: R) -> Self {
93 Self {
94 tx: Direction::default(),
95 rx: Direction::default(),
96 random,
97 }
98 }
99
100 pub fn with_tx_pass(mut self, range: Range<u64>) -> Self {
101 self.tx.pass = range;
102 self
103 }
104
105 pub fn with_tx_loss(mut self, range: Range<u64>) -> Self {
106 self.tx.loss = range;
107 self
108 }
109
110 pub fn with_rx_pass(mut self, range: Range<u64>) -> Self {
111 self.rx.pass = range;
112 self
113 }
114
115 pub fn with_rx_loss(mut self, range: Range<u64>) -> Self {
116 self.rx.loss = range;
117 self
118 }
119
120 pub fn build(self) -> Loss<R> {
121 Loss {
122 tx: self.tx,
123 rx: self.rx,
124 random: self.random,
125 }
126 }
127}
128
129#[derive(Debug, Default)]
130pub struct Loss<R>
131where
132 R: 'static + Send + havoc::Random,
133{
134 tx: Direction,
135 rx: Direction,
136 random: R,
137}
138
139impl<R> Loss<R>
140where
141 R: 'static + Send + havoc::Random,
142{
143 pub fn builder(random: R) -> Builder<R> {
144 Builder::new(random)
145 }
146}
147
148impl<R> Interceptor for Loss<R>
149where
150 R: 'static + Send + havoc::Random,
151{
152 #[inline]
153 fn intercept_rx_datagram<'a>(
154 &mut self,
155 _subject: &crate::event::api::Subject,
156 _datagram: &super::Datagram,
157 payload: s2n_codec::DecoderBufferMut<'a>,
158 ) -> s2n_codec::DecoderBufferMut<'a> {
159 if !self.rx.should_pass(&mut self.random) {
160 return s2n_codec::DecoderBufferMut::new(&mut payload.into_less_safe_slice()[..0]);
161 }
162
163 payload
164 }
165
166 #[inline]
167 fn intercept_tx_datagram(
168 &mut self,
169 _subject: &crate::event::api::Subject,
170 _datagram: &super::Datagram,
171 payload: &mut s2n_codec::EncoderBuffer,
172 ) {
173 if !self.tx.should_pass(&mut self.random) {
174 payload.set_position(0);
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::{havoc::testing::RandomSlice, *};
182
183 #[test]
184 fn alternate_test() {
185 static SLICE: &[u8] = &{
186 let mut slice = [0u8; 256];
187 let mut i = 0;
188 while i < slice.len() {
189 slice[i] = i as _;
190 i += 1;
191 }
192 slice
193 };
194
195 let mut rand = RandomSlice::new(SLICE);
196
197 let mut rx = Direction {
198 loss: 0..10,
199 pass: 1..10,
200 ..Default::default()
201 };
202
203 let mut passed = 0;
204 let mut dropped = 0;
205
206 for _ in 0..256 {
207 if rx.should_pass(&mut rand) {
208 passed += 1;
209 } else {
210 dropped += 1;
211 }
212 }
213
214 assert_eq!(passed, 143);
216 assert_eq!(dropped, 113);
217 }
218}