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}