use crate::event::{FlowSide, FlowState};
use crate::extractor::TcpFlags;
pub struct Transition {
pub state: FlowState,
pub became_established: bool,
}
pub fn transition(state: FlowState, flags: TcpFlags, side: FlowSide) -> Transition {
if flags.contains(TcpFlags::RST) {
return Transition {
state: FlowState::Reset,
became_established: false,
};
}
let new_state = match (state, side, flags) {
(FlowState::Active, FlowSide::Initiator, f) if f.contains(TcpFlags::SYN) => {
FlowState::SynSent
}
(FlowState::SynSent, FlowSide::Responder, f)
if f.contains(TcpFlags::SYN | TcpFlags::ACK) =>
{
FlowState::SynReceived
}
(FlowState::SynReceived, FlowSide::Initiator, f) if f.contains(TcpFlags::ACK) => {
FlowState::Established
}
(FlowState::Established, _, f) if f.contains(TcpFlags::FIN) => FlowState::FinWait,
(FlowState::FinWait, _, f) if f.contains(TcpFlags::FIN) => FlowState::ClosingTcp,
(FlowState::ClosingTcp, _, f) if f.contains(TcpFlags::ACK) => FlowState::Closed,
_ => state,
};
let became_established = state != FlowState::Established && new_state == FlowState::Established;
Transition {
state: new_state,
became_established,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn syn() -> TcpFlags {
TcpFlags::SYN
}
fn syn_ack() -> TcpFlags {
TcpFlags::SYN | TcpFlags::ACK
}
fn ack() -> TcpFlags {
TcpFlags::ACK
}
fn fin_ack() -> TcpFlags {
TcpFlags::FIN | TcpFlags::ACK
}
#[test]
fn three_way_handshake() {
let t1 = transition(FlowState::Active, syn(), FlowSide::Initiator);
assert_eq!(t1.state, FlowState::SynSent);
assert!(!t1.became_established);
let t2 = transition(t1.state, syn_ack(), FlowSide::Responder);
assert_eq!(t2.state, FlowState::SynReceived);
assert!(!t2.became_established);
let t3 = transition(t2.state, ack(), FlowSide::Initiator);
assert_eq!(t3.state, FlowState::Established);
assert!(t3.became_established);
}
#[test]
fn graceful_close() {
let t1 = transition(FlowState::Established, fin_ack(), FlowSide::Initiator);
assert_eq!(t1.state, FlowState::FinWait);
let t2 = transition(t1.state, fin_ack(), FlowSide::Responder);
assert_eq!(t2.state, FlowState::ClosingTcp);
let t3 = transition(t2.state, ack(), FlowSide::Initiator);
assert_eq!(t3.state, FlowState::Closed);
}
#[test]
fn rst_in_any_state() {
let rst = TcpFlags::RST;
let t1 = transition(FlowState::Established, rst, FlowSide::Initiator);
assert_eq!(t1.state, FlowState::Reset);
let t2 = transition(FlowState::SynSent, rst, FlowSide::Responder);
assert_eq!(t2.state, FlowState::Reset);
}
#[test]
fn unknown_packet_in_active_no_transition() {
let t = transition(FlowState::Active, ack(), FlowSide::Responder);
assert_eq!(t.state, FlowState::Active);
}
#[test]
fn syn_retransmit_doesnt_advance() {
let t = transition(FlowState::SynSent, syn(), FlowSide::Initiator);
assert_eq!(t.state, FlowState::SynSent);
}
#[test]
fn established_data_stays_established() {
let psh_ack = TcpFlags::PSH | TcpFlags::ACK;
let t = transition(FlowState::Established, psh_ack, FlowSide::Initiator);
assert_eq!(t.state, FlowState::Established);
assert!(!t.became_established);
}
}