dstar_gateway_core/
header.rs1use crate::types::{Callsign, Suffix};
32
33pub const ENCODED_LEN: usize = 41;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct DStarHeader {
39 pub flag1: u8,
41 pub flag2: u8,
43 pub flag3: u8,
45 pub rpt2: Callsign,
47 pub rpt1: Callsign,
49 pub ur_call: Callsign,
51 pub my_call: Callsign,
53 pub my_suffix: Suffix,
55}
56
57impl DStarHeader {
58 #[must_use]
60 pub fn encode(&self) -> [u8; ENCODED_LEN] {
61 let mut buf = [0u8; ENCODED_LEN];
62 if let Some(b) = buf.get_mut(0) {
63 *b = self.flag1;
64 }
65 if let Some(b) = buf.get_mut(1) {
66 *b = self.flag2;
67 }
68 if let Some(b) = buf.get_mut(2) {
69 *b = self.flag3;
70 }
71 if let Some(s) = buf.get_mut(3..11) {
72 s.copy_from_slice(self.rpt2.as_bytes());
73 }
74 if let Some(s) = buf.get_mut(11..19) {
75 s.copy_from_slice(self.rpt1.as_bytes());
76 }
77 if let Some(s) = buf.get_mut(19..27) {
78 s.copy_from_slice(self.ur_call.as_bytes());
79 }
80 if let Some(s) = buf.get_mut(27..35) {
81 s.copy_from_slice(self.my_call.as_bytes());
82 }
83 if let Some(s) = buf.get_mut(35..39) {
84 s.copy_from_slice(self.my_suffix.as_bytes());
85 }
86
87 let crc = crc_ccitt(buf.get(..39).unwrap_or(&[]));
88 if let Some(b) = buf.get_mut(39) {
89 *b = (crc & 0xFF) as u8;
90 }
91 if let Some(b) = buf.get_mut(40) {
92 *b = (crc >> 8) as u8;
93 }
94 buf
95 }
96
97 #[must_use]
106 pub fn encode_for_dsvt(&self) -> [u8; ENCODED_LEN] {
107 let mut h = *self;
108 h.flag1 = 0;
109 h.flag2 = 0;
110 h.flag3 = 0;
111 h.encode()
112 }
113
114 #[must_use]
123 pub fn decode(data: &[u8; ENCODED_LEN]) -> Self {
124 let mut rpt2_bytes = [0u8; 8];
125 if let Some(s) = data.get(3..11) {
126 rpt2_bytes.copy_from_slice(s);
127 }
128 let mut rpt1_bytes = [0u8; 8];
129 if let Some(s) = data.get(11..19) {
130 rpt1_bytes.copy_from_slice(s);
131 }
132 let mut ur_bytes = [0u8; 8];
133 if let Some(s) = data.get(19..27) {
134 ur_bytes.copy_from_slice(s);
135 }
136 let mut my_bytes = [0u8; 8];
137 if let Some(s) = data.get(27..35) {
138 my_bytes.copy_from_slice(s);
139 }
140 let mut suffix_bytes = [0u8; 4];
141 if let Some(s) = data.get(35..39) {
142 suffix_bytes.copy_from_slice(s);
143 }
144
145 Self {
146 flag1: *data.first().unwrap_or(&0),
147 flag2: *data.get(1).unwrap_or(&0),
148 flag3: *data.get(2).unwrap_or(&0),
149 rpt2: Callsign::from_wire_bytes(rpt2_bytes),
150 rpt1: Callsign::from_wire_bytes(rpt1_bytes),
151 ur_call: Callsign::from_wire_bytes(ur_bytes),
152 my_call: Callsign::from_wire_bytes(my_bytes),
153 my_suffix: Suffix::from_wire_bytes(suffix_bytes),
154 }
155 }
156}
157
158#[must_use]
162pub fn crc_ccitt(data: &[u8]) -> u16 {
163 let mut crc: u16 = 0xFFFF;
164 for &byte in data {
165 crc ^= u16::from(byte);
166 for _ in 0..8 {
167 if crc & 1 != 0 {
168 crc = (crc >> 1) ^ 0x8408;
169 } else {
170 crc >>= 1;
171 }
172 }
173 }
174 crc ^ 0xFFFF
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 const fn cs(bytes: [u8; 8]) -> Callsign {
182 Callsign::from_wire_bytes(bytes)
183 }
184
185 fn test_header() -> DStarHeader {
186 DStarHeader {
187 flag1: 0x00,
188 flag2: 0x00,
189 flag3: 0x00,
190 rpt2: cs(*b"REF030 G"),
191 rpt1: cs(*b"REF030 C"),
192 ur_call: cs(*b"CQCQCQ "),
193 my_call: cs(*b"W1AW "),
194 my_suffix: Suffix::EMPTY,
195 }
196 }
197
198 #[test]
199 fn encode_decode_roundtrip() {
200 let header = test_header();
201 let encoded = header.encode();
202 assert_eq!(encoded.len(), ENCODED_LEN);
203 let decoded = DStarHeader::decode(&encoded);
204 assert_eq!(decoded, header);
205 }
206
207 #[test]
208 fn decode_accepts_bad_crc() {
209 let header = test_header();
213 let mut encoded = header.encode();
214 if let Some(byte) = encoded.get_mut(40) {
215 *byte ^= 0xFF;
216 }
217 let decoded = DStarHeader::decode(&encoded);
218 assert_eq!(decoded.my_call, header.my_call);
219 }
220
221 #[test]
222 fn decode_accepts_non_ascii_callsign_verbatim() {
223 let header = test_header();
226 let mut encoded = header.encode();
227 if let Some(byte) = encoded.get_mut(27) {
228 *byte = 0xC3;
229 }
230 let decoded = DStarHeader::decode(&encoded);
231 assert_eq!(decoded.my_call.as_bytes()[0], 0xC3);
232 }
233
234 #[test]
235 fn encode_for_dsvt_zeros_flag_bytes_before_crc() {
236 let hdr = DStarHeader {
237 flag1: 0xAA,
238 flag2: 0xBB,
239 flag3: 0xCC,
240 ..test_header()
241 };
242 let dsvt = hdr.encode_for_dsvt();
243 assert_eq!(dsvt[0], 0, "flag1 zeroed in DSVT encoding");
244 assert_eq!(dsvt[1], 0, "flag2 zeroed in DSVT encoding");
245 assert_eq!(dsvt[2], 0, "flag3 zeroed in DSVT encoding");
246 }
247
248 #[test]
249 fn crc_ccitt_known_vector_w1aw_header() {
250 let mut body = [0u8; 39];
254 if let Some(s) = body.get_mut(3..11) {
255 s.copy_from_slice(b"REF030 G");
256 }
257 if let Some(s) = body.get_mut(11..19) {
258 s.copy_from_slice(b"REF030 C");
259 }
260 if let Some(s) = body.get_mut(19..27) {
261 s.copy_from_slice(b"CQCQCQ ");
262 }
263 if let Some(s) = body.get_mut(27..35) {
264 s.copy_from_slice(b"W1AW ");
265 }
266 if let Some(s) = body.get_mut(35..39) {
267 s.copy_from_slice(b" ");
268 }
269 let crc = crc_ccitt(&body);
270 assert_eq!(crc, 0x1073);
271 }
272
273 #[test]
274 fn suffix_roundtrip_nonempty() {
275 let hdr = DStarHeader {
276 my_suffix: Suffix::from_wire_bytes(*b"ECHO"),
277 ..test_header()
278 };
279 let encoded = hdr.encode();
280 let decoded = DStarHeader::decode(&encoded);
281 assert_eq!(decoded.my_suffix.as_bytes(), b"ECHO");
282 }
283}