1use crate::header::{DStarHeader, ENCODED_LEN};
12use crate::types::{Callsign, ProtocolKind, StreamId};
13use crate::validator::{Diagnostic, DiagnosticSink};
14use crate::voice::VoiceFrame;
15
16use super::consts::{DSVT_MAGIC, LINK2_ACCEPT_TAG, LINK2_BUSY_TAG};
17use super::error::DPlusError;
18use super::packet::{ClientPacket, Link2Result, ServerPacket};
19
20pub fn decode_server_to_client(
35 bytes: &[u8],
36 sink: &mut dyn DiagnosticSink,
37) -> Result<ServerPacket, DPlusError> {
38 let len = bytes.len();
39 let is_dsvt = len >= 6 && bytes.get(2..6) == Some(DSVT_MAGIC.as_slice());
40
41 if !is_dsvt {
42 return match len {
43 3 => Ok(ServerPacket::PollEcho),
44 5 => {
45 let ctrl = bytes.get(4).copied().unwrap_or(0);
46 match ctrl {
47 0x01 => Ok(ServerPacket::Link1Ack),
48 0x00 => Ok(ServerPacket::UnlinkAck),
49 other => Err(DPlusError::InvalidShortControlByte { byte: other }),
50 }
51 }
52 8 => {
53 let tag_slice = bytes.get(4..8).unwrap_or(&[]);
54 let mut tag = [0u8; 4];
55 tag.copy_from_slice(tag_slice);
56 let result = if tag == LINK2_ACCEPT_TAG {
57 Link2Result::Accept
58 } else if tag == LINK2_BUSY_TAG {
59 Link2Result::Busy
60 } else {
61 sink.record(Diagnostic::UnknownLink2Reply { reply: tag });
62 Link2Result::Unknown { reply: tag }
63 };
64 Ok(ServerPacket::Link2Reply { result })
65 }
66 28 => {
67 let cs_bytes = bytes.get(4..12).unwrap_or(&[]);
68 let mut cs = [b' '; 8];
69 let copy_len = cs_bytes.len().min(8);
70 if let Some(dst) = cs.get_mut(..copy_len)
71 && let Some(src) = cs_bytes.get(..copy_len)
72 {
73 dst.copy_from_slice(src);
74 }
75 for b in &mut cs {
78 if *b == 0 {
79 *b = b' ';
80 }
81 }
82 Ok(ServerPacket::Link2Echo {
83 callsign: Callsign::from_wire_bytes(cs),
84 })
85 }
86 _ => Err(DPlusError::UnknownPacketLength { got: len }),
87 };
88 }
89
90 decode_dsvt_server(bytes, sink)
92}
93
94pub fn decode_client_to_server(
107 bytes: &[u8],
108 sink: &mut dyn DiagnosticSink,
109) -> Result<ClientPacket, DPlusError> {
110 let len = bytes.len();
111 let is_dsvt = len >= 6 && bytes.get(2..6) == Some(DSVT_MAGIC.as_slice());
112
113 if !is_dsvt {
114 return match len {
115 3 => Ok(ClientPacket::Poll),
116 5 => {
117 let ctrl = bytes.get(4).copied().unwrap_or(0);
118 match ctrl {
119 0x01 => Ok(ClientPacket::Link1),
120 0x00 => Ok(ClientPacket::Unlink),
121 other => Err(DPlusError::InvalidShortControlByte { byte: other }),
122 }
123 }
124 28 => {
125 let cs_bytes = bytes.get(4..12).unwrap_or(&[]);
126 let mut cs = [b' '; 8];
127 let copy_len = cs_bytes.len().min(8);
128 if let Some(dst) = cs.get_mut(..copy_len)
129 && let Some(src) = cs_bytes.get(..copy_len)
130 {
131 dst.copy_from_slice(src);
132 }
133 for b in &mut cs {
134 if *b == 0 {
135 *b = b' ';
136 }
137 }
138 Ok(ClientPacket::Link2 {
139 callsign: Callsign::from_wire_bytes(cs),
140 })
141 }
142 _ => Err(DPlusError::UnknownPacketLength { got: len }),
143 };
144 }
145
146 decode_dsvt_client(bytes, sink)
148}
149
150fn decode_dsvt_server(
151 bytes: &[u8],
152 sink: &mut dyn DiagnosticSink,
153) -> Result<ServerPacket, DPlusError> {
154 let (stream_id, header, frame_bytes, is_header, is_eot, len) = parse_dsvt_common(bytes, sink)?;
155 if is_header {
156 let Some(hdr) = header else {
157 return Err(DPlusError::UnknownPacketLength { got: len });
158 };
159 Ok(ServerPacket::VoiceHeader {
160 stream_id,
161 header: hdr,
162 })
163 } else if is_eot {
164 let seq = bytes.get(16).copied().unwrap_or(0);
165 Ok(ServerPacket::VoiceEot { stream_id, seq })
166 } else {
167 let Some(frame) = frame_bytes else {
168 return Err(DPlusError::UnknownPacketLength { got: len });
169 };
170 let seq = bytes.get(16).copied().unwrap_or(0);
171 Ok(ServerPacket::VoiceData {
172 stream_id,
173 seq,
174 frame,
175 })
176 }
177}
178
179fn decode_dsvt_client(
180 bytes: &[u8],
181 sink: &mut dyn DiagnosticSink,
182) -> Result<ClientPacket, DPlusError> {
183 let (stream_id, header, frame_bytes, is_header, is_eot, len) = parse_dsvt_common(bytes, sink)?;
184 if is_header {
185 let Some(hdr) = header else {
186 return Err(DPlusError::UnknownPacketLength { got: len });
187 };
188 Ok(ClientPacket::VoiceHeader {
189 stream_id,
190 header: hdr,
191 })
192 } else if is_eot {
193 let seq = bytes.get(16).copied().unwrap_or(0);
194 Ok(ClientPacket::VoiceEot { stream_id, seq })
195 } else {
196 let Some(frame) = frame_bytes else {
197 return Err(DPlusError::UnknownPacketLength { got: len });
198 };
199 let seq = bytes.get(16).copied().unwrap_or(0);
200 Ok(ClientPacket::VoiceData {
201 stream_id,
202 seq,
203 frame,
204 })
205 }
206}
207
208type DsvtParse = (
209 StreamId,
210 Option<DStarHeader>,
211 Option<VoiceFrame>,
212 bool,
213 bool,
214 usize,
215);
216
217fn parse_dsvt_common(bytes: &[u8], sink: &mut dyn DiagnosticSink) -> Result<DsvtParse, DPlusError> {
218 let len = bytes.len();
219 match len {
220 58 | 29 | 32 => {}
221 _ => return Err(DPlusError::UnknownPacketLength { got: len }),
222 }
223
224 let lo = bytes.get(14).copied().unwrap_or(0);
226 let hi = bytes.get(15).copied().unwrap_or(0);
227 let raw_sid = u16::from_le_bytes([lo, hi]);
228 let stream_id = StreamId::new(raw_sid).ok_or(DPlusError::StreamIdZero)?;
229
230 let is_header = len == 58 && bytes.get(6).copied() == Some(0x10);
231 let is_eot = len == 32 && bytes.get(6).copied() == Some(0x20);
232 let is_voice = len == 29 && bytes.get(6).copied() == Some(0x20);
233
234 if !is_header && !is_eot && !is_voice {
235 return Err(DPlusError::UnknownPacketLength { got: len });
236 }
237
238 let header = if is_header {
239 let hdr_slice = bytes
240 .get(17..58)
241 .ok_or(DPlusError::UnknownPacketLength { got: len })?;
242 let mut arr = [0u8; ENCODED_LEN];
243 arr.copy_from_slice(hdr_slice);
244 let decoded = DStarHeader::decode(&arr);
245 if decoded.flag1 != 0 || decoded.flag2 != 0 || decoded.flag3 != 0 {
247 sink.record(Diagnostic::HeaderFlagsNonZero {
248 protocol: ProtocolKind::DPlus,
249 flag1: decoded.flag1,
250 flag2: decoded.flag2,
251 flag3: decoded.flag3,
252 });
253 }
254 Some(decoded)
255 } else {
256 None
257 };
258
259 let frame = if is_voice {
260 let mut ambe = [0u8; 9];
261 let mut slow = [0u8; 3];
262 if let Some(src) = bytes.get(17..26) {
263 ambe.copy_from_slice(src);
264 }
265 if let Some(src) = bytes.get(26..29) {
266 slow.copy_from_slice(src);
267 }
268 Some(VoiceFrame {
269 ambe,
270 slow_data: slow,
271 })
272 } else {
273 None
274 };
275
276 Ok((stream_id, header, frame, is_header, is_eot, len))
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::codec::dplus::encode::{
283 encode_link1, encode_link2, encode_link2_reply, encode_poll, encode_unlink,
284 encode_voice_data, encode_voice_eot, encode_voice_header,
285 };
286 use crate::types::Suffix;
287 use crate::validator::NullSink;
288
289 type TestResult = Result<(), Box<dyn std::error::Error>>;
290
291 const fn cs(bytes: [u8; 8]) -> Callsign {
292 Callsign::from_wire_bytes(bytes)
293 }
294
295 #[expect(clippy::unwrap_used, reason = "compile-time validated: n != 0")]
296 const fn sid(n: u16) -> StreamId {
297 StreamId::new(n).unwrap()
298 }
299
300 fn test_header() -> DStarHeader {
301 DStarHeader {
302 flag1: 0,
303 flag2: 0,
304 flag3: 0,
305 rpt2: cs(*b"REF030 G"),
306 rpt1: cs(*b"REF030 C"),
307 ur_call: cs(*b"CQCQCQ "),
308 my_call: cs(*b"W1AW "),
309 my_suffix: Suffix::EMPTY,
310 }
311 }
312
313 #[test]
315 fn link1_client_roundtrip() -> TestResult {
316 let mut buf = [0u8; 16];
317 let n = encode_link1(&mut buf)?;
318 let mut sink = NullSink;
319 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
320 assert!(matches!(pkt, ClientPacket::Link1));
321 Ok(())
322 }
323
324 #[test]
325 fn unlink_client_roundtrip() -> TestResult {
326 let mut buf = [0u8; 16];
327 let n = encode_unlink(&mut buf)?;
328 let mut sink = NullSink;
329 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
330 assert!(matches!(pkt, ClientPacket::Unlink));
331 Ok(())
332 }
333
334 #[test]
335 fn poll_client_roundtrip() -> TestResult {
336 let mut buf = [0u8; 16];
337 let n = encode_poll(&mut buf)?;
338 let mut sink = NullSink;
339 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
340 assert!(matches!(pkt, ClientPacket::Poll));
341 Ok(())
342 }
343
344 #[test]
345 fn link2_client_roundtrip() -> TestResult {
346 let cs_in = cs(*b"W1AW ");
347 let mut buf = [0u8; 32];
348 let n = encode_link2(&mut buf, &cs_in)?;
349 let mut sink = NullSink;
350 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
351 match pkt {
352 ClientPacket::Link2 { callsign } => assert_eq!(callsign, cs_in),
353 other => return Err(format!("expected Link2, got {other:?}").into()),
354 }
355 Ok(())
356 }
357
358 #[test]
360 fn link1_ack_server_roundtrip() -> TestResult {
361 let mut buf = [0u8; 16];
363 let n = encode_link1(&mut buf)?;
364 let mut sink = NullSink;
365 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
366 assert!(matches!(pkt, ServerPacket::Link1Ack));
367 Ok(())
368 }
369
370 #[test]
371 fn unlink_ack_server_roundtrip() -> TestResult {
372 let mut buf = [0u8; 16];
373 let n = encode_unlink(&mut buf)?;
374 let mut sink = NullSink;
375 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
376 assert!(matches!(pkt, ServerPacket::UnlinkAck));
377 Ok(())
378 }
379
380 #[test]
381 fn poll_echo_server_roundtrip() -> TestResult {
382 let mut buf = [0u8; 16];
383 let n = encode_poll(&mut buf)?;
384 let mut sink = NullSink;
385 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
386 assert!(matches!(pkt, ServerPacket::PollEcho));
387 Ok(())
388 }
389
390 #[test]
391 fn link2_reply_accept_roundtrip() -> TestResult {
392 let mut buf = [0u8; 16];
393 let n = encode_link2_reply(&mut buf, Link2Result::Accept)?;
394 let mut sink = NullSink;
395 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
396 assert!(matches!(
397 pkt,
398 ServerPacket::Link2Reply {
399 result: Link2Result::Accept
400 }
401 ));
402 Ok(())
403 }
404
405 #[test]
406 fn link2_reply_busy_roundtrip() -> TestResult {
407 let mut buf = [0u8; 16];
408 let n = encode_link2_reply(&mut buf, Link2Result::Busy)?;
409 let mut sink = NullSink;
410 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
411 assert!(matches!(
412 pkt,
413 ServerPacket::Link2Reply {
414 result: Link2Result::Busy
415 }
416 ));
417 Ok(())
418 }
419
420 #[test]
421 fn link2_reply_unknown_records_diagnostic() -> TestResult {
422 let bytes = [0x08, 0xC0, 0x04, 0x00, b'F', b'A', b'I', b'L'];
425 let mut sink = crate::validator::VecSink::default();
426 let pkt = decode_server_to_client(&bytes, &mut sink)?;
427 assert!(matches!(
428 pkt,
429 ServerPacket::Link2Reply {
430 result: Link2Result::Unknown { .. }
431 }
432 ));
433 assert_eq!(sink.len(), 1);
434 Ok(())
435 }
436
437 #[test]
439 fn voice_header_server_roundtrip() -> TestResult {
440 let mut buf = [0u8; 64];
441 let n = encode_voice_header(&mut buf, sid(0xCAFE), &test_header())?;
442 let mut sink = NullSink;
443 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
444 match pkt {
445 ServerPacket::VoiceHeader { stream_id, header } => {
446 assert_eq!(stream_id, sid(0xCAFE));
447 assert_eq!(header.my_call, test_header().my_call);
448 }
449 other => return Err(format!("expected VoiceHeader, got {other:?}").into()),
450 }
451 Ok(())
452 }
453
454 #[test]
455 fn voice_data_server_roundtrip() -> TestResult {
456 let frame = VoiceFrame {
457 ambe: [0x11; 9],
458 slow_data: [0x22; 3],
459 };
460 let mut buf = [0u8; 64];
461 let n = encode_voice_data(&mut buf, sid(0x1234), 5, &frame)?;
462 let mut sink = NullSink;
463 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
464 match pkt {
465 ServerPacket::VoiceData {
466 stream_id,
467 seq,
468 frame: f,
469 } => {
470 assert_eq!(stream_id, sid(0x1234));
471 assert_eq!(seq, 5);
472 assert_eq!(f.ambe, [0x11; 9]);
473 assert_eq!(f.slow_data, [0x22; 3]);
474 }
475 other => return Err(format!("expected VoiceData, got {other:?}").into()),
476 }
477 Ok(())
478 }
479
480 #[test]
481 fn voice_eot_server_roundtrip() -> TestResult {
482 let mut buf = [0u8; 64];
483 let n = encode_voice_eot(&mut buf, sid(0x1234), 7)?;
484 let mut sink = NullSink;
485 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut sink)?;
486 match pkt {
487 ServerPacket::VoiceEot { stream_id, seq } => {
488 assert_eq!(stream_id, sid(0x1234));
489 assert_eq!(seq & 0x40, 0x40, "EOT bit set");
490 assert_eq!(seq & 0x3F, 7, "low bits preserve seq");
491 }
492 other => return Err(format!("expected VoiceEot, got {other:?}").into()),
493 }
494 Ok(())
495 }
496
497 #[test]
499 fn unknown_length_returns_error() -> TestResult {
500 let mut sink = NullSink;
501 let Err(err) = decode_client_to_server(&[0u8; 11], &mut sink) else {
502 return Err("expected error for bad length".into());
503 };
504 assert!(matches!(err, DPlusError::UnknownPacketLength { got: 11 }));
505 Ok(())
506 }
507
508 #[test]
509 fn short_5_byte_with_bad_control_byte() -> TestResult {
510 let mut sink = NullSink;
511 let Err(err) = decode_client_to_server(&[0x05, 0x00, 0x18, 0x00, 0x77], &mut sink) else {
512 return Err("expected error for bad control byte".into());
513 };
514 assert!(matches!(
515 err,
516 DPlusError::InvalidShortControlByte { byte: 0x77 }
517 ));
518 Ok(())
519 }
520
521 #[test]
522 fn client_rejects_8_byte_server_reply() -> TestResult {
523 let bytes = [0x08, 0xC0, 0x04, 0x00, b'O', b'K', b'R', b'W'];
525 let mut sink = NullSink;
526 let Err(err) = decode_client_to_server(&bytes, &mut sink) else {
527 return Err("expected error for client rejecting 8-byte server reply".into());
528 };
529 assert!(matches!(err, DPlusError::UnknownPacketLength { got: 8 }));
530 Ok(())
531 }
532}