use alloc::string::String;
use crate::limits::ResourceLimits;
use crate::sasl::SaslState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConnectionState {
#[default]
Start,
HdrRcvd,
HdrExch,
OpenRcvd,
Opened,
CloseRcvd,
CloseSent,
End,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SessionState {
#[default]
Unmapped,
BeginRcvd,
Mapped,
EndRcvd,
EndSent,
Discarded,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndpointConfig {
pub container_id: String,
pub limits: ResourceLimits,
pub sasl: Option<SaslState>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EndpointError {
IllegalStateTransition {
from: ConnectionState,
attempted: &'static str,
},
ResourceLimitExceeded(&'static str),
IdleTimeout,
ConnectionAborted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InboundFrameKind {
Header,
Open,
Begin,
Attach,
Flow,
Transfer,
Disposition,
Detach,
End,
Close,
}
pub fn advance_connection(
state: ConnectionState,
frame: InboundFrameKind,
) -> Result<ConnectionState, EndpointError> {
use ConnectionState as C;
use InboundFrameKind as F;
let next = match (state, frame) {
(C::Start, F::Header) => C::HdrRcvd,
(C::HdrRcvd, F::Header) => C::HdrExch,
(C::HdrExch, F::Open) => C::OpenRcvd,
(C::OpenRcvd, F::Open) => C::Opened,
(
C::Opened,
F::Begin | F::Attach | F::Flow | F::Transfer | F::Disposition | F::Detach | F::End,
) => C::Opened,
(C::Opened, F::Close) => C::CloseRcvd,
(C::CloseRcvd, _) => C::End,
(
from,
F::Header
| F::Open
| F::Begin
| F::Attach
| F::Flow
| F::Transfer
| F::Disposition
| F::Detach
| F::End
| F::Close,
) => {
return Err(EndpointError::IllegalStateTransition {
from,
attempted: frame_label(frame),
});
}
};
Ok(next)
}
const fn frame_label(f: InboundFrameKind) -> &'static str {
match f {
InboundFrameKind::Header => "header",
InboundFrameKind::Open => "open",
InboundFrameKind::Begin => "begin",
InboundFrameKind::Attach => "attach",
InboundFrameKind::Flow => "flow",
InboundFrameKind::Transfer => "transfer",
InboundFrameKind::Disposition => "disposition",
InboundFrameKind::Detach => "detach",
InboundFrameKind::End => "end",
InboundFrameKind::Close => "close",
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn header_exchange_advances_to_hdr_exch() {
let s = advance_connection(ConnectionState::Start, InboundFrameKind::Header).expect("ok");
assert_eq!(s, ConnectionState::HdrRcvd);
let s = advance_connection(s, InboundFrameKind::Header).expect("ok");
assert_eq!(s, ConnectionState::HdrExch);
}
#[test]
fn open_advances_to_opened_state() {
let s = advance_connection(ConnectionState::HdrExch, InboundFrameKind::Open).expect("ok");
assert_eq!(s, ConnectionState::OpenRcvd);
let s = advance_connection(s, InboundFrameKind::Open).expect("ok");
assert_eq!(s, ConnectionState::Opened);
}
#[test]
fn close_in_opened_advances_to_closercvd_then_end() {
let s = advance_connection(ConnectionState::Opened, InboundFrameKind::Close).expect("ok");
assert_eq!(s, ConnectionState::CloseRcvd);
let s = advance_connection(s, InboundFrameKind::End).expect("ok");
assert_eq!(s, ConnectionState::End);
}
#[test]
fn open_in_start_state_yields_illegal_transition() {
let r = advance_connection(ConnectionState::Start, InboundFrameKind::Open);
assert!(matches!(
r,
Err(EndpointError::IllegalStateTransition { .. })
));
}
#[test]
fn begin_attach_flow_transfer_keep_opened_state() {
let s = ConnectionState::Opened;
for f in [
InboundFrameKind::Begin,
InboundFrameKind::Attach,
InboundFrameKind::Flow,
InboundFrameKind::Transfer,
InboundFrameKind::Disposition,
InboundFrameKind::Detach,
] {
let next = advance_connection(s, f).expect("ok");
assert_eq!(next, ConnectionState::Opened);
}
}
#[test]
fn session_state_default_is_unmapped() {
assert_eq!(SessionState::default(), SessionState::Unmapped);
}
}