use std::time::Instant;
pub trait SessionState: private::Sealed {}
mod private {
pub trait Sealed {}
}
#[derive(Debug, Clone, Copy)]
pub struct Disconnected;
impl private::Sealed for Disconnected {}
impl SessionState for Disconnected {}
#[derive(Debug, Clone, Copy)]
pub struct Connecting;
impl private::Sealed for Connecting {}
impl SessionState for Connecting {}
#[derive(Debug, Clone, Copy)]
pub struct LogonSent {
pub sent_at: Instant,
}
impl private::Sealed for LogonSent {}
impl SessionState for LogonSent {}
#[derive(Debug, Clone, Copy)]
pub struct LogonReceived {
pub received_at: Instant,
}
impl private::Sealed for LogonReceived {}
impl SessionState for LogonReceived {}
#[derive(Debug, Clone, Copy)]
pub struct Active;
impl private::Sealed for Active {}
impl SessionState for Active {}
#[derive(Debug, Clone, Copy)]
pub struct Resending {
pub begin_seq: u64,
pub end_seq: u64,
}
impl private::Sealed for Resending {}
impl SessionState for Resending {}
#[derive(Debug, Clone, Copy)]
pub struct LogoutPending {
pub sent_at: Instant,
}
impl private::Sealed for LogoutPending {}
impl SessionState for LogoutPending {}
#[derive(Debug)]
pub struct Session<S: SessionState> {
pub session_id: String,
state: S,
}
impl<S: SessionState> Session<S> {
#[must_use]
pub fn session_id(&self) -> &str {
&self.session_id
}
#[must_use]
pub const fn state(&self) -> &S {
&self.state
}
fn transition<N: SessionState>(self, next: N) -> Session<N> {
Session {
session_id: self.session_id,
state: next,
}
}
}
impl Session<Disconnected> {
#[must_use]
pub fn new(session_id: impl Into<String>) -> Self {
Self {
session_id: session_id.into(),
state: Disconnected,
}
}
#[must_use]
pub fn connect(self) -> Session<Connecting> {
self.transition(Connecting)
}
#[must_use]
pub fn accept(self) -> Session<Connecting> {
self.transition(Connecting)
}
}
impl Session<Connecting> {
#[must_use]
pub fn send_logon(self) -> Session<LogonSent> {
self.transition(LogonSent {
sent_at: Instant::now(),
})
}
#[must_use]
pub fn on_logon_received(self) -> Session<LogonReceived> {
self.transition(LogonReceived {
received_at: Instant::now(),
})
}
#[must_use]
pub fn disconnect(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
impl Session<LogonSent> {
#[must_use]
pub const fn sent_at(&self) -> Instant {
self.state.sent_at
}
#[must_use]
pub fn on_logon_ack(self) -> Session<Active> {
self.transition(Active)
}
#[must_use]
pub fn on_logon_reject(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
impl Session<LogonReceived> {
#[must_use]
pub const fn received_at(&self) -> Instant {
self.state.received_at
}
#[must_use]
pub fn accept_logon(self) -> Session<Active> {
self.transition(Active)
}
#[must_use]
pub fn reject_logon(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
#[must_use]
pub fn on_timeout(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
impl Session<Active> {
#[must_use]
pub fn start_resend(self, begin_seq: u64, end_seq: u64) -> Session<Resending> {
self.transition(Resending { begin_seq, end_seq })
}
#[must_use]
pub fn initiate_logout(self) -> Session<LogoutPending> {
self.transition(LogoutPending {
sent_at: Instant::now(),
})
}
#[must_use]
pub fn disconnect(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
impl Session<Resending> {
#[must_use]
pub const fn begin_seq(&self) -> u64 {
self.state.begin_seq
}
#[must_use]
pub const fn end_seq(&self) -> u64 {
self.state.end_seq
}
#[must_use]
pub fn resend_complete(self) -> Session<Active> {
self.transition(Active)
}
#[must_use]
pub fn disconnect(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
impl Session<LogoutPending> {
#[must_use]
pub const fn sent_at(&self) -> Instant {
self.state.sent_at
}
#[must_use]
pub fn on_logout_ack(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
#[must_use]
pub fn on_timeout(self) -> Session<Disconnected> {
self.transition(Disconnected)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_state_transitions() {
let session = Session::<Disconnected>::new("TEST");
assert_eq!(session.session_id(), "TEST");
let session = session.connect();
let session = session.send_logon();
let session = session.on_logon_ack();
let session = session.initiate_logout();
let _session = session.on_logout_ack();
}
#[test]
fn test_acceptor_flow() {
let session = Session::<Disconnected>::new("ACCEPTOR");
let session = session.accept();
let session = session.on_logon_received();
let session = session.accept_logon();
let session = session.initiate_logout();
let _session = session.on_logout_ack();
}
#[test]
fn test_acceptor_reject_flow() {
let session = Session::<Disconnected>::new("ACCEPTOR");
let session = session.accept();
let session = session.on_logon_received();
let _session = session.reject_logon();
}
#[test]
fn test_acceptor_timeout_flow() {
let session = Session::<Disconnected>::new("ACCEPTOR");
let session = session.accept();
let session = session.on_logon_received();
let _session = session.on_timeout();
}
#[test]
fn test_resend_flow() {
let session = Session::<Disconnected>::new("TEST");
let session = session.connect();
let session = session.send_logon();
let session = session.on_logon_ack();
let session = session.start_resend(1, 5);
let _session = session.resend_complete();
}
#[test]
fn test_send_logon_records_the_send_instant() {
let before = Instant::now();
let session = Session::<Disconnected>::new("TEST").connect().send_logon();
let after = Instant::now();
assert!(session.sent_at() >= before);
assert!(session.sent_at() <= after);
assert_eq!(session.state().sent_at, session.sent_at());
}
#[test]
fn test_on_logon_received_records_the_arrival_instant() {
let before = Instant::now();
let session = Session::<Disconnected>::new("ACCEPTOR")
.accept()
.on_logon_received();
let after = Instant::now();
assert!(session.received_at() >= before);
assert!(session.received_at() <= after);
}
#[test]
fn test_initiate_logout_records_the_send_instant() {
let before = Instant::now();
let session = Session::<Disconnected>::new("TEST")
.connect()
.send_logon()
.on_logon_ack()
.initiate_logout();
let after = Instant::now();
assert!(session.sent_at() >= before);
assert!(session.sent_at() <= after);
}
#[test]
fn test_start_resend_keeps_the_requested_range() {
let session = Session::<Disconnected>::new("TEST")
.connect()
.send_logon()
.on_logon_ack()
.start_resend(7, 16);
assert_eq!(session.begin_seq(), 7);
assert_eq!(session.end_seq(), 16);
assert_eq!(session.state().begin_seq, 7);
}
#[test]
fn test_start_resend_keeps_the_open_ended_range() {
let session = Session::<Disconnected>::new("TEST")
.connect()
.send_logon()
.on_logon_ack()
.start_resend(42, 0);
assert_eq!(session.begin_seq(), 42);
assert_eq!(session.end_seq(), 0);
}
#[test]
fn test_session_id_survives_every_transition() {
let session = Session::<Disconnected>::new("PERSISTENT")
.connect()
.send_logon()
.on_logon_ack();
assert_eq!(session.session_id(), "PERSISTENT");
let session = session.start_resend(1, 2).resend_complete();
assert_eq!(session.session_id(), "PERSISTENT");
let session = session.initiate_logout();
assert_eq!(session.session_id(), "PERSISTENT");
let session = session.on_logout_ack();
assert_eq!(session.session_id(), "PERSISTENT");
}
#[test]
fn test_stateless_states_stay_zero_sized() {
use std::mem::size_of;
assert_eq!(size_of::<Disconnected>(), 0);
assert_eq!(size_of::<Connecting>(), 0);
assert_eq!(size_of::<Active>(), 0);
assert_eq!(size_of::<Session<Active>>(), size_of::<String>());
}
}