Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/chaff-capture/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ impl TryFrom<Event> for Direction {
Event::ReceiveNormal | Event::ReceiveDecoy => Ok(Self::Receive),
Event::QueuePopped(_)
| Event::QueueFull(_)
| Event::QueueFilled(_)
| Event::QueuePushed(_)
| Event::StateBudgetExhausted
| Event::MachineBudgetExhausted
| Event::MachineBudgetReached
| Event::MachineBudgetRecovered
| Event::QueueEmpty(_) => Err(CaptureError::CantConvert),
| Event::QueueEmpty(_)
| Event::SendBlocked => Err(CaptureError::CantConvert),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/chaff-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ bpaf = { version = "0.9.24", features = ["derive"] }
rand = "0.10.1"
mac_address = "1.1.8"
pcap = "2.4.0"
iterstats = "0.7.0"
4 changes: 2 additions & 2 deletions crates/chaff-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use chaff_cli::{
errors::CliError,
subcommands::{cap_convert, capture, dataset_convert, dataset_stats, simulate, trace_stats},
};
use chaff_machines::test::construct_test_machine;
use chaff_machines::constant;

/// Command-line interface options
#[derive(Debug, Clone, Bpaf)]
Expand Down Expand Up @@ -150,7 +150,7 @@ fn run() -> Result<(), CliError> {
let mut file = File::open(path)?;
Machine::deserialize_reader(&mut file)?
} else {
construct_test_machine()
constant::construct()
};

match action {
Expand Down
18 changes: 13 additions & 5 deletions crates/chaff-cli/src/subcommands/trace_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,32 @@ use chaff_capture::trace::{Direction, Trace};

use crate::errors::CliError;

use iterstats::Iterstats as _;

/// Run the trace stats subcommand with the given trace.
///
/// # Errors
///
/// - If deserialising the trace fails [`Trace::deserialise`].
#[expect(clippy::cast_precision_loss)]
#[expect(clippy::similar_names)]
pub fn run(input: &PathBuf) -> Result<(), CliError> {
let trace = Trace::deserialise(&input)?;
let deltas = trace.timing_deltas();
let sizes = trace.sizes();
let total = trace.len();

let sent = trace
.directions()
.iter()
.filter(|direction| **direction == Direction::Send)
.count();
let avg_delta = f64::from(deltas.iter().sum::<u32>()) / total as f64;
let avg_size = f64::from(trace.sizes().iter().sum::<u32>()) / total as f64;

let avg_delta = deltas.iter().map(|delta| f64::from(*delta)).mean();
let std_delta = deltas.iter().map(|delta| f64::from(*delta)).stddev();

let avg_size = sizes.iter().map(|size| f64::from(*size)).mean();
let std_size = sizes.iter().map(|size| f64::from(*size)).stddev();

let largest_burst_0mus = deltas
.split(|&delta| delta != 0)
.map(<[u32]>::len)
Expand Down Expand Up @@ -61,8 +69,8 @@ pub fn run(input: &PathBuf) -> Result<(), CliError> {
println!("Packets: {total}");
println!("Sent: {sent}");
println!("Received: {}", total - sent);
println!("Average packet size: {avg_size:.2} bytes");
println!("Average time delta: {avg_delta:.2}μs");
println!("Average packet size: {avg_size:.2}±{std_size:.2} bytes");
println!("Average time delta: {avg_delta:.2}±{std_delta:.2}μs");
println!("Largest time delta: {largest_delta}μs");
println!("Largest burst (0μs): {largest_burst_0mus} packets");
println!("Largest burst (100μs): {largest_burst_100mus} packets");
Expand Down
87 changes: 87 additions & 0 deletions crates/chaff-machines/src/constant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! An attempted implementation of a machine that tries to make packets flow at a constant rate.

#![cfg(not(tarpaulin_include))]

use std::time::Duration;

use chaff::{
action::{FrameworkAction, IntegratorAction},
distr::Distr,
event::Event,
machine,
machine::Machine,
};

/// Construct the constant machine.
///
/// # Panics
///
/// Should not panic unless modified.
#[must_use]
#[expect(clippy::expect_used)]
pub fn construct() -> Machine {
let hundred_ms: Distr = Duration::from_millis(100)
.try_into()
.expect("constant shouldn't cause validation issue");
let max: Distr = Duration::from_nanos(u64::MAX)
.try_into()
.expect("constant shouldn't cause validation issue");
let zero: Distr = Duration::ZERO
.try_into()
.expect("constant shouldn't cause validation issue");

machine! {
queues: [Some(1), Some(1), Some(1)],
budget: Absolute(500),
state init {
action: IntegratorAction::BlockOutgoing(max),
transitions: [
Event::SendNormal => init,
Event::SendBlocked => schedule_release,
Event::ReceiveNormal => schedule_release,
],
},
state do_block {
action: FrameworkAction::schedule(
IntegratorAction::BlockOutgoing(max),
1,
zero,
),
transitions: [
Event::QueueFilled(1) => schedule_release,
],
},
state send_decoy {
action: FrameworkAction::schedule(
IntegratorAction::SendDecoy,
2,
zero,
),
transitions: [
Event::QueueFilled(2) => do_block,
]
},
state schedule_release {
action: FrameworkAction::schedule(
IntegratorAction::ReleaseBlock,
0,
hundred_ms,
),
transitions: [
Event::QueueFilled(0) => blocked,
],
},
state blocked {
transitions: [
Event::QueueEmpty(0) => send_decoy,
Event::SendBlocked => wait,
]
},
state wait {
transitions: [
Event::QueueEmpty(0) => do_block,
]
},
}
.expect("predefined machine should be valid")
}
1 change: 1 addition & 0 deletions crates/chaff-machines/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Machines written in the Chaff framework.

pub mod constant;
pub mod test;
2 changes: 2 additions & 0 deletions crates/chaff-sim/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! The Chaff simulator for creating defended traces with machines.

use std::{
cmp::Ordering,
collections::{BTreeMap, VecDeque},
Expand Down Expand Up @@ -314,6 +315,7 @@ impl<R: Rng + CryptoRng> Simulator<R> {
for event in &events_now {
if event.event == Event::SendNormal && block_state.is_active_at(event.time) {
block_state.buffer(event.clone());
buffered_events.push(Event::SendBlocked);
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/chaff/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl FrameworkAction {
feature = "borsh",
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
)]
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IntegratorAction {
/// Send a decoy packet.
SendDecoy,
Expand Down
16 changes: 14 additions & 2 deletions crates/chaff/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ pub enum Event {
/// Decoy packet received (ingress). Emitted by integrator.
ReceiveDecoy,

/// Packet blocked from sending. Emitted by framework.
SendBlocked,

/// Given queue was popped. Emitted by framework.
QueuePopped(u8),

/// Given queue has reached capacity. Emitted by framework.
/// Given queue was pushed to. Emitted by framework.
QueuePushed(u8),

/// Given queue was pushed to but has been filled. Emitted by framework.
QueueFilled(u8),

/// Given queue is already at capacity. Emitted by framework.
QueueFull(u8),

/// Given queue has emptied. Emitted by framework.
Expand Down Expand Up @@ -53,11 +62,14 @@ impl Event {
Self::SendNormal | Self::ReceiveNormal | Self::SendDecoy | Self::ReceiveDecoy => false,
Self::QueuePopped(_)
| Self::QueueFull(_)
| Self::QueueFilled(_)
| Self::QueuePushed(_)
| Self::StateBudgetExhausted
| Self::MachineBudgetExhausted
| Self::MachineBudgetReached
| Self::MachineBudgetRecovered
| Self::QueueEmpty(_) => true,
| Self::QueueEmpty(_)
| Self::SendBlocked => true,
}
}
}
14 changes: 11 additions & 3 deletions crates/chaff/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
action::{Action, FrameworkAction, IntegratorAction},
event::Event,
machine::{Machine, MachineDecoyBudget, MachineRuntime},
queue::{TimedAction, TimedQueue},
queue::{QueuePushStatus, TimedAction, TimedQueue},
state::TransitionProbs,
};

Expand Down Expand Up @@ -57,11 +57,19 @@ impl<R: Rng + CryptoRng> Framework<R> {
queue,
delay,
} => {
if !self.runtime.queues[queue as usize].push(TimedAction {
match self.runtime.queues[queue as usize].push(TimedAction {
execute_at: now + delay.sample(&mut self.rng),
action: int_action.into(),
}) {
self.runtime.deferred_events.push(Event::QueueFull(queue));
QueuePushStatus::Pushed => {
self.runtime.deferred_events.push(Event::QueuePushed(queue));
}
QueuePushStatus::PushedButFull => {
self.runtime.deferred_events.push(Event::QueueFilled(queue));
}
QueuePushStatus::Full => {
self.runtime.deferred_events.push(Event::QueueFull(queue));
}
}
}
FrameworkAction::CancelQueue(queue) => self.runtime.queues[queue as usize].cancel(),
Expand Down
3 changes: 3 additions & 0 deletions crates/chaff/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ impl Machine {
errors.push(ValidationError::NegativeProportion(percent));
}

// TODO: maybe validate transitions too, i.e. no point in having a QueueFilled(10)
// transition when there's only 10 queues.

#[expect(clippy::expect_used)]
match errors.len() {
0 => Ok(()),
Expand Down
24 changes: 21 additions & 3 deletions crates/chaff/src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ impl Timed for TimedAction {
}
}

/// Status from pushing to a [`TimedQueue`]
pub enum QueuePushStatus {
/// Push successful.
Pushed,

/// Push successful but queue now full.
PushedButFull,

/// Push unsuccessful as queue full.
Full,
}

/// A priority queue of [`Scheduled`] objects, ordered by their [`Timed::execute_at`]. Under the hood
/// this is just a [`BinaryHeap<Scheduled<T>>`].
#[derive(Debug, Clone, Default)]
Expand All @@ -84,14 +96,20 @@ impl<T: Timed> TimedQueue<T> {

/// Push an item onto the [`TimedQueue`].
#[must_use]
pub fn push(&mut self, item: T) -> bool {
pub fn push(&mut self, item: T) -> QueuePushStatus {
if let Some(capacity) = self.capacity
&& self.queue.len() >= capacity
{
false
QueuePushStatus::Full
} else {
self.queue.push(Scheduled(item));
true
if let Some(capacity) = self.capacity
&& self.queue.len() >= capacity
{
QueuePushStatus::PushedButFull
} else {
QueuePushStatus::Pushed
}
}
}

Expand Down
Loading