1use std::str::FromStr;
2
3use anyhow::{Result, bail};
4use enum_map::enum_map;
5use maybenot::{Machine, state::State};
6use rand_core::RngCore;
7use serde::{Deserialize, Serialize};
8
9pub mod break_pad;
10pub mod front;
11pub mod interspace;
12pub mod netflow;
13pub mod regulator;
14pub mod scrambler;
15pub mod tamaraw;
16
17#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
20pub enum StaticMachine {
21 NoOp,
23 SimpleNetFlow,
26 RegulatorClient {
28 u: f64,
30 c: f64,
32 },
33 RegulatorServer {
35 r: f64,
37 d: f64,
39 t: f64,
41 n: f64,
43 b: usize,
45 },
46 Tamaraw {
48 p: f64,
50 stop_window: f64,
53 },
54 InterspaceClient,
56 InterspaceServer,
58 Front {
60 padding_budget_max: u32,
62 window_min: f64,
64 window_max: f64,
66 num_states: usize,
68 },
69 BreakPadClient,
71 BreakPadServer,
73 ScramblerClient,
75 ScramblerServer {
77 interval: f64,
79 min_count: f64,
81 min_trail: f64,
83 max_trail: f64,
85 },
86}
87
88impl FromStr for StaticMachine {
89 type Err = anyhow::Error;
90
91 fn from_str(s: &str) -> Result<Self> {
92 if s.contains("tamaraw ") {
93 let parts: Vec<&str> = s.split_whitespace().collect();
94 if parts.len() != 3 {
95 bail!("invalid tamaraw defense: {}", s);
96 }
97 let p = parts[1].parse::<f64>().map_err(|_| {
98 anyhow::anyhow!("invalid tamaraw defense parameter 'p': {}", parts[1])
99 })?;
100 let stop_window = parts[2].parse::<f64>().map_err(|_| {
101 anyhow::anyhow!(
102 "invalid tamaraw defense parameter 'stop_window': {}",
103 parts[2]
104 )
105 })?;
106 return Ok(StaticMachine::Tamaraw { p, stop_window });
107 }
108
109 if s.contains("regulator_client ") {
110 let parts: Vec<&str> = s.split_whitespace().collect();
111 if parts.len() != 3 {
112 bail!("invalid regulator client defense: {}", s);
113 }
114 let u = parts[1].parse::<f64>().map_err(|_| {
115 anyhow::anyhow!(
116 "invalid regulator client defense parameter 'u': {}",
117 parts[1]
118 )
119 })?;
120 let c = parts[2].parse::<f64>().map_err(|_| {
121 anyhow::anyhow!(
122 "invalid regulator client defense parameter 'c': {}",
123 parts[2]
124 )
125 })?;
126 return Ok(StaticMachine::RegulatorClient { u, c });
127 }
128 if s.contains("regulator_server ") {
129 let parts: Vec<&str> = s.split_whitespace().collect();
130 if parts.len() != 6 {
131 bail!("invalid regulator server defense: {}", s);
132 }
133 let r = parts[1].parse::<f64>().map_err(|_| {
134 anyhow::anyhow!(
135 "invalid regulator server defense parameter 'r': {}",
136 parts[1]
137 )
138 })?;
139 let d = parts[2].parse::<f64>().map_err(|_| {
140 anyhow::anyhow!(
141 "invalid regulator server defense parameter 'd': {}",
142 parts[2]
143 )
144 })?;
145 let t = parts[3].parse::<f64>().map_err(|_| {
146 anyhow::anyhow!(
147 "invalid regulator server defense parameter 't': {}",
148 parts[3]
149 )
150 })?;
151 let n = parts[4].parse::<f64>().map_err(|_| {
152 anyhow::anyhow!(
153 "invalid regulator server defense parameter 'n': {}",
154 parts[4]
155 )
156 })?;
157 let b = parts[5].parse::<usize>().map_err(|_| {
158 anyhow::anyhow!(
159 "invalid regulator server defense parameter 'b': {}",
160 parts[5]
161 )
162 })?;
163 return Ok(StaticMachine::RegulatorServer { r, d, t, n, b });
164 }
165
166 if s.contains("front ") {
167 let parts: Vec<&str> = s.split_whitespace().collect();
168 if parts.len() != 5 {
169 bail!("invalid front defense: {}", s);
170 }
171 let padding_budget_max = parts[1].parse::<u32>().map_err(|_| {
172 anyhow::anyhow!(
173 "invalid front defense parameter 'padding_budget_max': {}",
174 parts[1]
175 )
176 })?;
177 let window_min = parts[2].parse::<f64>().map_err(|_| {
178 anyhow::anyhow!("invalid front defense parameter 'window_min': {}", parts[2])
179 })?;
180 let window_max = parts[3].parse::<f64>().map_err(|_| {
181 anyhow::anyhow!("invalid front defense parameter 'window_max': {}", parts[3])
182 })?;
183 let num_states = parts[4].parse::<usize>().map_err(|_| {
184 anyhow::anyhow!("invalid front defense parameter 'num_states': {}", parts[4])
185 })?;
186 return Ok(StaticMachine::Front {
187 padding_budget_max,
188 window_min,
189 window_max,
190 num_states,
191 });
192 }
193
194 if s.contains("scrambler_server") {
195 let parts: Vec<&str> = s.split_whitespace().collect();
196 if parts.len() != 5 {
197 bail!("invalid scrambler server defense: {}", s);
198 }
199 let interval = parts[1].parse::<f64>().map_err(|_| {
200 anyhow::anyhow!(
201 "invalid scrambler server defense parameter 'interval': {}",
202 parts[1]
203 )
204 })?;
205 let min_count = parts[2].parse::<f64>().map_err(|_| {
206 anyhow::anyhow!(
207 "invalid scrambler server defense parameter 'min_count': {}",
208 parts[2]
209 )
210 })?;
211 let min_trail = parts[3].parse::<f64>().map_err(|_| {
212 anyhow::anyhow!(
213 "invalid scrambler server defense parameter 'min_trail': {}",
214 parts[3]
215 )
216 })?;
217 let max_trail = parts[4].parse::<f64>().map_err(|_| {
218 anyhow::anyhow!(
219 "invalid scrambler server defense parameter 'max_trail': {}",
220 parts[4]
221 )
222 })?;
223 return Ok(StaticMachine::ScramblerServer {
224 interval,
225 min_count,
226 min_trail,
227 max_trail,
228 });
229 }
230
231 match s {
232 "noop" => Ok(StaticMachine::NoOp),
233 "netflow" => Ok(StaticMachine::SimpleNetFlow),
234 "interspace_client" => Ok(StaticMachine::InterspaceClient),
235 "interspace_server" => Ok(StaticMachine::InterspaceServer),
236 "break_pad_client" => Ok(StaticMachine::BreakPadClient),
237 "break_pad_server" => Ok(StaticMachine::BreakPadServer),
238 "scrambler_client" => Ok(StaticMachine::ScramblerClient),
239 _ => bail!("invalid static machine: {}", s),
240 }
241 }
242}
243
244pub fn get_static_machine_strings() -> Vec<String> {
245 vec![
246 "noop".to_string(),
247 "netflow".to_string(),
248 "break_pad_client".to_string(),
249 "break_pad_server".to_string(),
250 "interspace_client".to_string(),
251 "interspace_server".to_string(),
252 "front padding_budget_max window_min window_max num_states".to_string(),
253 "tamaraw padding_rate stop_window".to_string(),
254 "regulator_client u c".to_string(),
255 "regulator_server r d t n b".to_string(),
256 "scrambler_client".to_string(),
257 "scrambler_server interval min_count min_trail max_trail".to_string(),
258 ]
259}
260
261pub fn get_machine<R: RngCore>(s: &[StaticMachine], rng: &mut R) -> Vec<Machine> {
263 let mut machines = vec![];
264
265 for m in s {
266 match m {
267 StaticMachine::NoOp => machines.push(no_op_machine()),
268 StaticMachine::SimpleNetFlow => machines.push(netflow::simple_netflow()),
269 StaticMachine::RegulatorClient { u, c } => {
270 machines.extend(regulator::regulator_client(*u, *c))
271 }
272 StaticMachine::RegulatorServer { r, d, t, n, b } => {
273 machines.extend(regulator::regulator_server(*r, *d, *t, *n, *b))
274 }
275 StaticMachine::Tamaraw { p, stop_window } => {
276 machines.extend(tamaraw::tamaraw(*p, *stop_window))
277 }
278 StaticMachine::InterspaceClient => machines.extend(interspace::interspace_client(rng)),
279 StaticMachine::InterspaceServer => machines.extend(interspace::interspace_server(rng)),
280 StaticMachine::Front {
281 padding_budget_max,
282 window_min,
283 window_max,
284 num_states,
285 } => machines.extend(front::front(
286 *padding_budget_max,
287 *window_min,
288 *window_max,
289 *num_states,
290 rng,
291 )),
292 StaticMachine::BreakPadClient => machines.extend(break_pad::break_pad_client()),
293 StaticMachine::BreakPadServer => machines.extend(break_pad::break_pad_server()),
294 StaticMachine::ScramblerClient => machines.extend(scrambler::scrambler_client()),
295 StaticMachine::ScramblerServer {
296 interval,
297 min_count,
298 min_trail,
299 max_trail,
300 } => machines.extend(scrambler::scrambler_server(
301 *interval, *min_count, *min_trail, *max_trail,
302 )),
303 }
304 }
305
306 machines
307}
308
309fn no_op_machine() -> Machine {
310 let s0 = State::new(enum_map! {
311 _ => vec![],
312 });
313 Machine::new(0, 0.0, 0, 0.0, vec![s0]).unwrap()
314}
315
316#[cfg(test)]
318mod tests {
319 use maybenot::{
320 action::Action,
321 dist::{Dist, DistType},
322 event::Event,
323 state::Trans,
324 };
325
326 use crate::*;
327
328 #[test]
329 fn test_no_op() {
330 assert!(no_op_machine().validate().is_ok());
331 assert_eq!(no_op_machine().serialize(), "02eNpjYEAHjOgCAAA0AAI=")
332 }
333
334 #[test]
335 fn test_example_machine() {
336 let mut states = vec![];
337
338 let start_state = State::new(enum_map! {
339 Event::TunnelRecv => vec![Trans(1, 1.0)],
340 _ => vec![],
341 });
342 states.push(start_state);
343
344 let mut padding_state = State::new(enum_map! {
345 Event::PaddingSent => vec![Trans(0, 1.0)],
346 _ => vec![],
347 });
348 padding_state.action = Some(Action::SendPadding {
349 bypass: false,
350 replace: false,
351 timeout: Dist {
352 dist: DistType::Uniform {
353 low: 0.0,
354 high: 0.0,
355 },
356 start: 5_000_000.0,
357 max: 0.0,
358 },
359 limit: None,
360 });
361 states.push(padding_state);
362
363 let m = Machine::new(u64::MAX, 1.0, 0, 0.0, states).unwrap();
364 assert_eq!(
365 m.serialize(),
366 "02eNpti1EJACAQQzcjWEjMYCEjGsUCojiUg+Pex2CPbe0HxCz4JCVJoJvF7SEjt+qUtnY+gDUNIg=="
367 );
368 }
369
370 #[test]
371 fn test_ping_1s_loop_machine() {
372 let mut states = vec![];
373
374 let start_state = State::new(enum_map! {
375 Event::NormalSent => vec![Trans(1, 1.0)],
376 Event::NormalRecv => vec![Trans(1, 1.0)],
377 _ => vec![],
378 });
379 states.push(start_state);
380
381 let mut padding_state = State::new(enum_map! {
382 Event::PaddingSent => vec![Trans(1, 1.0)],
383 _ => vec![],
384 });
385 padding_state.action = Some(Action::SendPadding {
386 bypass: false,
387 replace: false,
388 timeout: Dist {
389 dist: DistType::Uniform {
390 low: 0.0,
391 high: 0.0,
392 },
393 start: 1_000_000.0,
394 max: 0.0,
395 },
396 limit: None,
397 });
398 states.push(padding_state);
399
400 let m = Machine::new(u64::MAX, 1.0, 0, 0.0, states).unwrap();
401 assert_eq!(
402 m.serialize(),
403 "02eNpty8sJACAMA9DEgVyhuFkPDuwC4o8KpfRBoQlkLoNnCL5yjiSg4h5zY0p7baEK2w33hw3i"
404 );
405 }
406}