dstar_gateway_core/codec/dextra/
packet.rs

1//! `DExtra` packet enums.
2//!
3//! `ClientPacket` represents every packet a `DExtra` client sends to a
4//! reflector. `ServerPacket` represents every packet a reflector sends
5//! to a client. The codec is symmetric — both directions are
6//! first-class.
7
8use crate::header::DStarHeader;
9use crate::types::{Callsign, Module, StreamId};
10use crate::voice::VoiceFrame;
11
12/// Packets a `DExtra` client sends to a reflector.
13#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum ClientPacket {
16    /// 11-byte LINK request.
17    Link {
18        /// Logging-in client callsign.
19        callsign: Callsign,
20        /// Module on the reflector to link to.
21        reflector_module: Module,
22        /// Client's local module letter.
23        client_module: Module,
24    },
25
26    /// 11-byte UNLINK request: same shape but reflector module is space.
27    Unlink {
28        /// Logging-out client callsign.
29        callsign: Callsign,
30        /// Client's local module letter.
31        client_module: Module,
32    },
33
34    /// 9-byte keepalive poll.
35    Poll {
36        /// Polling client callsign.
37        callsign: Callsign,
38    },
39
40    /// 56-byte voice header (DSVT framed, no `DPlus` prefix).
41    VoiceHeader {
42        /// D-STAR stream id.
43        stream_id: StreamId,
44        /// Decoded D-STAR header.
45        header: DStarHeader,
46    },
47
48    /// 27-byte voice data (DSVT framed).
49    VoiceData {
50        /// D-STAR stream id.
51        stream_id: StreamId,
52        /// Frame sequence number.
53        seq: u8,
54        /// 9 AMBE bytes + 3 slow data bytes.
55        frame: VoiceFrame,
56    },
57
58    /// 27-byte voice EOT (DSVT framed, seq has 0x40 bit).
59    VoiceEot {
60        /// D-STAR stream id.
61        stream_id: StreamId,
62        /// Final seq value (encoder OR's in 0x40).
63        seq: u8,
64    },
65}
66
67/// Packets a `DExtra` reflector sends to a client.
68#[non_exhaustive]
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum ServerPacket {
71    /// 14-byte connect ACK: echoed callsign + module at `[0..10]`,
72    /// `b"ACK"` at `[10..13]`, NUL at `[13]`.
73    ConnectAck {
74        /// Echoed callsign.
75        callsign: Callsign,
76        /// Echoed reflector module.
77        reflector_module: Module,
78    },
79
80    /// 14-byte connect NAK: echoed callsign + module at `[0..10]`,
81    /// `b"NAK"` at `[10..13]`, NUL at `[13]`.
82    ConnectNak {
83        /// Echoed callsign.
84        callsign: Callsign,
85        /// Echoed reflector module.
86        reflector_module: Module,
87    },
88
89    /// 9-byte poll echo.
90    PollEcho {
91        /// Echoed callsign.
92        callsign: Callsign,
93    },
94
95    /// 56-byte voice header.
96    VoiceHeader {
97        /// D-STAR stream id.
98        stream_id: StreamId,
99        /// Decoded header.
100        header: DStarHeader,
101    },
102
103    /// 27-byte voice data.
104    VoiceData {
105        /// D-STAR stream id.
106        stream_id: StreamId,
107        /// Frame sequence number.
108        seq: u8,
109        /// Voice frame.
110        frame: VoiceFrame,
111    },
112
113    /// 27-byte voice EOT.
114    VoiceEot {
115        /// D-STAR stream id.
116        stream_id: StreamId,
117        /// Final seq.
118        seq: u8,
119    },
120}
121
122/// Result of a `DExtra` connect attempt.
123#[non_exhaustive]
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum ConnectResult {
126    /// `b"ACK"` reply.
127    Accept,
128    /// `b"NAK"` reply.
129    Reject,
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn client_packet_link_constructible() {
138        let p = ClientPacket::Link {
139            callsign: Callsign::from_wire_bytes(*b"W1AW    "),
140            reflector_module: Module::C,
141            client_module: Module::B,
142        };
143        assert!(matches!(p, ClientPacket::Link { .. }));
144    }
145
146    #[test]
147    fn server_packet_connect_ack_carries_module() {
148        let p = ServerPacket::ConnectAck {
149            callsign: Callsign::from_wire_bytes(*b"XRF030  "),
150            reflector_module: Module::C,
151        };
152        assert!(
153            matches!(p, ServerPacket::ConnectAck { reflector_module, .. } if reflector_module == Module::C),
154            "expected ConnectAck with Module::C, got {p:?}"
155        );
156    }
157
158    #[test]
159    fn connect_result_variants_distinct() {
160        assert_ne!(ConnectResult::Accept, ConnectResult::Reject);
161    }
162}