use crust::{CrustEventSender, PeerId, Service};
use crust::Event as CrustEvent;
#[cfg(feature = "use-mock-crust")]
use kademlia_routing_table::RoutingTable;
use maidsafe_utilities::event_sender::MaidSafeEventCategory;
use std::mem;
use std::sync::mpsc::{self, Receiver};
use action::Action;
use id::PublicId;
use states::{Bootstrapping, Client, JoiningNode, Node};
#[cfg(feature = "use-mock-crust")]
use states::Testable;
use timer::Timer;
use types::RoutingActionSender;
#[cfg(feature = "use-mock-crust")]
use xor_name::XorName;
pub struct StateMachine {
state: State,
category_rx: Receiver<MaidSafeEventCategory>,
crust_rx: Receiver<CrustEvent>,
action_rx: Receiver<Action>,
is_running: bool,
}
pub enum State {
Bootstrapping(Bootstrapping),
Client(Client),
JoiningNode(JoiningNode),
Node(Node),
Terminated,
}
impl State {
fn handle_action(&mut self, action: Action) -> Transition {
match *self {
State::Bootstrapping(ref mut state) => state.handle_action(action),
State::Client(ref mut state) => state.handle_action(action),
State::JoiningNode(ref mut state) => state.handle_action(action),
State::Node(ref mut state) => state.handle_action(action),
State::Terminated => Transition::Terminate,
}
}
fn handle_crust_event(&mut self, event: CrustEvent) -> Transition {
match *self {
State::Bootstrapping(ref mut state) => state.handle_crust_event(event),
State::Client(ref mut state) => state.handle_crust_event(event),
State::JoiningNode(ref mut state) => state.handle_crust_event(event),
State::Node(ref mut state) => state.handle_crust_event(event),
State::Terminated => Transition::Terminate,
}
}
fn into_bootstrapped(self,
proxy_peer_id: PeerId,
proxy_public_id: PublicId,
quorum_size: usize)
-> Self {
match self {
State::Bootstrapping(state) => {
if state.client_restriction() {
State::Client(state.into_client(proxy_peer_id, proxy_public_id, quorum_size))
} else if let Some(state) =
state.into_joining_node(proxy_peer_id, proxy_public_id, quorum_size) {
State::JoiningNode(state)
} else {
State::Terminated
}
}
_ => unreachable!(),
}
}
fn into_node(self, peer_id: PeerId, public_id: PublicId) -> Self {
match self {
State::JoiningNode(state) => State::Node(state.into_node(peer_id, public_id)),
_ => unreachable!(),
}
}
#[cfg(feature = "use-mock-crust")]
pub fn resend_unacknowledged(&mut self) -> bool {
match *self {
State::Client(ref mut state) => state.resend_unacknowledged(),
State::JoiningNode(ref mut state) => state.resend_unacknowledged(),
State::Node(ref mut state) => state.resend_unacknowledged(),
State::Bootstrapping(_) |
State::Terminated => false,
}
}
#[cfg(feature = "use-mock-crust")]
pub fn has_unacknowledged(&self) -> bool {
match *self {
State::Client(ref state) => state.has_unacknowledged(),
State::JoiningNode(ref state) => state.has_unacknowledged(),
State::Node(ref state) => state.has_unacknowledged(),
State::Bootstrapping(_) |
State::Terminated => false,
}
}
#[cfg(feature = "use-mock-crust")]
pub fn routing_table(&self) -> &RoutingTable<XorName> {
match *self {
State::JoiningNode(ref state) => state.routing_table(),
State::Node(ref state) => state.routing_table(),
_ => unreachable!(),
}
}
#[cfg(feature = "use-mock-crust")]
pub fn clear_state(&mut self) {
match *self {
State::JoiningNode(ref mut state) => state.clear_state(),
State::Node(ref mut state) => state.clear_state(),
State::Bootstrapping(_) |
State::Client(_) |
State::Terminated => (),
}
}
}
pub enum Transition {
Stay,
IntoBootstrapped {
proxy_peer_id: PeerId,
proxy_public_id: PublicId,
quorum_size: usize,
},
IntoNode {
peer_id: PeerId,
public_id: PublicId,
},
Terminate,
}
impl StateMachine {
pub fn new<F>(init_state: F) -> (RoutingActionSender, Self)
where F: FnOnce(Service, Timer) -> State
{
let (category_tx, category_rx) = mpsc::channel();
let (crust_tx, crust_rx) = mpsc::channel();
let (action_tx, action_rx) = mpsc::channel();
let action_sender = RoutingActionSender::new(action_tx,
MaidSafeEventCategory::Routing,
category_tx.clone());
let crust_sender =
CrustEventSender::new(crust_tx, MaidSafeEventCategory::Crust, category_tx);
let mut crust_service = match Service::new(crust_sender) {
Ok(service) => service,
Err(error) => panic!("Unable to start crust::Service {:?}", error),
};
crust_service.start_service_discovery();
let timer = Timer::new(action_sender.clone());
let state = init_state(crust_service, timer);
let is_running = match state {
State::Terminated => false,
_ => true,
};
let machine = StateMachine {
category_rx: category_rx,
crust_rx: crust_rx,
action_rx: action_rx,
state: state,
is_running: is_running,
};
(action_sender, machine)
}
#[cfg(feature = "use-mock-crust")]
pub fn poll(&mut self) -> bool {
if !self.is_running {
return false;
}
match self.category_rx.try_recv() {
Ok(category) => {
self.handle_event(category);
true
}
_ => false,
}
}
#[cfg(not(feature = "use-mock-crust"))]
pub fn run(&mut self) {
while self.is_running {
if let Ok(category) = self.category_rx.recv() {
self.handle_event(category);
} else {
break;
}
}
}
#[cfg(feature = "use-mock-crust")]
pub fn current(&self) -> &State {
&self.state
}
#[cfg(feature = "use-mock-crust")]
pub fn current_mut(&mut self) -> &mut State {
&mut self.state
}
fn handle_event(&mut self, category: MaidSafeEventCategory) {
let transition = match category {
MaidSafeEventCategory::Routing => {
if let Ok(action) = self.action_rx.try_recv() {
self.state.handle_action(action)
} else {
Transition::Terminate
}
}
MaidSafeEventCategory::Crust => {
if let Ok(crust_event) = self.crust_rx.try_recv() {
self.state.handle_crust_event(crust_event)
} else {
Transition::Terminate
}
}
};
match transition {
Transition::Stay => (),
Transition::IntoBootstrapped { proxy_peer_id, proxy_public_id, quorum_size } => {
self.transition_to_bootstrapped(proxy_peer_id, proxy_public_id, quorum_size)
}
Transition::IntoNode { peer_id, public_id } => self.transition_to_node(peer_id, public_id),
Transition::Terminate => self.terminate(),
}
}
fn transition_to_bootstrapped(&mut self,
proxy_peer_id: PeerId,
proxy_public_id: PublicId,
quorum_size: usize) {
self.transition(|state| {
state.into_bootstrapped(proxy_peer_id, proxy_public_id, quorum_size)
})
}
fn transition_to_node(&mut self, peer_id: PeerId, public_id: PublicId) {
self.transition(|state| state.into_node(peer_id, public_id))
}
fn terminate(&mut self) {
self.is_running = false;
}
fn transition<F>(&mut self, f: F)
where F: FnOnce(State) -> State
{
let prev_state = mem::replace(&mut self.state, State::Terminated);
self.state = f(prev_state);
}
}