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}