diff --git a/.gitignore b/.gitignore index 4fffb2f..d78768a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /Cargo.lock +*.a diff --git a/Cargo.toml b/Cargo.toml index 620d411..81d89f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ resolver = "2" members = [ # lib for the Maybenot framework "crates/maybenot", + # ffi lib for the Maybenot framework + "crates/maybenot-ffi", # simulator for the Maybenot framework "crates/maybenot-simulator", ] @@ -34,4 +36,4 @@ explicit_outlives_requirements = "warn" missing_abi = "deny" unused_lifetimes = "warn" unused_macro_rules = "warn" -single_use_lifetimes = "warn" \ No newline at end of file +single_use_lifetimes = "warn" diff --git a/README.md b/README.md index d845ab1..dc2129d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ framework for creating defenses that hide such patterns. The Maybenot workspace consists of the following crates: - [maybenot](crates/maybenot): The core framework for creating defenses. +- [maybenot-ffi](crates/maybenot-ffi): A wrapper library around maybenot with a C FFI. - [maybenot-simulator](crates/maybenot-simulator): A simulator for testing defenses. diff --git a/crates/maybenot-ffi/Cargo.toml b/crates/maybenot-ffi/Cargo.toml new file mode 100644 index 0000000..60af438 --- /dev/null +++ b/crates/maybenot-ffi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "maybenot-ffi" +description = "An FFI wrapper around Maybenot" +version = "2.0.0" +edition.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +repository.workspace = true + +[lib] +crate-type = ["lib", "staticlib", "cdylib"] + +[dependencies] +maybenot = { version = "2.0.0", path = "../maybenot" } +rand = "0.8.5" +rand_chacha = "0.3.1" diff --git a/crates/maybenot-ffi/Makefile b/crates/maybenot-ffi/Makefile new file mode 100644 index 0000000..a681b0b --- /dev/null +++ b/crates/maybenot-ffi/Makefile @@ -0,0 +1,36 @@ +CBINDGEN ?= cbindgen +CARGO ?= cargo +TARGET ?= +PROFILE ?= release +DESTINATION ?= . +CARGO_TARGET_DIR ?= ../../target + +CARGO_OUTPUT_DIR := $(CARGO_TARGET_DIR)/$(TARGET)/$(PROFILE) +CARGOFLAGS += --target-dir $(CARGO_TARGET_DIR) + +ifeq ($(PROFILE), release) + CARGOFLAGS += --release +endif + +ifneq ($(TARGET),) + CARGOFLAGS += --target + CARGOFLAGS += $(TARGET) +endif + +.PHONY: clean + +# copy the library to the final destination, and strip the _ffi part +$(DESTINATION)/libmaybenot.a: $(CARGO_OUTPUT_DIR)/libmaybenot_ffi.a + cp $^ $@ + +# generate maybenot.h +maybenot.h: src/*.rs Cargo.toml cbindgen.toml + ${CBINDGEN} -o maybenot.h + +# build the static library +$(CARGO_OUTPUT_DIR)/libmaybenot_ffi.a: maybenot.h src/*.rs Cargo.toml cbindgen.toml + RUSTFLAGS="-C metadata=maybenot-ffi" ${CARGO} build $(CARGOFLAGS) + +clean: + rm -f $(DESTINATION)/libmaybenot.a + ${CARGO} clean $(CARGOFLAGS) diff --git a/crates/maybenot-ffi/README.md b/crates/maybenot-ffi/README.md new file mode 100644 index 0000000..5d85035 --- /dev/null +++ b/crates/maybenot-ffi/README.md @@ -0,0 +1,31 @@ +# Maybenot FFI + +This crate contains C FFI bindings for Maybenot, which let's you use Maybenot as a static library +for languages other than Rust. Headers are found at `maybenot-ffi/maybenot.h` and are +auto-generated when compiling using `make`. + +## Building +You need to have [rust](https://rustup.rs/) installed. +`cbindgen` is also required: `cargo install --force cbindgen` +Then just run `make` to build a static library at `maybenot-ffi/libmaybenot.a`. + +Arguments to `make`, including default values: +- `DESTINATION=.` - the directory where the output artifacts will be placed. +- `TARGET=` override target architecture; cross-compile. + Use `rustup target` to list and install targets. +- `PROFILE=release` - override the cargo profile, valid options are `release` and `debug`. +- `CARGO=cargo` - path to cargo. +- `CBINDGEN=cbindgen` - path to cbindgen. +- `CARGO_TARGET_DIR=../../target` - the build directory. + +Example: +``` +make TARGET=x86_64-unknown-linux-gnu PROFILE=debug +``` + +In order to link the resulting library to your program, you'll need to explicitly link some +additional dependencies in addition to `-lmaybenot`. +Run the following command to get an up-to-date list of the required flags for your platform: +``` +RUSTFLAGS="--print native-static-libs" cargo build +``` diff --git a/crates/maybenot-ffi/cbindgen.toml b/crates/maybenot-ffi/cbindgen.toml new file mode 100644 index 0000000..7ff27bf --- /dev/null +++ b/crates/maybenot-ffi/cbindgen.toml @@ -0,0 +1,9 @@ +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml + +language = "C" + +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = true + +[enum] +prefix_with_name = true diff --git a/crates/maybenot-ffi/maybenot.h b/crates/maybenot-ffi/maybenot.h new file mode 100644 index 0000000..65a5c5c --- /dev/null +++ b/crates/maybenot-ffi/maybenot.h @@ -0,0 +1,229 @@ +/* Generated with cbindgen:0.26.0 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include + +enum MaybenotEventType { + MaybenotEventType_NormalRecv = 0, + MaybenotEventType_PaddingRecv = 1, + MaybenotEventType_TunnelRecv = 2, + MaybenotEventType_NormalSent = 3, + MaybenotEventType_PaddingSent = 4, + MaybenotEventType_TunnelSent = 5, + MaybenotEventType_BlockingBegin = 6, + MaybenotEventType_BlockingEnd = 7, + MaybenotEventType_TimerBegin = 8, + MaybenotEventType_TimerEnd = 9, +}; +typedef uint32_t MaybenotEventType; + +/** + * An FFI friendly result error code type. + */ +enum MaybenotResult { + /** + * Operation completed successfully + */ + MaybenotResult_Ok = 0, + /** + * The machine string wasn't valid UTF-8 + */ + MaybenotResult_MachineStringNotUtf8 = 1, + /** + * Failed to parse machine string + */ + MaybenotResult_InvalidMachineString = 2, + /** + * Failed to start framework + */ + MaybenotResult_StartFramework = 3, + /** + * A null pointer was encountered + */ + MaybenotResult_NullPointer = 4, +}; +typedef uint32_t MaybenotResult; + +/** + * The different types of timers used by a [Machine]. + */ +enum MaybenotTimer { + /** + * The scheduled timer for actions with a timeout. + */ + MaybenotTimer_Action = 0, + /** + * The machine's internal timer, updated by the machine using [MaybenotAction::UpdateTimer]. + */ + MaybenotTimer_Internal = 1, + /** + * Apply to all timers. + */ + MaybenotTimer_All = 2, +}; +typedef uint32_t MaybenotTimer; + +/** + * A running Maybenot instance. + * + * - Create it: [maybenot_start]. + * - Feed it actions: [maybenot_on_events]. + * - Stop it: [maybenot_stop]. + */ +typedef struct MaybenotFramework MaybenotFramework; + +typedef struct MaybenotEvent { + MaybenotEventType event_type; + /** + * The ID of the machine that triggered the event, if any. + */ + uintptr_t machine; +} MaybenotEvent; + +typedef struct MaybenotDuration { + /** + * Number of whole seconds + */ + uint64_t secs; + /** + * A nanosecond fraction of a second. + */ + uint32_t nanos; +} MaybenotDuration; + +/** + * The action to be taken by the framework user. + */ +enum MaybenotAction_Tag { + /** + * Cancel the timer for a machine. + */ + MaybenotAction_Cancel = 0, + /** + * Schedule padding to be injected after the given timeout for a machine. + */ + MaybenotAction_SendPadding = 1, + /** + * Schedule blocking of outgoing traffic after the given timeout for a machine. + */ + MaybenotAction_BlockOutgoing = 2, + /** + * Update the timer duration for a machine. + */ + MaybenotAction_UpdateTimer = 3, +}; +typedef uint32_t MaybenotAction_Tag; + +typedef struct MaybenotAction_Cancel_Body { + /** + * The machine that generated the action. + */ + uintptr_t machine; + MaybenotTimer timer; +} MaybenotAction_Cancel_Body; + +typedef struct MaybenotAction_SendPadding_Body { + /** + * The machine that generated the action. + */ + uintptr_t machine; + /** + * The time to wait before injecting a padding packet. + */ + struct MaybenotDuration timeout; + bool replace; + bool bypass; +} MaybenotAction_SendPadding_Body; + +typedef struct MaybenotAction_BlockOutgoing_Body { + /** + * The machine that generated the action. + */ + uintptr_t machine; + /** + * The time to wait before blocking. + */ + struct MaybenotDuration timeout; + bool replace; + bool bypass; + /** + * How long to block. + */ + struct MaybenotDuration duration; +} MaybenotAction_BlockOutgoing_Body; + +typedef struct MaybenotAction_UpdateTimer_Body { + uintptr_t machine; + struct MaybenotDuration duration; + bool replace; +} MaybenotAction_UpdateTimer_Body; + +typedef struct MaybenotAction { + MaybenotAction_Tag tag; + union { + MaybenotAction_Cancel_Body cancel; + MaybenotAction_SendPadding_Body send_padding; + MaybenotAction_BlockOutgoing_Body block_outgoing; + MaybenotAction_UpdateTimer_Body update_timer; + }; +} MaybenotAction; + +/** + * Get the version of maybenot-ffi as a null terminated UTF-8-string. + * + * Example: `maybenot-ffi/1.0.1` + */ +const char *maybenot_version(void); + +/** + * Start a new [`MaybenotFramework`] instance. + * + * # Safety + * - `machines_str` must be a null-terminated UTF-8 string, containing LF-separated machines. + * - `out` must be a valid pointer to some valid and aligned pointer-sized memory. + * - The pointer written to `out` is NOT safe to be used concurrently. + */ +MaybenotResult maybenot_start(const char *machines_str, + double max_padding_frac, + double max_blocking_frac, + struct MaybenotFramework **out); + +/** + * Get the number of machines running in the [`MaybenotFramework`] instance. + * + * # Safety + * - `this` must have been created by [`maybenot_start`]. + */ +uintptr_t maybenot_num_machines(struct MaybenotFramework *this_); + +/** + * Stop a running [`MaybenotFramework`] instance. This will free the maybenot pointer. + * + * # Safety + * - `this` MUST have been created by [`maybenot_start`]. + * - `this` MUST NOT be used after it has been passed to [`maybenot_stop`]. + */ +void maybenot_stop(struct MaybenotFramework *this_); + +/** + * Feed events to the [`MaybenotFramework`] instance. + * + * This may generate [super::MaybenotAction]s that will be written to `actions_out`. + * The number of actions will be written to `num_actions_out`. + * + * # Safety + * - `this` MUST have been created by [`maybenot_start`]. + * - `events` MUST be a valid pointer to an array of size `num_events`. + * - `actions_out` MUST have capacity for [`maybenot_num_machines`] items of size + * `sizeof(MaybenotAction)` bytes. + * - `num_actions_out` MUST be a valid pointer where a 64bit int can be written. + */ +MaybenotResult maybenot_on_events(struct MaybenotFramework *this_, + const struct MaybenotEvent *events, + uintptr_t num_events, + struct MaybenotAction *actions_out, + uintptr_t *num_actions_out); diff --git a/crates/maybenot-ffi/src/error.rs b/crates/maybenot-ffi/src/error.rs new file mode 100644 index 0000000..1f0deea --- /dev/null +++ b/crates/maybenot-ffi/src/error.rs @@ -0,0 +1,25 @@ +/// An FFI friendly result error code type. +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +pub enum MaybenotResult { + /// Operation completed successfully + Ok = 0, + + /// The machine string wasn't valid UTF-8 + MachineStringNotUtf8 = 1, + + /// Failed to parse machine string + InvalidMachineString = 2, + + /// Failed to start framework + StartFramework = 3, + + /// A null pointer was encountered + NullPointer = 4, +} + +impl From> for MaybenotResult { + fn from(result: Result) -> Self { + result.map(|_| MaybenotResult::Ok).unwrap_or_else(|err| err) + } +} diff --git a/crates/maybenot-ffi/src/ffi.rs b/crates/maybenot-ffi/src/ffi.rs new file mode 100644 index 0000000..410eccc --- /dev/null +++ b/crates/maybenot-ffi/src/ffi.rs @@ -0,0 +1,133 @@ +use crate::{error::MaybenotResult, MaybenotAction, MaybenotEvent, MaybenotFramework}; +use core::{ + ffi::{c_char, CStr}, + mem::MaybeUninit, + slice::from_raw_parts_mut, +}; +use std::slice::from_raw_parts; + +// NOTE: must be null-terminated. +static VERSION: &str = concat!("maybenot-ffi/", env!("CARGO_PKG_VERSION"), "\0"); + +/// Get the version of maybenot-ffi as a null terminated UTF-8-string. +/// +/// Example: `maybenot-ffi/1.0.1` +#[no_mangle] +pub extern "C" fn maybenot_version() -> *const c_char { + debug_assert_eq!( + VERSION.find('\0'), + Some(VERSION.len() - 1), + "VERSION must be null terminated" + ); + + VERSION.as_ptr().cast() +} + +/// Start a new [`MaybenotFramework`] instance. +/// +/// # Safety +/// - `machines_str` must be a null-terminated UTF-8 string, containing LF-separated machines. +/// - `out` must be a valid pointer to some valid and aligned pointer-sized memory. +/// - The pointer written to `out` is NOT safe to be used concurrently. +#[no_mangle] +pub unsafe extern "C" fn maybenot_start( + machines_str: *const c_char, + max_padding_frac: f64, + max_blocking_frac: f64, + out: *mut MaybeUninit<*mut MaybenotFramework>, +) -> MaybenotResult { + // SAFETY: see function docs + let Some(out) = (unsafe { out.as_mut() }) else { + return MaybenotResult::NullPointer; + }; + + // SAFETY: see function docs + let machines_str = unsafe { CStr::from_ptr(machines_str) }; + let Ok(machines_str) = machines_str.to_str() else { + return MaybenotResult::MachineStringNotUtf8; + }; + + let framework = + match MaybenotFramework::start(machines_str, max_padding_frac, max_blocking_frac) { + Ok(framework) => framework, + Err(e) => return e, + }; + + // framework MUST be Sync if we are going to hand out references to it over FFI. + // we can assert this at compile time: + fn assert_sync(_: &impl Sync) {} + assert_sync(&framework); + + let box_pointer = Box::into_raw(Box::new(framework)); + out.write(box_pointer); + + MaybenotResult::Ok +} + +/// Get the number of machines running in the [`MaybenotFramework`] instance. +/// +/// # Safety +/// - `this` must have been created by [`maybenot_start`]. +#[no_mangle] +pub unsafe extern "C" fn maybenot_num_machines(this: *mut MaybenotFramework) -> usize { + let Some(this) = (unsafe { this.as_mut() }) else { + return 0; + }; + + this.framework.num_machines() +} + +/// Stop a running [`MaybenotFramework`] instance. This will free the maybenot pointer. +/// +/// # Safety +/// - `this` MUST have been created by [`maybenot_start`]. +/// - `this` MUST NOT be used after it has been passed to [`maybenot_stop`]. +#[no_mangle] +pub unsafe extern "C" fn maybenot_stop(this: *mut MaybenotFramework) { + // Reconstruct the Box and drop it. + // SAFETY: caller pinky promises that this pointer was created by `maybenot_start` + let _this = unsafe { Box::from_raw(this) }; +} + +/// Feed events to the [`MaybenotFramework`] instance. +/// +/// This may generate [super::MaybenotAction]s that will be written to `actions_out`. +/// The number of actions will be written to `num_actions_out`. +/// +/// # Safety +/// - `this` MUST have been created by [`maybenot_start`]. +/// - `events` MUST be a valid pointer to an array of size `num_events`. +/// - `actions_out` MUST have capacity for [`maybenot_num_machines`] items of size +/// `sizeof(MaybenotAction)` bytes. +/// - `num_actions_out` MUST be a valid pointer where a 64bit int can be written. +#[no_mangle] +pub unsafe extern "C" fn maybenot_on_events( + this: *mut MaybenotFramework, + events: *const MaybenotEvent, + num_events: usize, + actions_out: *mut MaybeUninit, + num_actions_out: *mut usize, +) -> MaybenotResult { + let Some(this) = (unsafe { this.as_mut() }) else { + return MaybenotResult::NullPointer; + }; + + if events.is_null() || actions_out.is_null() || num_actions_out.is_null() { + return MaybenotResult::NullPointer; + } + + // SAFETY: caller promises that `events` points to a valid array containing `num_events` events. + // Rust arrays have the same layout as C arrays. + let events: &[MaybenotEvent] = unsafe { from_raw_parts(events, num_events) }; + + // SAFETY: called promises that `actions_out` points to valid memory with the capacity to + // hold at least a `num_machines` amount of `MaybenotAction`. Rust arrays have the same + // layout as C arrays. Since we use `MaybeUninit`, rust won't assume that the slice + // elements have been initialized. + let actions: &mut [MaybeUninit] = + unsafe { from_raw_parts_mut(actions_out, this.framework.num_machines()) }; + + let num_actions = this.on_events(events, actions); + unsafe { num_actions_out.write(num_actions) }; + MaybenotResult::Ok +} diff --git a/crates/maybenot-ffi/src/lib.rs b/crates/maybenot-ffi/src/lib.rs new file mode 100644 index 0000000..e1b9b19 --- /dev/null +++ b/crates/maybenot-ffi/src/lib.rs @@ -0,0 +1,281 @@ +use core::{mem::MaybeUninit, str::FromStr, time::Duration}; +use std::time::Instant; + +use maybenot::{Framework, Machine, MachineId, TriggerEvent}; + +mod error; +pub use error::MaybenotResult; + +mod ffi; +pub use ffi::*; +use rand::{ + rngs::{adapter::ReseedingRng, OsRng}, + SeedableRng, +}; + +/// A running Maybenot instance. +/// +/// - Create it: [maybenot_start]. +/// - Feed it actions: [maybenot_on_events]. +/// - Stop it: [maybenot_stop]. +pub struct MaybenotFramework { + framework: Framework, Rng>, + + /// A buffer used internally for converting from [MaybenotEvent]s. + events_buf: Vec, +} + +/// The randomness generator used for the framework. +/// +/// This setup uses [OsRng] as the source of entropy, but extrapolates each call to [OsRng] into +/// at least [RNG_RESEED_THRESHOLD] bytes of randomness using [rand_chacha::ChaCha12Core]. +/// +/// This is the same Rng that [rand::thread_rng] uses internally, +/// but unlike thread_rng, this is Sync. +type Rng = ReseedingRng; +const RNG_RESEED_THRESHOLD: u64 = 1024 * 64; // 64 KiB + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct MaybenotEvent { + pub event_type: MaybenotEventType, + + /// The ID of the machine that triggered the event, if any. + pub machine: usize, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct MaybenotDuration { + /// Number of whole seconds + pub secs: u64, + + /// A nanosecond fraction of a second. + pub nanos: u32, +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub enum MaybenotEventType { + NormalRecv = 0, + PaddingRecv = 1, + TunnelRecv = 2, + + NormalSent = 3, + PaddingSent = 4, + TunnelSent = 5, + + BlockingBegin = 6, + BlockingEnd = 7, + + TimerBegin = 8, + TimerEnd = 9, +} + +/// The action to be taken by the framework user. +#[repr(C, u32)] +#[derive(Debug, Clone, Copy)] +pub enum MaybenotAction { + /// Cancel the timer for a machine. + Cancel { + /// The machine that generated the action. + machine: usize, + + timer: MaybenotTimer, + } = 0, + + /// Schedule padding to be injected after the given timeout for a machine. + SendPadding { + /// The machine that generated the action. + machine: usize, + + /// The time to wait before injecting a padding packet. + timeout: MaybenotDuration, + + replace: bool, + bypass: bool, + } = 1, + + /// Schedule blocking of outgoing traffic after the given timeout for a machine. + BlockOutgoing { + /// The machine that generated the action. + machine: usize, + + /// The time to wait before blocking. + timeout: MaybenotDuration, + + replace: bool, + bypass: bool, + + /// How long to block. + duration: MaybenotDuration, + } = 2, + + /// Update the timer duration for a machine. + UpdateTimer { + machine: usize, + + duration: MaybenotDuration, + + replace: bool, + } = 3, +} + +/// The different types of timers used by a [Machine]. +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub enum MaybenotTimer { + /// The scheduled timer for actions with a timeout. + Action = 0, + + /// The machine's internal timer, updated by the machine using [MaybenotAction::UpdateTimer]. + Internal = 1, + + /// Apply to all timers. + All = 2, +} + +impl MaybenotFramework { + fn start( + machines_str: &str, + max_padding_frac: f64, + max_blocking_frac: f64, + ) -> Result { + let machines: Vec<_> = machines_str + .lines() + .map(Machine::from_str) + .collect::>() + .map_err(|_e| MaybenotResult::InvalidMachineString)?; + + let machines_count = machines.len(); + + let rng_core = rand_chacha::ChaCha12Core::from_entropy(); + let rng = Rng::new(rng_core, RNG_RESEED_THRESHOLD, OsRng); + + let framework = Framework::new( + machines, + max_padding_frac, + max_blocking_frac, + Instant::now(), + rng, + ) + .map_err(|_e| MaybenotResult::StartFramework)?; + + Ok(MaybenotFramework { + framework, + events_buf: Vec::with_capacity(machines_count), + }) + } + + fn on_events( + &mut self, + events: &[MaybenotEvent], + actions: &mut [MaybeUninit], + ) -> usize { + let now = Instant::now(); + + // convert from the repr(C) events and store them temporarily in our buffer + self.events_buf.clear(); + for &event in events { + self.events_buf.push(convert_event(event)); + } + + let num_actions = self + .framework + .trigger_events(&self.events_buf, now) + // convert maybenot actions to repr(C) equivalents + .map(convert_action) + // write the actions to the out buffer + // NOTE: trigger_events will not emit more than one action per machine. + .zip(actions.iter_mut()) + .map(|(action, out)| out.write(action)) + .count(); + + num_actions + } +} + +/// Convert an action from [maybenot] to our own `repr(C)` action type. +fn convert_action(action: &maybenot::TriggerAction) -> MaybenotAction { + match *action { + maybenot::TriggerAction::Cancel { machine, timer } => MaybenotAction::Cancel { + machine: machine.into_raw(), + timer: timer.into(), + }, + maybenot::TriggerAction::SendPadding { + timeout, + bypass, + replace, + machine, + } => MaybenotAction::SendPadding { + timeout: timeout.into(), + replace, + bypass, + machine: machine.into_raw(), + }, + maybenot::TriggerAction::BlockOutgoing { + timeout, + duration, + bypass, + replace, + machine, + } => MaybenotAction::BlockOutgoing { + timeout: timeout.into(), + duration: duration.into(), + replace, + bypass, + machine: machine.into_raw(), + }, + maybenot::TriggerAction::UpdateTimer { + duration, + replace, + machine, + } => MaybenotAction::UpdateTimer { + duration: duration.into(), + replace, + machine: machine.into_raw(), + }, + } +} + +fn convert_event(event: MaybenotEvent) -> TriggerEvent { + let machine = MachineId::from_raw(event.machine); + + match event.event_type { + MaybenotEventType::NormalRecv => TriggerEvent::NormalRecv, + MaybenotEventType::PaddingRecv => TriggerEvent::PaddingRecv, + MaybenotEventType::TunnelRecv => TriggerEvent::TunnelRecv, + + MaybenotEventType::NormalSent => TriggerEvent::NormalSent, + MaybenotEventType::PaddingSent => TriggerEvent::PaddingSent { machine }, + MaybenotEventType::TunnelSent => TriggerEvent::TunnelSent, + + MaybenotEventType::BlockingBegin => TriggerEvent::BlockingBegin { machine }, + MaybenotEventType::BlockingEnd => TriggerEvent::BlockingEnd, + + MaybenotEventType::TimerBegin => TriggerEvent::TimerBegin { machine }, + MaybenotEventType::TimerEnd => TriggerEvent::TimerEnd { machine }, + } +} + +impl From for MaybenotDuration { + #[inline] + fn from(duration: Duration) -> Self { + MaybenotDuration { + secs: duration.as_secs(), + nanos: duration.subsec_nanos(), + } + } +} + +impl From for MaybenotTimer { + fn from(timer: maybenot::Timer) -> Self { + match timer { + maybenot::Timer::Action => MaybenotTimer::Action, + maybenot::Timer::Internal => MaybenotTimer::Internal, + maybenot::Timer::All => MaybenotTimer::All, + } + } +}