dstar_gateway_core/session/client/
any_session.rs

1//! `AnySession<P>` — storage-friendly enum wrapping a session in any state.
2
3use super::protocol::{DPlus, Protocol};
4use super::session::Session;
5use super::state::{
6    Authenticated, ClientStateKind, Closed, Configured, Connected, Connecting, Disconnecting,
7};
8
9/// Storage-friendly enum wrapping a [`Session<P, S>`] in any state.
10///
11/// Use this when you need to keep a session in a struct field that
12/// might be in any state (e.g., a long-lived REPL state). For
13/// individual transitions, use the typed [`Session<P, S>`] directly.
14///
15/// Note: [`AnySession<P>`] is generic over the protocol. The
16/// [`Self::Authenticated`] variant is hard-coded to [`DPlus`] because
17/// the typestate guarantees only `DPlus` reaches that state. This is
18/// a known wart of full typestate that we accept.
19#[non_exhaustive]
20#[derive(Debug)]
21pub enum AnySession<P: Protocol> {
22    /// [`Configured`] state — session built but no I/O has happened.
23    Configured(Session<P, Configured>),
24    /// [`Authenticated`] state — `DPlus` only, TCP auth completed.
25    Authenticated(Session<DPlus, Authenticated>),
26    /// [`Connecting`] state — LINK packet sent, waiting for ACK.
27    Connecting(Session<P, Connecting>),
28    /// [`Connected`] state — operational.
29    Connected(Session<P, Connected>),
30    /// [`Disconnecting`] state — UNLINK sent, waiting for ACK.
31    Disconnecting(Session<P, Disconnecting>),
32    /// [`Closed`] state — terminal.
33    Closed(Session<P, Closed>),
34}
35
36impl<P: Protocol> AnySession<P> {
37    /// Runtime state discriminator for whichever state the session is in.
38    #[must_use]
39    pub const fn state_kind(&self) -> ClientStateKind {
40        match self {
41            Self::Configured(s) => s.state_kind(),
42            Self::Authenticated(s) => s.state_kind(),
43            Self::Connecting(s) => s.state_kind(),
44            Self::Connected(s) => s.state_kind(),
45            Self::Disconnecting(s) => s.state_kind(),
46            Self::Closed(s) => s.state_kind(),
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::session::client::core::SessionCore;
55    use crate::session::client::protocol::DExtra;
56    use crate::types::{Callsign, Module, ProtocolKind};
57    use std::marker::PhantomData;
58    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
59
60    const PEER: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 30001);
61
62    fn dextra_configured_any() -> AnySession<DExtra> {
63        let core = SessionCore::new(
64            ProtocolKind::DExtra,
65            Callsign::from_wire_bytes(*b"W1AW    "),
66            Module::B,
67            Module::C,
68            PEER,
69        );
70        AnySession::Configured(Session {
71            inner: core,
72            _protocol: PhantomData,
73            _state: PhantomData,
74        })
75    }
76
77    #[test]
78    fn any_session_configured_state_kind() {
79        let s = dextra_configured_any();
80        assert_eq!(s.state_kind(), ClientStateKind::Configured);
81    }
82}