dstar_gateway_core/session/client/
protocol.rs

1//! Sealed `Protocol` marker trait + the three reflector protocol markers.
2
3use crate::types::ProtocolKind;
4
5/// Sealed marker trait for D-STAR reflector protocols.
6///
7/// Only the three protocols defined in this module ([`DPlus`],
8/// [`DExtra`], [`Dcs`]) may implement this trait. The seal prevents
9/// downstream crates from adding fake protocols, which would
10/// invalidate every typestate proof.
11pub trait Protocol: sealed::Sealed + Copy + Send + Sync + 'static {
12    /// Runtime discriminator for this protocol.
13    const KIND: ProtocolKind;
14    /// Default UDP port.
15    const DEFAULT_PORT: u16;
16    /// Whether this protocol requires TCP authentication before UDP linking.
17    const NEEDS_AUTH: bool;
18}
19
20mod sealed {
21    pub trait Sealed {}
22}
23
24/// `DPlus` (REF reflectors). Requires TCP authentication.
25#[derive(Debug, Clone, Copy)]
26pub struct DPlus;
27
28/// `DExtra` (XRF/XLX reflectors).
29#[derive(Debug, Clone, Copy)]
30pub struct DExtra;
31
32/// `DCS` reflectors.
33#[derive(Debug, Clone, Copy)]
34pub struct Dcs;
35
36impl sealed::Sealed for DPlus {}
37impl Protocol for DPlus {
38    const KIND: ProtocolKind = ProtocolKind::DPlus;
39    const DEFAULT_PORT: u16 = 20001;
40    const NEEDS_AUTH: bool = true;
41}
42
43impl sealed::Sealed for DExtra {}
44impl Protocol for DExtra {
45    const KIND: ProtocolKind = ProtocolKind::DExtra;
46    const DEFAULT_PORT: u16 = 30001;
47    const NEEDS_AUTH: bool = false;
48}
49
50impl sealed::Sealed for Dcs {}
51impl Protocol for Dcs {
52    const KIND: ProtocolKind = ProtocolKind::Dcs;
53    const DEFAULT_PORT: u16 = 30051;
54    const NEEDS_AUTH: bool = false;
55}
56
57/// Protocols that do NOT require authentication (`DExtra`, `Dcs`).
58///
59/// Used as a trait bound on `Session<P, Configured>::connect` so
60/// the no-auth path only exists for protocols that don't need it.
61/// `DPlus` does NOT impl this — it requires `authenticate` first.
62pub trait NoAuthRequired: Protocol {}
63impl NoAuthRequired for DExtra {}
64impl NoAuthRequired for Dcs {}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    // `Protocol::KIND` / `DEFAULT_PORT` / `NEEDS_AUTH` are associated
71    // consts, so `assert_eq!` against another literal is a
72    // compile-time check clippy flags as "will be optimised out".
73    // Pull the values into local bindings first — this is a runtime
74    // read from the function's perspective, even though the optimiser
75    // folds it.
76    fn needs_auth_of<P: Protocol>() -> bool {
77        <P as Protocol>::NEEDS_AUTH
78    }
79    fn kind_of<P: Protocol>() -> ProtocolKind {
80        <P as Protocol>::KIND
81    }
82    fn default_port_of<P: Protocol>() -> u16 {
83        <P as Protocol>::DEFAULT_PORT
84    }
85
86    #[test]
87    fn dplus_kind_is_dplus() {
88        assert_eq!(kind_of::<DPlus>(), ProtocolKind::DPlus);
89        assert_eq!(default_port_of::<DPlus>(), 20001);
90        assert!(needs_auth_of::<DPlus>());
91    }
92
93    #[test]
94    fn dextra_kind_is_dextra() {
95        assert_eq!(kind_of::<DExtra>(), ProtocolKind::DExtra);
96        assert_eq!(default_port_of::<DExtra>(), 30001);
97        assert!(!needs_auth_of::<DExtra>());
98    }
99
100    #[test]
101    fn dcs_kind_is_dcs() {
102        assert_eq!(kind_of::<Dcs>(), ProtocolKind::Dcs);
103        assert_eq!(default_port_of::<Dcs>(), 30051);
104        assert!(!needs_auth_of::<Dcs>());
105    }
106}