dstar_gateway_core/codec/dcs/consts.rs
1//! `DCS` 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 `DCS` reflectors.
9///
10/// Reference: `ircDDBGateway/Common/DStarDefines.h:117` (`DCS_PORT = 30051U`).
11pub const DEFAULT_PORT: u16 = 30051;
12
13/// `DCS` keepalive interval.
14///
15/// Reference: `ircDDBGateway/Common/DCSHandler.cpp:54`
16/// (`m_pollTimer(1000U, 5U)` = 5 seconds).
17pub const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5);
18
19/// `DCS` keepalive inactivity timeout.
20///
21/// Reference: `ircDDBGateway/Common/DCSHandler.cpp:55`
22/// (`m_pollInactivityTimer(1000U, 60U)` = 60 seconds).
23pub const KEEPALIVE_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(60);
24
25/// `DCS` voice inactivity timeout — synthesize `VoiceEnd` after this.
26pub const VOICE_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(2);
27
28/// Disconnect ACK timeout — give up waiting for unlink reply.
29pub const DISCONNECT_TIMEOUT: Duration = Duration::from_secs(2);
30
31/// LINK packet length (519 bytes — includes 500-byte HTML template).
32///
33/// Reference: `ircDDBGateway/Common/ConnectData.cpp:364` (return 519U).
34pub const LINK_LEN: usize = 519;
35
36/// UNLINK packet length (19 bytes).
37///
38/// Reference: `ircDDBGateway/Common/ConnectData.cpp:372` (return 19U).
39pub const UNLINK_LEN: usize = 19;
40
41/// Connect reply (ACK/NAK) length (14 bytes).
42///
43/// Reference: `ircDDBGateway/Common/ConnectData.cpp:380,388` (return 14U).
44pub const CONNECT_REPLY_LEN: usize = 14;
45
46/// Poll packet length (17 bytes, both directions in this codec).
47///
48/// Reference: `ircDDBGateway/Common/PollData.cpp:186` (return 17U).
49/// The 22-byte `DIR_INCOMING` variant from the reference is not used
50/// here — both directions are symmetric 17-byte packets, which matches
51/// xlxd's `IsValidKeepAlivePacket` accept list
52/// (`xlxd/src/cdcsprotocol.cpp:411`).
53pub const POLL_LEN: usize = 17;
54
55/// Voice frame length (100 bytes).
56///
57/// Reference: `ircDDBGateway/Common/AMBEData.cpp:430` (return 100U).
58pub const VOICE_LEN: usize = 100;
59
60/// `"0001"` magic at offsets `[0..4]` of every `DCS` voice frame.
61///
62/// Reference: `ircDDBGateway/Common/AMBEData.cpp:398-401`.
63pub const VOICE_MAGIC: [u8; 4] = *b"0001";
64
65/// ACK tag at `[10..13]` of a 14-byte connect reply, followed by
66/// `0x00` at byte `[13]`.
67///
68/// Reference: `ircDDBGateway/Common/ConnectData.cpp:374-380`
69/// (`data[LONG_CALLSIGN_LENGTH + 2U] = 'A'`, i.e. `data[10]`).
70pub const CONNECT_ACK_TAG: [u8; 3] = *b"ACK";
71
72/// NAK tag at `[10..13]` of a 14-byte connect reply, followed by
73/// `0x00` at byte `[13]`.
74///
75/// Reference: `ircDDBGateway/Common/ConnectData.cpp:382-388`.
76pub const CONNECT_NAK_TAG: [u8; 3] = *b"NAK";
77
78/// End-of-stream sentinel bytes at `[55..58]` of an EOT voice frame.
79///
80/// Reference: `ircDDBGateway/Common/AMBEData.cpp:410-414`.
81pub const VOICE_EOT_MARKER: [u8; 3] = [0x55, 0x55, 0x55];
82
83/// DCS HTML template for REPEATER gateway type (fills bytes `[19..519]`
84/// of the LINK packet).
85///
86/// Reference: `ircDDBGateway/Common/ConnectData.cpp:344-358` shows the
87/// reference populating a template via `wxString::Printf(HTML, ...)`.
88/// We emit a static short banner instead of the full template — the
89/// receiving reflector logs the HTML but does not parse it, so any
90/// short identification string that fits in 500 bytes satisfies the
91/// protocol. This mirrors `xlxd`, which accepts LINK packets regardless
92/// of the HTML payload content.
93pub const LINK_HTML_REPEATER: &[u8] =
94 b"<table border=\"0\" width=\"95%\"><tr><td>REPEATER</td></tr></table>";
95
96/// DCS HTML template banner for HOTSPOT gateway type.
97pub const LINK_HTML_HOTSPOT: &[u8] =
98 b"<table border=\"0\" width=\"95%\"><tr><td>HOTSPOT</td></tr></table>";
99
100/// DCS HTML template banner for DONGLE gateway type.
101pub const LINK_HTML_DONGLE: &[u8] =
102 b"<table border=\"0\" width=\"95%\"><tr><td>DONGLE</td></tr></table>";
103
104/// DCS HTML template banner for STARNET gateway type.
105pub const LINK_HTML_STARNET: &[u8] =
106 b"<table border=\"0\" width=\"95%\"><tr><td>STARNET</td></tr></table>";
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn default_port_is_30051() {
114 assert_eq!(DEFAULT_PORT, 30051);
115 }
116
117 #[test]
118 fn voice_len_is_100_bytes() {
119 assert_eq!(VOICE_LEN, 100);
120 }
121
122 #[test]
123 fn link_len_is_519_bytes() {
124 assert_eq!(LINK_LEN, 519);
125 }
126
127 #[test]
128 fn unlink_len_is_19_bytes() {
129 assert_eq!(UNLINK_LEN, 19);
130 }
131
132 #[test]
133 fn connect_reply_len_is_14_bytes() {
134 assert_eq!(CONNECT_REPLY_LEN, 14);
135 }
136
137 #[test]
138 fn poll_len_is_17_bytes() {
139 assert_eq!(POLL_LEN, 17);
140 }
141
142 #[test]
143 fn keepalive_interval_five_seconds() {
144 assert_eq!(KEEPALIVE_INTERVAL, Duration::from_secs(5));
145 }
146
147 #[test]
148 fn keepalive_inactivity_sixty_seconds() {
149 assert_eq!(KEEPALIVE_INACTIVITY_TIMEOUT, Duration::from_secs(60));
150 }
151
152 #[test]
153 fn voice_magic_is_0001() {
154 assert_eq!(&VOICE_MAGIC, b"0001");
155 }
156
157 #[test]
158 fn voice_eot_marker_is_triple_0x55() {
159 assert_eq!(VOICE_EOT_MARKER, [0x55, 0x55, 0x55]);
160 }
161
162 #[test]
163 fn ack_tag_is_ack() {
164 assert_eq!(&CONNECT_ACK_TAG, b"ACK");
165 }
166
167 #[test]
168 fn nak_tag_is_nak() {
169 assert_eq!(&CONNECT_NAK_TAG, b"NAK");
170 }
171
172 #[test]
173 fn html_templates_fit_in_500_bytes() {
174 assert!(LINK_HTML_REPEATER.len() <= 500);
175 assert!(LINK_HTML_HOTSPOT.len() <= 500);
176 assert!(LINK_HTML_DONGLE.len() <= 500);
177 assert!(LINK_HTML_STARNET.len() <= 500);
178 }
179}