From 2392559a390d0d342351a94b1570aae8b210142a Mon Sep 17 00:00:00 2001 From: peterc-s Date: Thu, 30 Apr 2026 23:20:41 +0100 Subject: [PATCH] Fix constant machine by adding multiple actions per state --- crates/chaff-capture/src/trace.rs | 3 +- crates/chaff-machines/src/constant.rs | 84 +++++++------------ crates/chaff-machines/src/test.rs | 4 +- crates/chaff-sim/src/lib.rs | 115 ++++++++++++++++---------- crates/chaff/src/event.rs | 9 +- crates/chaff/src/framework.rs | 104 +++++++++++------------ crates/chaff/src/machine.rs | 90 +++++++++----------- crates/chaff/src/state.rs | 20 +++-- 8 files changed, 221 insertions(+), 208 deletions(-) diff --git a/crates/chaff-capture/src/trace.rs b/crates/chaff-capture/src/trace.rs index 8838690..ea8f712 100644 --- a/crates/chaff-capture/src/trace.rs +++ b/crates/chaff-capture/src/trace.rs @@ -36,7 +36,8 @@ impl TryFrom for Direction { | Event::MachineBudgetReached | Event::MachineBudgetRecovered | Event::QueueEmpty(_) - | Event::SendBlocked => Err(CaptureError::CantConvert), + | Event::SendBlocked + | Event::BlockReleased => Err(CaptureError::CantConvert), } } } diff --git a/crates/chaff-machines/src/constant.rs b/crates/chaff-machines/src/constant.rs index 3e05e5c..0788b3a 100644 --- a/crates/chaff-machines/src/constant.rs +++ b/crates/chaff-machines/src/constant.rs @@ -4,13 +4,7 @@ use std::time::Duration; -use chaff::{ - action::{FrameworkAction, IntegratorAction}, - distr::Distr, - event::Event, - machine, - machine::Machine, -}; +use chaff::{action::IntegratorAction, distr::Distr, event::Event, machine, machine::Machine}; /// Construct the constant machine. /// @@ -18,70 +12,52 @@ use chaff::{ /// /// Should not panic unless modified. #[must_use] -#[expect(clippy::expect_used)] +#[expect(clippy::unwrap_used, clippy::expect_used)] pub fn construct() -> Machine { - let hundred_ms: Distr = Duration::from_millis(100).try_into().expect("valid distr"); - let long_delay: Distr = Duration::from_micros(u64::MAX) - .try_into() - .expect("valid distr"); - let zero: Distr = Duration::ZERO.try_into().expect("valid distr"); + let hundred_ms: Distr = Duration::from_millis(100).try_into().unwrap(); machine! { - queues: [Some(1), Some(1), Some(1)], - budget: Absolute(500), - state init { - action: IntegratorAction::BlockOutgoing(long_delay), // block immediately - transitions: [ - Event::SendBlocked => schedule_release, // first blocked packet - ], - }, - state schedule_release { - // schedule a release with a 100ms delay - action: FrameworkAction::schedule( - IntegratorAction::ReleaseBlock, - 0, - hundred_ms, - ), - transitions: [ - // since we scheduled on a 1 capacity queue, this transition should - // happen immediately - Event::QueueFilled(0) => blocked, + queues: [], + budget: Proportion(3.5), + + state wait_clean { + actions: [ + IntegratorAction::BlockOutgoing(hundred_ms), ], - }, - state blocked { transitions: [ - Event::QueueEmpty(0) => send_decoy, // no real packet sent - Event::SendBlocked => wait, // real packet sent + Event::SendBlocked => wait_dirty, + Event::BlockReleased => send_decoy, + Event::MachineBudgetReached => end, ] }, - state wait { + + state wait_dirty { transitions: [ - Event::QueueEmpty(0) => do_block, // wait until release happens + Event::BlockReleased => wait_clean, + Event::MachineBudgetReached => end, ] }, + state send_decoy { - action: FrameworkAction::schedule( + actions: [ IntegratorAction::SendDecoy, - 2, - zero, - ), + IntegratorAction::BlockOutgoing(hundred_ms), + ], transitions: [ - // similar trick to schedule_release, this time used to do two actions - // in quick succession - Event::QueueFilled(2) => do_block, + Event::SendBlocked => wait_dirty, + Event::BlockReleased => send_decoy, + Event::MachineBudgetReached => end, ] }, - state do_block { - action: FrameworkAction::schedule( - IntegratorAction::BlockOutgoing(long_delay), - 1, - zero, - ), + + state end { + actions: [ + IntegratorAction::ReleaseBlock + ], transitions: [ - // similar trick to send_decoy - Event::QueueFilled(1) => schedule_release, + Event::MachineBudgetRecovered => wait_clean, ], - }, + } } .expect("preconstructed machine should be valid") } diff --git a/crates/chaff-machines/src/test.rs b/crates/chaff-machines/src/test.rs index 331dcb8..0502ce2 100644 --- a/crates/chaff-machines/src/test.rs +++ b/crates/chaff-machines/src/test.rs @@ -25,8 +25,8 @@ pub fn construct_test_machine() -> Machine { Machine::try_new( vec![ - State::new(Some(trans_probs), Some(IntegratorAction::SendDecoy), None), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(Some(trans_probs), [IntegratorAction::SendDecoy], None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [None], None, diff --git a/crates/chaff-sim/src/lib.rs b/crates/chaff-sim/src/lib.rs index 55c484f..b4d6a8d 100644 --- a/crates/chaff-sim/src/lib.rs +++ b/crates/chaff-sim/src/lib.rs @@ -299,19 +299,27 @@ impl Simulator { let mut out_builder = TraceBuilder::default(); let base_instant = Instant::now(); + let mut buffered_events = Vec::new(); while let Some(sim_now) = self.next_earliest_time(&block_state, base_instant) { - if block_state.until.is_some_and(|until| until <= sim_now) { + let block_released = if block_state.until.is_some_and(|until| until <= sim_now) { self.queue.extend(block_state.release(sim_now)); - } + true + } else { + false + }; let mut events_now = Vec::new(); - while self.queue.peek_time().is_some_and(|t| t == sim_now) { - if let Some(event) = self.queue.pop() { - events_now.push(event); + if self.framework.is_initialised() { + while self.queue.peek_time().is_some_and(|t| t == sim_now) { + if let Some(event) = self.queue.pop() { + events_now.push(event); + } } } - let mut buffered_events = Vec::with_capacity(events_now.len()); + if block_released { + buffered_events.push(Event::BlockReleased); + } for event in &events_now { if event.event == Event::SendNormal && block_state.is_active_at(event.time) { block_state.buffer(event.clone()); @@ -330,6 +338,7 @@ impl Simulator { let sim_instant = base_instant + Duration::from_micros(sim_now); let actions = self.framework.process(&buffered_events, sim_instant); + buffered_events.clear(); actions.into_iter().for_each(|action| match action { IntegratorAction::SendDecoy => { @@ -346,6 +355,7 @@ impl Simulator { } IntegratorAction::ReleaseBlock => { self.queue.extend(block_state.release(sim_now)); + buffered_events.push(Event::BlockReleased); } }); } @@ -480,17 +490,13 @@ mod tests { let machine = Machine::try_new( vec![ - State::new( - Some(trans_0_to_1), - Some(IntegratorAction::ReleaseBlock), - None, - ), + State::new(Some(trans_0_to_1), [IntegratorAction::ReleaseBlock], None), State::new( Some(trans_1_to_2), - Some(IntegratorAction::block_outgoing(long_delay)), + [IntegratorAction::block_outgoing(long_delay)], None, ), - State::new(None, Some(IntegratorAction::ReleaseBlock), None), + State::new(None, [IntegratorAction::ReleaseBlock], None), ], [], None, @@ -546,12 +552,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans), Some(IntegratorAction::ReleaseBlock), None), - State::new( - None, - Some(IntegratorAction::block_outgoing(long_delay)), - None, - ), + State::new(Some(trans), [IntegratorAction::ReleaseBlock], None), + State::new(None, [IntegratorAction::block_outgoing(long_delay)], None), ], [], None, @@ -591,8 +593,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans), Some(IntegratorAction::ReleaseBlock), None), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(Some(trans), [IntegratorAction::ReleaseBlock], None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [], None, @@ -638,12 +640,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new( - Some(trans_0_to_1), - Some(IntegratorAction::ReleaseBlock), - None, - ), - State::new(None, Some(IntegratorAction::BlockOutgoing(uniform)), None), + State::new(Some(trans_0_to_1), [IntegratorAction::ReleaseBlock], None), + State::new(None, [IntegratorAction::BlockOutgoing(uniform)], None), ], [], None, @@ -704,12 +702,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans), Some(IntegratorAction::ReleaseBlock), None), - State::new( - None, - Some(IntegratorAction::block_outgoing(long_delay)), - None, - ), + State::new(Some(trans), [IntegratorAction::ReleaseBlock], None), + State::new(None, [IntegratorAction::block_outgoing(long_delay)], None), ], [], None, @@ -744,7 +738,7 @@ mod tests { let machine = machine! { queues: [], state dummy { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -771,11 +765,11 @@ mod tests { let machine = machine! { queues: [None], state init { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(delay_secs).try_into().unwrap() - ), + )], } } .unwrap(); @@ -814,11 +808,11 @@ mod tests { let machine = machine! { queues: [None], state init { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(0.0).try_into().unwrap() - ), + )], } } .unwrap(); @@ -856,11 +850,11 @@ mod tests { let machine = machine! { queues: [None], state init { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(1.0).try_into().unwrap() - ), + )], } } .unwrap(); @@ -896,11 +890,11 @@ mod tests { let machine = machine! { queues: [None; 2], state init { - action: FrameworkAction::schedule(IntegratorAction::SendDecoy, 0, delay), + actions: [FrameworkAction::schedule(IntegratorAction::SendDecoy, 0, delay)], transitions: [Event::ReceiveNormal => schedule_second] }, state schedule_second { - action: FrameworkAction::schedule(IntegratorAction::SendDecoy, 1, delay), + actions: [FrameworkAction::schedule(IntegratorAction::SendDecoy, 1, delay)], }, } .unwrap(); @@ -940,11 +934,11 @@ mod tests { let machine = machine! { queues: [None], state init { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(1.0).try_into().unwrap() - ), + )], } } .unwrap(); @@ -963,4 +957,37 @@ mod tests { // TODO: simulator is "perfect" in that it always jumps to the soonest event and an // integrator is not in that they don't know the soonest event. need some way to simulate this // and also the tests for it. + + #[cfg(test)] + mod machines { + use super::Simulator; + use chaff::framework::Framework; + use chaff_capture::trace::{Direction, Trace}; + use chaff_machines::constant; + + #[test] + pub fn test_constant_machine() { + let machine = constant::construct(); + let framework = Framework::new(machine, rand::rng()); + let trace = Trace::new( + [Direction::Send; 7], + [0, 123, 100_000, 200_000, 104, 204_213, 1_000_000], + [0; 7], + ); + let mut simulator = Simulator::with(framework, trace, rand::rng()); + let (out, _) = simulator.run(); + + let deltas = out.timing_deltas(); + + assert!( + deltas + .iter() + .filter(|delta| **delta != 0 && **delta != 100_000) + .count() + == 0, + "found poorly spaced deltas: {deltas:?}", + ); + assert!(out.len() < 35, "len: {}", out.len()); + } + } } diff --git a/crates/chaff/src/event.rs b/crates/chaff/src/event.rs index 220197d..a8a9d28 100644 --- a/crates/chaff/src/event.rs +++ b/crates/chaff/src/event.rs @@ -22,6 +22,9 @@ pub enum Event { /// Decoy packet received (ingress). Emitted by integrator. ReceiveDecoy, + /// Blocking released. Emitted by integrator. + BlockReleased, + /// Packet blocked from sending. Emitted by framework. SendBlocked, @@ -59,7 +62,11 @@ impl Event { #[must_use] pub fn is_deferred(&self) -> bool { match self { - Self::SendNormal | Self::ReceiveNormal | Self::SendDecoy | Self::ReceiveDecoy => false, + Self::SendNormal + | Self::ReceiveNormal + | Self::SendDecoy + | Self::ReceiveDecoy + | Self::BlockReleased => false, Self::QueuePopped(_) | Self::QueueFull(_) | Self::QueueFilled(_) diff --git a/crates/chaff/src/framework.rs b/crates/chaff/src/framework.rs index ec81987..52d93e5 100644 --- a/crates/chaff/src/framework.rs +++ b/crates/chaff/src/framework.rs @@ -162,8 +162,8 @@ impl Framework { // initialisation if !self.runtime.initialised { - if let Some(action) = &self.machine.states[self.runtime.state].action { - actions.push(action.clone()); + if !&self.machine.states[self.runtime.state].actions.is_empty() { + actions.extend(self.machine.states[self.runtime.state].actions.clone()); } self.runtime.initialised = true; } @@ -200,8 +200,8 @@ impl Framework { } } - if let Some(action) = &self.machine.states[new_state].action { - actions.push(action.clone()); + if !&self.machine.states[self.runtime.state].actions.is_empty() { + actions.extend(self.machine.states[self.runtime.state].actions.clone()); } } } @@ -281,10 +281,10 @@ mod tests { vec![ State::new( Some(trans_probs.clone()), - Some(IntegratorAction::SendDecoy), + [IntegratorAction::SendDecoy], None, ), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [], None, @@ -305,10 +305,10 @@ mod tests { vec![ State::new( Some(trans_probs.clone()), - Some(IntegratorAction::SendDecoy), + [IntegratorAction::SendDecoy], None, ), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [], None, @@ -334,10 +334,10 @@ mod tests { vec![ State::new( Some(trans_probs.clone()), - Some(IntegratorAction::SendDecoy), + [IntegratorAction::SendDecoy], None, ), - State::new(None, Some(FrameworkAction::CancelAll), None), + State::new(None, [FrameworkAction::CancelAll], None), ], [], None, @@ -357,7 +357,7 @@ mod tests { #[test] fn test_get_trans_probs_state_with_no_trans_probs() { let machine = Machine::try_new( - vec![State::new(None, Some(IntegratorAction::SendDecoy), None)], + vec![State::new(None, [IntegratorAction::SendDecoy], None)], [], None, ) @@ -377,8 +377,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), None::, None), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new::(Some(trans_probs), [], None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [], None, @@ -401,8 +401,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), Some(IntegratorAction::SendDecoy), None), - State::new(None, Some(FrameworkAction::CancelAll), None), + State::new(Some(trans_probs), [IntegratorAction::SendDecoy], None), + State::new(None, [FrameworkAction::CancelAll], None), ], [None, None], None, @@ -432,8 +432,8 @@ mod tests { .unwrap(); let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), Some(IntegratorAction::SendDecoy), None), - State::new(None, Some(FrameworkAction::CancelQueue(0)), None), + State::new(Some(trans_probs), [IntegratorAction::SendDecoy], None), + State::new(None, [FrameworkAction::CancelQueue(0)], None), ], [None, None], None, @@ -470,11 +470,11 @@ mod tests { transitions: [Event::SendNormal => schedule_decoy], }, state schedule_decoy { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(999.0).try_into().unwrap() - ), + )], } } .unwrap(); @@ -500,14 +500,14 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), None::, None), + State::new::(Some(trans_probs), [], None), State::new( None, - Some(FrameworkAction::schedule( + [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(999.0).try_into().unwrap(), - )), + )], None, ), ], @@ -533,7 +533,7 @@ mod tests { #[test] fn test_perform_action_cancel_all_via_queue() { let machine = Machine::try_new( - vec![State::new(None, Some(IntegratorAction::SendDecoy), None)], + vec![State::new(None, [IntegratorAction::SendDecoy], None)], [None; 3], None, ) @@ -565,7 +565,7 @@ mod tests { #[test] fn test_perform_action_cancel_queue_via_queue() { let machine = Machine::try_new( - vec![State::new(None, Some(IntegratorAction::SendDecoy), None)], + vec![State::new(None, [IntegratorAction::SendDecoy], None)], [None, None], None, ) @@ -601,7 +601,7 @@ mod tests { #[test] fn test_perform_action_schedule_via_queue() { let machine = Machine::try_new( - vec![State::new(None, None::, None)], + vec![State::new::(None, [], None)], [None, None], None, ) @@ -643,7 +643,7 @@ mod tests { transitions: [Event::QueuePopped(0) => release], }, state release { - action: IntegratorAction::ReleaseBlock + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -689,15 +689,15 @@ mod tests { let machine = machine! { queues: [None], state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], transitions: [Event::QueuePopped(0) => jump], }, state jump { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], transitions: [Event::SendNormal => end], }, state end { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -725,7 +725,7 @@ mod tests { #[test] fn test_framework_action_schedule_samples_delay() { let machine = Machine::try_new( - vec![State::new(None, Some(IntegratorAction::ReleaseBlock), None)], + vec![State::new(None, [IntegratorAction::ReleaseBlock], None)], [None], None, ) @@ -748,26 +748,26 @@ mod tests { let machine = machine! { queues: [Some(1)], state init { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, long_delay, - ), + )], transitions: [ Event::ReceiveNormal => init, Event::SendNormal => overflow, ], }, state overflow { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, long_delay, - ), + )], transitions: [Event::QueueFull(0) => end], }, state end { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -793,16 +793,16 @@ mod tests { let machine = machine! { queues: [], state init { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], transitions: [Event::SendNormal => decoy_burst], }, state decoy_burst { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], transitions: [Event::StateBudgetExhausted => end], budget: 1, }, state end { - action: IntegratorAction::ReleaseBlock + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -825,12 +825,12 @@ mod tests { let machine = machine! { queues: [None], state init { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], transitions: [Event::StateBudgetExhausted => end], budget: 1, }, state end { - action: IntegratorAction::ReleaseBlock + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -856,11 +856,11 @@ mod tests { let machine = machine! { queues: [], state init { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], transitions: [Event::ReceiveNormal => decoy_burst], }, state decoy_burst { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], transitions: [ Event::SendNormal => decoy_burst, Event::StateBudgetExhausted => end @@ -868,7 +868,7 @@ mod tests { budget: 2, }, state end { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], budget: 0, } } @@ -899,26 +899,26 @@ mod tests { let machine = machine! { queues: [Some(2)], state init { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], transitions: [Event::ReceiveNormal => fill_queue], }, state fill_queue { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Constant(Duration::from_millis(15).as_secs_f64()).try_into().unwrap(), - ), + )], transitions: [ Event::SendNormal => fill_queue, Event::ReceiveNormal => wait_for_queue_drain ], }, state wait_for_queue_drain { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], transitions: [Event::QueueEmpty(0) => end], }, state end { - action: IntegratorAction::ReleaseBlock, + actions: [IntegratorAction::ReleaseBlock], } } .unwrap(); @@ -1002,7 +1002,7 @@ mod tests { queues: [None], budget: Absolute(1), state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], }, } .unwrap(); @@ -1047,7 +1047,7 @@ mod tests { queues: [None], budget: Proportion(0.5), state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], }, } .unwrap(); @@ -1101,7 +1101,7 @@ mod tests { queues: [], budget: Absolute(0), state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], budget: 1 }, } @@ -1215,7 +1215,7 @@ mod tests { queues: [None], budget: Absolute(1), state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], } } .unwrap(); diff --git a/crates/chaff/src/machine.rs b/crates/chaff/src/machine.rs index 0a0e7c5..2225940 100644 --- a/crates/chaff/src/machine.rs +++ b/crates/chaff/src/machine.rs @@ -78,19 +78,22 @@ impl Machine { // non-existent queues let mut invalid_state_action_queues = states .iter() - .map(|state| state.action.clone()) - .filter_map(|action| match action { - Some(Action::Framework(framework_action)) => match framework_action { - FrameworkAction::Schedule { queue, .. } - | FrameworkAction::CancelQueue(queue) - if queue > queues => - { - Some(queue) - } - _ => None, - }, - Some(Action::Integrator(_)) | None => None, + .map(|state| state.actions.clone()) + .flat_map(|actions| { + actions.into_iter().map(|action| match action { + Action::Framework(framework_action) => match framework_action { + FrameworkAction::Schedule { queue, .. } + | FrameworkAction::CancelQueue(queue) + if queue > queues => + { + Some(queue) + } + _ => None, + }, + Action::Integrator(_) => None, + }) }) + .flatten() .peekable(); if invalid_state_action_queues.peek().is_some() { @@ -181,7 +184,7 @@ impl borsh::BorshDeserialize for Machine { /// budget: Proportion(0.5), /// /// state init { -/// action: IntegratorAction::SendDecoy, +/// actions: [IntegratorAction::SendDecoy], /// budget: 25, /// transitions: [ /// Event::SendNormal => [(jump, 0.5)], @@ -196,7 +199,7 @@ impl borsh::BorshDeserialize for Machine { /// }, /// /// state end { -/// action: IntegratorAction::SendDecoy +/// actions: [IntegratorAction::SendDecoy], /// } /// }.unwrap(); /// @@ -236,9 +239,10 @@ macro_rules! machine { }; // state field parsing - // parse `action:` - updates the status to found - (@parse_state $a:ident $t:ident $b:ident action: $action:expr $(, $($rest:tt)*)? ) => { - $a = ::core::option::Option::Some($action.into()); + (@parse_state $a:ident $t:ident $b:ident actions: [ $( $action:expr ),* $(,)? ] $(, $($rest:tt)*)? ) => { + $( + $a.push($action.into()); + )* $crate::machine!(@parse_state $a $t $b $($($rest)*)?); }; @@ -298,16 +302,16 @@ macro_rules! machine { // build states $( - let mut _action: ::core::option::Option<$crate::action::Action> = ::core::option::Option::None; + let mut _actions: std::vec::Vec<$crate::action::Action> = ::std::vec::Vec::new(); let mut _probs = ::core::option::Option::None; let mut _budget = ::core::option::Option::None; // assign state properties - $crate::machine!(@parse_state _action _probs _budget $($body)*); + $crate::machine!(@parse_state _actions _probs _budget $($body)*); states.push($crate::state::State::new( _probs, - _action, + _actions.into_boxed_slice(), _budget, )); )* @@ -457,8 +461,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), Some(IntegratorAction::SendDecoy), None), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(Some(trans_probs), [IntegratorAction::SendDecoy], None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [None; 42], None, @@ -501,8 +505,8 @@ mod tests { let machine = Machine::try_new( vec![ - State::new(Some(trans_probs), Some(IntegratorAction::SendDecoy), None), - State::new(None, Some(IntegratorAction::SendDecoy), None), + State::new(Some(trans_probs), [IntegratorAction::SendDecoy], None), + State::new(None, [IntegratorAction::SendDecoy], None), ], [None; 42], None, @@ -526,18 +530,14 @@ mod tests { let machine = Machine::try_new( vec![ - State::new( - Some(trans_probs), - Some(FrameworkAction::CancelQueue(1)), - None, - ), + State::new(Some(trans_probs), [FrameworkAction::CancelQueue(1)], None), State::new( None, - Some(FrameworkAction::schedule( + [FrameworkAction::schedule( IntegratorAction::SendDecoy, 1, const_distr, - )), + )], None, ), ], @@ -558,18 +558,14 @@ mod tests { let machine = Machine::try_new( vec![ + State::new(Some(trans_probs), [FrameworkAction::CancelQueue(2)], None), State::new( - Some(trans_probs), - Some(FrameworkAction::CancelQueue(2)), None, - ), - State::new( - None, - Some(FrameworkAction::schedule( + [FrameworkAction::schedule( IntegratorAction::SendDecoy, 3, const_distr, - )), + )], None, ), ], @@ -600,18 +596,14 @@ mod tests { let machine = Machine::try_new( vec![ - State::new( - Some(trans_probs), - Some(FrameworkAction::CancelQueue(2)), - None, - ), + State::new(Some(trans_probs), [FrameworkAction::CancelQueue(2)], None), State::new( None, - Some(FrameworkAction::schedule( + [FrameworkAction::schedule( IntegratorAction::SendDecoy, 3, const_distr, - )), + )], None, ), ], @@ -680,7 +672,7 @@ mod tests { budget: Proportion(0.67), state init { - action: IntegratorAction::SendDecoy, + actions: [IntegratorAction::SendDecoy], budget: 25, transitions: [ Event::SendNormal => [(jump, 0.5), (end, 0.5)], @@ -696,21 +688,21 @@ mod tests { }, state other { - action: FrameworkAction::schedule( + actions: [FrameworkAction::schedule( IntegratorAction::SendDecoy, 0, DistrKind::Uniform { low: 0.1, high: 0.2 }.try_into().unwrap() - ), + )], transitions: [ Event::SendNormal => end, ] }, state end { - action: IntegratorAction::SendDecoy + actions: [IntegratorAction::SendDecoy], } } .unwrap(); diff --git a/crates/chaff/src/state.rs b/crates/chaff/src/state.rs index e8c96fd..27344b2 100644 --- a/crates/chaff/src/state.rs +++ b/crates/chaff/src/state.rs @@ -17,7 +17,7 @@ pub struct State { pub(crate) trans_probs: Option, /// The action to take on transitioning to this state. - pub(crate) action: Option, + pub(crate) actions: Box<[Action]>, /// The number of decoys this state can send via self-transition (includes initial transition to /// this state). @@ -27,14 +27,24 @@ pub struct State { impl State { /// Create a new state with the given [`TransitionProbs`], [`Action`] to take on transition, and /// a budget for the number of decoys the state can send during self-transition. - pub fn new( + pub fn new( trans_probs: impl Into>, - action: Option>, + actions: I, decoy_budget: Option, - ) -> Self { + ) -> Self + where + I: IntoIterator, + A: Into, + { + let actions_boxed = actions + .into_iter() + .map(Into::into) + .collect::>() + .into_boxed_slice(); + Self { trans_probs: trans_probs.into(), - action: action.map(std::convert::Into::into), + actions: actions_boxed, decoy_budget, } }