maybenot_ffi/
lib.rs

1use core::{mem::MaybeUninit, str::FromStr, time::Duration};
2use std::time::Instant;
3
4use maybenot::{Framework, Machine, MachineId, TriggerEvent};
5
6mod error;
7pub use error::MaybenotResult;
8
9mod ffi;
10pub use ffi::*;
11use rand::rngs::{OsRng, ReseedingRng};
12
13/// A running Maybenot instance.
14///
15/// - Create it: [maybenot_start].
16/// - Feed it actions: [maybenot_on_events].
17/// - Stop it: [maybenot_stop].
18pub struct MaybenotFramework {
19    framework: Framework<Vec<Machine>, Rng>,
20
21    /// A buffer used internally for converting from [MaybenotEvent]s.
22    events_buf: Vec<TriggerEvent>,
23}
24
25/// The randomness generator used for the framework.
26///
27/// This setup uses [OsRng] as the source of entropy, but extrapolates each call to [OsRng] into
28/// at least [RNG_RESEED_THRESHOLD] bytes of randomness using [rand_chacha::ChaCha12Core].
29///
30/// This is the same Rng that [rand::thread_rng] uses internally,
31/// but unlike thread_rng, this is Sync.
32type Rng = ReseedingRng<rand_chacha::ChaCha12Core, OsRng>;
33const RNG_RESEED_THRESHOLD: u64 = 1024 * 64; // 64 KiB
34
35#[repr(C)]
36#[derive(Debug, Clone, Copy)]
37pub struct MaybenotEvent {
38    pub event_type: MaybenotEventType,
39
40    /// The ID of the machine that triggered the event, if any.
41    pub machine: usize,
42}
43
44#[repr(C)]
45#[derive(Debug, Clone, Copy)]
46pub struct MaybenotDuration {
47    /// Number of whole seconds
48    pub secs: u64,
49
50    /// A nanosecond fraction of a second.
51    pub nanos: u32,
52}
53
54#[repr(u32)]
55#[derive(Debug, Clone, Copy)]
56#[allow(dead_code)]
57pub enum MaybenotEventType {
58    NormalRecv = 0,
59    PaddingRecv = 1,
60    TunnelRecv = 2,
61
62    NormalSent = 3,
63    PaddingSent = 4,
64    TunnelSent = 5,
65
66    BlockingBegin = 6,
67    BlockingEnd = 7,
68
69    TimerBegin = 8,
70    TimerEnd = 9,
71}
72
73/// The action to be taken by the framework user.
74#[repr(C, u32)]
75#[derive(Debug, Clone, Copy)]
76pub enum MaybenotAction {
77    /// Cancel the timer for a machine.
78    Cancel {
79        /// The machine that generated the action.
80        machine: usize,
81
82        timer: MaybenotTimer,
83    } = 0,
84
85    /// Schedule padding to be injected after the given timeout for a machine.
86    SendPadding {
87        /// The machine that generated the action.
88        machine: usize,
89
90        /// The time to wait before injecting a padding packet.
91        timeout: MaybenotDuration,
92
93        replace: bool,
94        bypass: bool,
95    } = 1,
96
97    /// Schedule blocking of outgoing traffic after the given timeout for a machine.
98    BlockOutgoing {
99        /// The machine that generated the action.
100        machine: usize,
101
102        /// The time to wait before blocking.
103        timeout: MaybenotDuration,
104
105        replace: bool,
106        bypass: bool,
107
108        /// How long to block.
109        duration: MaybenotDuration,
110    } = 2,
111
112    /// Update the timer duration for a machine.
113    UpdateTimer {
114        machine: usize,
115
116        duration: MaybenotDuration,
117
118        replace: bool,
119    } = 3,
120}
121
122/// The different types of timers used by a [Machine].
123#[repr(u32)]
124#[derive(Debug, Clone, Copy)]
125#[allow(dead_code)]
126pub enum MaybenotTimer {
127    /// The scheduled timer for actions with a timeout.
128    Action = 0,
129
130    /// The machine's internal timer, updated by the machine using [MaybenotAction::UpdateTimer].
131    Internal = 1,
132
133    /// Apply to all timers.
134    All = 2,
135}
136
137impl MaybenotFramework {
138    fn start(
139        machines_str: &str,
140        max_padding_frac: f64,
141        max_blocking_frac: f64,
142    ) -> Result<Self, MaybenotResult> {
143        let machines: Vec<_> = machines_str
144            .lines()
145            .map(Machine::from_str)
146            .collect::<Result<_, _>>()
147            .map_err(|_e| MaybenotResult::InvalidMachineString)?;
148
149        let machines_count = machines.len();
150
151        let rng = Rng::new(RNG_RESEED_THRESHOLD, OsRng).unwrap();
152
153        let framework = Framework::new(
154            machines,
155            max_padding_frac,
156            max_blocking_frac,
157            Instant::now(),
158            rng,
159        )
160        .map_err(|_e| MaybenotResult::StartFramework)?;
161
162        Ok(MaybenotFramework {
163            framework,
164            events_buf: Vec::with_capacity(machines_count),
165        })
166    }
167
168    fn on_events(
169        &mut self,
170        events: &[MaybenotEvent],
171        actions: &mut [MaybeUninit<MaybenotAction>],
172    ) -> usize {
173        let now = Instant::now();
174
175        // convert from the repr(C) events and store them temporarily in our buffer
176        self.events_buf.clear();
177        for &event in events {
178            self.events_buf.push(convert_event(event));
179        }
180
181        self.framework
182            .trigger_events(&self.events_buf, now)
183            // convert maybenot actions to repr(C) equivalents
184            .map(convert_action)
185            // write the actions to the out buffer
186            // NOTE: trigger_events will not emit more than one action per machine.
187            .zip(actions.iter_mut())
188            .map(|(action, out)| out.write(action))
189            .count()
190    }
191}
192
193/// Convert an action from [maybenot] to our own `repr(C)` action type.
194fn convert_action(action: &maybenot::TriggerAction) -> MaybenotAction {
195    match *action {
196        maybenot::TriggerAction::Cancel { machine, timer } => MaybenotAction::Cancel {
197            machine: machine.into_raw(),
198            timer: timer.into(),
199        },
200        maybenot::TriggerAction::SendPadding {
201            timeout,
202            bypass,
203            replace,
204            machine,
205        } => MaybenotAction::SendPadding {
206            timeout: timeout.into(),
207            replace,
208            bypass,
209            machine: machine.into_raw(),
210        },
211        maybenot::TriggerAction::BlockOutgoing {
212            timeout,
213            duration,
214            bypass,
215            replace,
216            machine,
217        } => MaybenotAction::BlockOutgoing {
218            timeout: timeout.into(),
219            duration: duration.into(),
220            replace,
221            bypass,
222            machine: machine.into_raw(),
223        },
224        maybenot::TriggerAction::UpdateTimer {
225            duration,
226            replace,
227            machine,
228        } => MaybenotAction::UpdateTimer {
229            duration: duration.into(),
230            replace,
231            machine: machine.into_raw(),
232        },
233    }
234}
235
236fn convert_event(event: MaybenotEvent) -> TriggerEvent {
237    let machine = MachineId::from_raw(event.machine);
238
239    match event.event_type {
240        MaybenotEventType::NormalRecv => TriggerEvent::NormalRecv,
241        MaybenotEventType::PaddingRecv => TriggerEvent::PaddingRecv,
242        MaybenotEventType::TunnelRecv => TriggerEvent::TunnelRecv,
243
244        MaybenotEventType::NormalSent => TriggerEvent::NormalSent,
245        MaybenotEventType::PaddingSent => TriggerEvent::PaddingSent { machine },
246        MaybenotEventType::TunnelSent => TriggerEvent::TunnelSent,
247
248        MaybenotEventType::BlockingBegin => TriggerEvent::BlockingBegin { machine },
249        MaybenotEventType::BlockingEnd => TriggerEvent::BlockingEnd,
250
251        MaybenotEventType::TimerBegin => TriggerEvent::TimerBegin { machine },
252        MaybenotEventType::TimerEnd => TriggerEvent::TimerEnd { machine },
253    }
254}
255
256impl From<Duration> for MaybenotDuration {
257    #[inline]
258    fn from(duration: Duration) -> Self {
259        MaybenotDuration {
260            secs: duration.as_secs(),
261            nanos: duration.subsec_nanos(),
262        }
263    }
264}
265
266impl From<maybenot::Timer> for MaybenotTimer {
267    fn from(timer: maybenot::Timer) -> Self {
268        match timer {
269            maybenot::Timer::Action => MaybenotTimer::Action,
270            maybenot::Timer::Internal => MaybenotTimer::Internal,
271            maybenot::Timer::All => MaybenotTimer::All,
272        }
273    }
274}