dstar_gateway_core/codec/dplus/consts.rs
1//! `DPlus` wire-format constants.
2//!
3//! Every magic byte and every duration here is annotated with the
4//! exact line in the GPL reference implementation it was derived from.
5
6use std::time::Duration;
7
8/// Default UDP port for `DPlus` (REF) reflectors.
9///
10/// Reference: `ircDDBGateway/Common/DStarDefines.h:115` (`DPLUS_PORT = 20001U`).
11/// Reference: `xlxd/src/main.h:93` (`#define DPLUS_PORT 20001`).
12pub const DEFAULT_PORT: u16 = 20001;
13
14/// `DPlus` keepalive interval.
15///
16/// Reference: `ircDDBGateway/Common/DPlusHandler.cpp:57`
17/// (`m_pollTimer(1000U, 1U)` = 1 second).
18/// Reference: `xlxd/src/main.h:94` (`DPLUS_KEEPALIVE_PERIOD = 1`).
19///
20/// **The legacy `dstar-gateway` crate ships 5s — that is the bug
21/// the audit found.** This rewrite uses 1s to match both references.
22pub const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
23
24/// `DPlus` outgoing keepalive inactivity timeout.
25///
26/// Reference: `ircDDBGateway/Common/DPlusHandler.cpp:58`
27/// (`m_pollInactivityTimer(1000U, 30U)` = 30 seconds for outgoing
28/// reflector links).
29pub const KEEPALIVE_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(30);
30
31/// `DPlus` voice inactivity timeout — synthesize `VoiceEnd` after this.
32///
33/// Reference: `ircDDBGateway/Common/DStarDefines.h:122`
34/// (`NETWORK_TIMEOUT = 2U`).
35pub const VOICE_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(2);
36
37/// `DPlus` disconnect ACK timeout — give up waiting for unlink reply.
38pub const DISCONNECT_TIMEOUT: Duration = Duration::from_secs(2);
39
40/// Number of times the initial connect packet is retransmitted.
41pub const CONNECT_RETX: u8 = 2;
42
43/// Number of times the voice header is retransmitted.
44///
45/// Reference: `ircDDBGateway/Common/DPlusProtocolHandler.cpp:64-68`
46/// (the `for (i = 0; i < 5; i++)` loop in `writeHeader`).
47pub const HEADER_RETX: u8 = 5;
48
49/// Number of times the unlink packet is retransmitted.
50///
51/// Reference: `ircDDBGateway/Common/DPlusHandler.cpp:481-482` sends
52/// unlink twice; we send three times for an extra margin on lossy links.
53pub const DISCONNECT_RETX: u8 = 3;
54
55/// Inter-copy delay for retransmission bursts.
56pub const RETX_DELAY: Duration = Duration::from_millis(50);
57
58/// LINK1 connect packet (5 bytes).
59///
60/// Reference: `ircDDBGateway/Common/ConnectData.cpp:441-447`
61/// (`getDPlusData CT_LINK1`).
62/// Reference: `xlxd/src/cdplusprotocol.cpp:430`
63/// (`IsValidConnectPacket`).
64pub const LINK1_BYTES: [u8; 5] = [0x05, 0x00, 0x18, 0x00, 0x01];
65
66/// UNLINK packet (5 bytes).
67///
68/// Reference: `ircDDBGateway/Common/ConnectData.cpp:475-481`
69/// (`getDPlusData CT_UNLINK`).
70/// Reference: `xlxd/src/cdplusprotocol.cpp:447-451`
71/// (`IsValidDisconnectPacket`).
72pub const UNLINK_BYTES: [u8; 5] = [0x05, 0x00, 0x18, 0x00, 0x00];
73
74/// Keepalive poll packet (3 bytes).
75///
76/// Reference: `xlxd/src/cdplusprotocol.cpp:529-533`
77/// (`EncodeKeepAlivePacket`).
78pub const POLL_BYTES: [u8; 3] = [0x03, 0x60, 0x00];
79
80/// LINK1 ACK echo — server replies with the same bytes as LINK1.
81pub const LINK1_ACK_BYTES: [u8; 5] = LINK1_BYTES;
82
83/// UNLINK ACK echo — server replies with the same bytes as UNLINK.
84pub const UNLINK_ACK_BYTES: [u8; 5] = UNLINK_BYTES;
85
86/// Poll echo — server replies with the same bytes as POLL.
87pub const POLL_ECHO_BYTES: [u8; 3] = POLL_BYTES;
88
89/// LINK2 reply tag for accept (4 bytes at offsets `[4..8]` of an
90/// 8-byte reply).
91///
92/// Reference: `ircDDBGateway/Common/ConnectData.cpp:251-259`
93/// (`memcmp(data + 4, "OKRW", 4)`).
94/// Reference: `xlxd/src/cdplusprotocol.cpp:535-539`
95/// (`EncodeLoginAckPacket`).
96pub const LINK2_ACCEPT_TAG: [u8; 4] = *b"OKRW";
97
98/// LINK2 reply tag for busy (4 bytes at offsets `[4..8]` of an
99/// 8-byte reply).
100///
101/// Reference: `xlxd/src/cdplusprotocol.cpp:541-545`
102/// (`EncodeLoginNackPacket`).
103pub const LINK2_BUSY_TAG: [u8; 4] = *b"BUSY";
104
105/// LINK2 reply prefix (first 4 bytes of an 8-byte reply, before the tag).
106pub const LINK2_REPLY_PREFIX: [u8; 4] = [0x08, 0xC0, 0x04, 0x00];
107
108/// LINK2 packet header (4 bytes; full packet is 28 bytes with
109/// callsign + `DV019999`).
110///
111/// Reference: `ircDDBGateway/Common/ConnectData.cpp:449-473`
112/// (`getDPlusData CT_LINK2`).
113pub const LINK2_HEADER: [u8; 4] = [0x1C, 0xC0, 0x04, 0x00];
114
115/// `DV019999` 8-byte client identifier embedded in LINK2 at offsets
116/// `[20..28]`.
117pub const DV_CLIENT_ID: [u8; 8] = *b"DV019999";
118
119/// DSVT magic at offsets `[2..6]` of every voice header / voice
120/// data / EOT packet.
121pub const DSVT_MAGIC: [u8; 4] = *b"DSVT";
122
123/// Voice header DSVT length byte (offset 0 of a voice header packet).
124pub const VOICE_HEADER_PREFIX: u8 = 0x3A;
125
126/// Voice data DSVT length byte (offset 0 of a voice data packet).
127pub const VOICE_DATA_PREFIX: u8 = 0x1D;
128
129/// Voice EOT DSVT length byte (offset 0 of an EOT packet).
130pub const VOICE_EOT_PREFIX: u8 = 0x20;
131
132/// DSVT type byte (offset 1 of every DSVT-framed packet).
133pub const DSVT_TYPE: u8 = 0x80;
134
135/// EOT trailing pattern (last 6 bytes of an EOT packet).
136///
137/// Reference: `ircDDBGateway/Common/DStarDefines.h:34`
138/// (`END_PATTERN_BYTES`).
139pub const VOICE_EOT_TRAILER: [u8; 6] = [0x55, 0x55, 0x55, 0x55, 0xC8, 0x7A];
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn keepalive_interval_is_one_second() {
147 assert_eq!(KEEPALIVE_INTERVAL, Duration::from_secs(1));
148 }
149
150 #[test]
151 fn link1_bytes_are_known() {
152 assert_eq!(LINK1_BYTES, [0x05, 0x00, 0x18, 0x00, 0x01]);
153 }
154
155 #[test]
156 fn unlink_bytes_are_known() {
157 assert_eq!(UNLINK_BYTES, [0x05, 0x00, 0x18, 0x00, 0x00]);
158 }
159
160 #[test]
161 fn poll_bytes_are_known() {
162 assert_eq!(POLL_BYTES, [0x03, 0x60, 0x00]);
163 }
164
165 #[test]
166 fn link2_accept_is_okrw() {
167 assert_eq!(&LINK2_ACCEPT_TAG, b"OKRW");
168 }
169
170 #[test]
171 fn link2_busy_is_busy() {
172 assert_eq!(&LINK2_BUSY_TAG, b"BUSY");
173 }
174
175 #[test]
176 fn dsvt_magic_is_dsvt() {
177 assert_eq!(&DSVT_MAGIC, b"DSVT");
178 }
179
180 #[test]
181 fn voice_eot_trailer_is_known() {
182 assert_eq!(VOICE_EOT_TRAILER, [0x55, 0x55, 0x55, 0x55, 0xC8, 0x7A]);
183 }
184}