1use crate::header::{DStarHeader, ENCODED_LEN};
13use crate::types::{Callsign, Module, ProtocolKind, StreamId};
14use crate::validator::{Diagnostic, DiagnosticSink};
15use crate::voice::VoiceFrame;
16
17use super::consts::{
18 CONNECT_ACK_TAG, CONNECT_LEN, CONNECT_NAK_TAG, CONNECT_REPLY_LEN, DSVT_MAGIC, POLL_LEN,
19 VOICE_DATA_LEN, VOICE_HEADER_LEN,
20};
21use super::error::DExtraError;
22use super::packet::{ClientPacket, ServerPacket};
23
24pub fn decode_client_to_server(
41 bytes: &[u8],
42 sink: &mut dyn DiagnosticSink,
43) -> Result<ClientPacket, DExtraError> {
44 let len = bytes.len();
45 match len {
46 POLL_LEN => {
47 let cs = extract_callsign(bytes);
48 Ok(ClientPacket::Poll { callsign: cs })
49 }
50 CONNECT_LEN => decode_client_connect(bytes),
51 VOICE_DATA_LEN => decode_dsvt_client_voice(bytes, sink),
52 VOICE_HEADER_LEN => decode_dsvt_client_header(bytes, sink),
53 _ => Err(DExtraError::UnknownPacketLength { got: len }),
54 }
55}
56
57pub fn decode_server_to_client(
69 bytes: &[u8],
70 sink: &mut dyn DiagnosticSink,
71) -> Result<ServerPacket, DExtraError> {
72 let len = bytes.len();
73 match len {
74 POLL_LEN => {
75 let cs = extract_callsign(bytes);
76 Ok(ServerPacket::PollEcho { callsign: cs })
77 }
78 CONNECT_REPLY_LEN => decode_server_connect_reply(bytes),
79 VOICE_DATA_LEN => decode_dsvt_server_voice(bytes, sink),
80 VOICE_HEADER_LEN => decode_dsvt_server_header(bytes, sink),
81 _ => Err(DExtraError::UnknownPacketLength { got: len }),
82 }
83}
84
85fn extract_callsign(bytes: &[u8]) -> Callsign {
104 let mut cs = [b' '; 8];
105 if let Some(src) = bytes.get(..8) {
106 cs.copy_from_slice(src);
107 }
108 for b in &mut cs {
109 if *b == 0 {
110 *b = b' ';
111 }
112 }
113 Callsign::from_wire_bytes(cs)
114}
115
116fn decode_client_connect(bytes: &[u8]) -> Result<ClientPacket, DExtraError> {
118 let cs = extract_callsign(bytes);
119 let client_byte = bytes.get(8).copied().unwrap_or(0);
120 let client_module =
121 Module::try_from_byte(client_byte).map_err(|_| DExtraError::InvalidModuleByte {
122 offset: 8,
123 byte: client_byte,
124 })?;
125 let reflector_byte = bytes.get(9).copied().unwrap_or(0);
126 if reflector_byte == b' ' {
127 Ok(ClientPacket::Unlink {
128 callsign: cs,
129 client_module,
130 })
131 } else {
132 let reflector_module =
133 Module::try_from_byte(reflector_byte).map_err(|_| DExtraError::InvalidModuleByte {
134 offset: 9,
135 byte: reflector_byte,
136 })?;
137 Ok(ClientPacket::Link {
138 callsign: cs,
139 reflector_module,
140 client_module,
141 })
142 }
143}
144
145fn decode_server_connect_reply(bytes: &[u8]) -> Result<ServerPacket, DExtraError> {
147 let cs = extract_callsign(bytes);
148 let module_byte = bytes.get(9).copied().unwrap_or(0);
150 let reflector_module =
151 Module::try_from_byte(module_byte).map_err(|_| DExtraError::InvalidModuleByte {
152 offset: 9,
153 byte: module_byte,
154 })?;
155 let mut tag = [0u8; 3];
158 if let Some(src) = bytes.get(10..13) {
159 tag.copy_from_slice(src);
160 }
161 if tag == CONNECT_ACK_TAG {
162 Ok(ServerPacket::ConnectAck {
163 callsign: cs,
164 reflector_module,
165 })
166 } else if tag == CONNECT_NAK_TAG {
167 Ok(ServerPacket::ConnectNak {
168 callsign: cs,
169 reflector_module,
170 })
171 } else {
172 Err(DExtraError::UnknownConnectTag { tag })
173 }
174}
175
176fn decode_dsvt_client_voice(
178 bytes: &[u8],
179 _sink: &mut dyn DiagnosticSink,
180) -> Result<ClientPacket, DExtraError> {
181 let (stream_id, seq, frame) = parse_dsvt_voice(bytes)?;
182 if seq & 0x40 != 0 {
183 Ok(ClientPacket::VoiceEot { stream_id, seq })
184 } else {
185 Ok(ClientPacket::VoiceData {
186 stream_id,
187 seq,
188 frame,
189 })
190 }
191}
192
193fn decode_dsvt_server_voice(
195 bytes: &[u8],
196 _sink: &mut dyn DiagnosticSink,
197) -> Result<ServerPacket, DExtraError> {
198 let (stream_id, seq, frame) = parse_dsvt_voice(bytes)?;
199 if seq & 0x40 != 0 {
200 Ok(ServerPacket::VoiceEot { stream_id, seq })
201 } else {
202 Ok(ServerPacket::VoiceData {
203 stream_id,
204 seq,
205 frame,
206 })
207 }
208}
209
210fn decode_dsvt_client_header(
212 bytes: &[u8],
213 sink: &mut dyn DiagnosticSink,
214) -> Result<ClientPacket, DExtraError> {
215 let (stream_id, header) = parse_dsvt_header(bytes, sink)?;
216 Ok(ClientPacket::VoiceHeader { stream_id, header })
217}
218
219fn decode_dsvt_server_header(
221 bytes: &[u8],
222 sink: &mut dyn DiagnosticSink,
223) -> Result<ServerPacket, DExtraError> {
224 let (stream_id, header) = parse_dsvt_header(bytes, sink)?;
225 Ok(ServerPacket::VoiceHeader { stream_id, header })
226}
227
228fn parse_dsvt_voice(bytes: &[u8]) -> Result<(StreamId, u8, VoiceFrame), DExtraError> {
230 check_dsvt_magic(bytes)?;
231 if bytes.get(4).copied() != Some(0x20) {
233 return Err(DExtraError::UnknownPacketLength { got: bytes.len() });
234 }
235 let stream_id = extract_stream_id(bytes)?;
236 let seq = bytes.get(14).copied().unwrap_or(0);
237 let mut ambe = [0u8; 9];
238 let mut slow = [0u8; 3];
239 if let Some(src) = bytes.get(15..24) {
240 ambe.copy_from_slice(src);
241 }
242 if let Some(src) = bytes.get(24..27) {
243 slow.copy_from_slice(src);
244 }
245 Ok((
246 stream_id,
247 seq,
248 VoiceFrame {
249 ambe,
250 slow_data: slow,
251 },
252 ))
253}
254
255fn parse_dsvt_header(
257 bytes: &[u8],
258 sink: &mut dyn DiagnosticSink,
259) -> Result<(StreamId, DStarHeader), DExtraError> {
260 check_dsvt_magic(bytes)?;
261 if bytes.get(4).copied() != Some(0x10) {
263 return Err(DExtraError::UnknownPacketLength { got: bytes.len() });
264 }
265 let stream_id = extract_stream_id(bytes)?;
266 let hdr_slice = bytes
267 .get(15..56)
268 .ok_or(DExtraError::UnknownPacketLength { got: bytes.len() })?;
269 let mut arr = [0u8; ENCODED_LEN];
270 arr.copy_from_slice(hdr_slice);
271 let decoded = DStarHeader::decode(&arr);
272 if decoded.flag1 != 0 || decoded.flag2 != 0 || decoded.flag3 != 0 {
273 sink.record(Diagnostic::HeaderFlagsNonZero {
274 protocol: ProtocolKind::DExtra,
275 flag1: decoded.flag1,
276 flag2: decoded.flag2,
277 flag3: decoded.flag3,
278 });
279 }
280 Ok((stream_id, decoded))
281}
282
283fn check_dsvt_magic(bytes: &[u8]) -> Result<(), DExtraError> {
285 let slice = bytes
286 .get(..4)
287 .ok_or(DExtraError::UnknownPacketLength { got: bytes.len() })?;
288 if slice == DSVT_MAGIC.as_slice() {
289 Ok(())
290 } else {
291 let mut got = [0u8; 4];
292 got.copy_from_slice(slice);
293 Err(DExtraError::DsvtMagicMissing { got })
294 }
295}
296
297fn extract_stream_id(bytes: &[u8]) -> Result<StreamId, DExtraError> {
299 let lo = bytes.get(12).copied().unwrap_or(0);
300 let hi = bytes.get(13).copied().unwrap_or(0);
301 let raw = u16::from_le_bytes([lo, hi]);
302 StreamId::new(raw).ok_or(DExtraError::StreamIdZero)
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::codec::dextra::encode::{
309 encode_connect_ack, encode_connect_link, encode_connect_nak, encode_poll, encode_unlink,
310 encode_voice_data, encode_voice_eot, encode_voice_header,
311 };
312 use crate::types::Suffix;
313 use crate::validator::NullSink;
314
315 type TestResult = Result<(), Box<dyn std::error::Error>>;
316
317 const fn cs(bytes: [u8; 8]) -> Callsign {
318 Callsign::from_wire_bytes(bytes)
319 }
320
321 #[expect(clippy::unwrap_used, reason = "compile-time validated: n != 0")]
322 const fn sid(n: u16) -> StreamId {
323 StreamId::new(n).unwrap()
324 }
325
326 fn test_header() -> DStarHeader {
327 DStarHeader {
328 flag1: 0,
329 flag2: 0,
330 flag3: 0,
331 rpt2: cs(*b"XRF030 G"),
332 rpt1: cs(*b"XRF030 C"),
333 ur_call: cs(*b"CQCQCQ "),
334 my_call: cs(*b"W1AW "),
335 my_suffix: Suffix::EMPTY,
336 }
337 }
338
339 #[test]
341 fn link_client_roundtrip() -> TestResult {
342 let mut buf = [0u8; 16];
348 let n = encode_connect_link(&mut buf, &cs(*b"W1AW "), Module::C, Module::B)?;
349 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
350 match pkt {
351 ClientPacket::Link {
352 callsign,
353 reflector_module,
354 client_module,
355 } => {
356 assert_eq!(callsign, cs(*b"W1AW "));
357 assert_eq!(reflector_module, Module::C);
358 assert_eq!(client_module, Module::B);
359 }
360 other => return Err(format!("expected Link, got {other:?}").into()),
361 }
362 Ok(())
363 }
364
365 #[test]
366 fn unlink_client_roundtrip() -> TestResult {
367 let mut buf = [0u8; 16];
368 let n = encode_unlink(&mut buf, &cs(*b"W1AW "), Module::B)?;
369 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
370 match pkt {
371 ClientPacket::Unlink {
372 callsign,
373 client_module,
374 } => {
375 assert_eq!(callsign, cs(*b"W1AW "));
376 assert_eq!(client_module, Module::B);
377 }
378 other => return Err(format!("expected Unlink, got {other:?}").into()),
379 }
380 Ok(())
381 }
382
383 #[test]
384 fn poll_client_roundtrip() -> TestResult {
385 let mut buf = [0u8; 16];
386 let n = encode_poll(&mut buf, &cs(*b"W1AW "))?;
387 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
388 match pkt {
389 ClientPacket::Poll { callsign } => {
390 assert_eq!(callsign, cs(*b"W1AW "));
391 }
392 other => return Err(format!("expected Poll, got {other:?}").into()),
393 }
394 Ok(())
395 }
396
397 #[test]
398 fn voice_header_client_roundtrip() -> TestResult {
399 let mut buf = [0u8; 64];
400 let n = encode_voice_header(&mut buf, sid(0xCAFE), &test_header())?;
401 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
402 match pkt {
403 ClientPacket::VoiceHeader { stream_id, header } => {
404 assert_eq!(stream_id, sid(0xCAFE));
405 assert_eq!(header.my_call, test_header().my_call);
406 }
407 other => return Err(format!("expected VoiceHeader, got {other:?}").into()),
408 }
409 Ok(())
410 }
411
412 #[test]
413 fn voice_data_client_roundtrip() -> TestResult {
414 let frame = VoiceFrame {
415 ambe: [0x11; 9],
416 slow_data: [0x22; 3],
417 };
418 let mut buf = [0u8; 64];
419 let n = encode_voice_data(&mut buf, sid(0x1234), 5, &frame)?;
420 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
421 match pkt {
422 ClientPacket::VoiceData {
423 stream_id,
424 seq,
425 frame: f,
426 } => {
427 assert_eq!(stream_id, sid(0x1234));
428 assert_eq!(seq, 5);
429 assert_eq!(f.ambe, [0x11; 9]);
430 assert_eq!(f.slow_data, [0x22; 3]);
431 }
432 other => return Err(format!("expected VoiceData, got {other:?}").into()),
433 }
434 Ok(())
435 }
436
437 #[test]
438 fn voice_eot_client_roundtrip() -> TestResult {
439 let mut buf = [0u8; 64];
440 let n = encode_voice_eot(&mut buf, sid(0x1234), 7)?;
441 let pkt = decode_client_to_server(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
442 match pkt {
443 ClientPacket::VoiceEot { stream_id, seq } => {
444 assert_eq!(stream_id, sid(0x1234));
445 assert_eq!(seq & 0x40, 0x40, "EOT bit set");
446 assert_eq!(seq & 0x3F, 7, "low bits preserve seq");
447 }
448 other => return Err(format!("expected VoiceEot, got {other:?}").into()),
449 }
450 Ok(())
451 }
452
453 #[test]
455 fn connect_ack_server_roundtrip() -> TestResult {
456 let mut buf = [0u8; 16];
457 let n = encode_connect_ack(&mut buf, &cs(*b"XRF030 "), Module::C)?;
458 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
459 match pkt {
460 ServerPacket::ConnectAck {
461 callsign,
462 reflector_module,
463 } => {
464 assert_eq!(callsign, cs(*b"XRF030 "));
465 assert_eq!(reflector_module, Module::C);
466 }
467 other => return Err(format!("expected ConnectAck, got {other:?}").into()),
468 }
469 Ok(())
470 }
471
472 #[test]
473 fn connect_nak_server_roundtrip() -> TestResult {
474 let mut buf = [0u8; 16];
475 let n = encode_connect_nak(&mut buf, &cs(*b"XRF030 "), Module::C)?;
476 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
477 match pkt {
478 ServerPacket::ConnectNak {
479 callsign,
480 reflector_module,
481 } => {
482 assert_eq!(callsign, cs(*b"XRF030 "));
483 assert_eq!(reflector_module, Module::C);
484 }
485 other => return Err(format!("expected ConnectNak, got {other:?}").into()),
486 }
487 Ok(())
488 }
489
490 #[test]
491 fn poll_echo_server_roundtrip() -> TestResult {
492 let mut buf = [0u8; 16];
493 let n = encode_poll(&mut buf, &cs(*b"XRF030 "))?;
494 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
495 match pkt {
496 ServerPacket::PollEcho { callsign } => {
497 assert_eq!(callsign, cs(*b"XRF030 "));
498 }
499 other => return Err(format!("expected PollEcho, got {other:?}").into()),
500 }
501 Ok(())
502 }
503
504 #[test]
505 fn voice_header_server_roundtrip() -> TestResult {
506 let mut buf = [0u8; 64];
507 let n = encode_voice_header(&mut buf, sid(0xCAFE), &test_header())?;
508 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
509 match pkt {
510 ServerPacket::VoiceHeader { stream_id, header } => {
511 assert_eq!(stream_id, sid(0xCAFE));
512 assert_eq!(header.my_call, test_header().my_call);
513 }
514 other => return Err(format!("expected VoiceHeader, got {other:?}").into()),
515 }
516 Ok(())
517 }
518
519 #[test]
520 fn voice_data_server_roundtrip() -> TestResult {
521 let frame = VoiceFrame {
522 ambe: [0x33; 9],
523 slow_data: [0x44; 3],
524 };
525 let mut buf = [0u8; 64];
526 let n = encode_voice_data(&mut buf, sid(0x4321), 9, &frame)?;
527 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
528 match pkt {
529 ServerPacket::VoiceData {
530 stream_id,
531 seq,
532 frame: f,
533 } => {
534 assert_eq!(stream_id, sid(0x4321));
535 assert_eq!(seq, 9);
536 assert_eq!(f.ambe, [0x33; 9]);
537 assert_eq!(f.slow_data, [0x44; 3]);
538 }
539 other => return Err(format!("expected VoiceData, got {other:?}").into()),
540 }
541 Ok(())
542 }
543
544 #[test]
545 fn voice_eot_server_roundtrip() -> TestResult {
546 let mut buf = [0u8; 64];
547 let n = encode_voice_eot(&mut buf, sid(0x1234), 7)?;
548 let pkt = decode_server_to_client(buf.get(..n).ok_or("n within buf")?, &mut NullSink)?;
549 match pkt {
550 ServerPacket::VoiceEot { stream_id, seq } => {
551 assert_eq!(stream_id, sid(0x1234));
552 assert_eq!(seq & 0x40, 0x40, "EOT bit set");
553 assert_eq!(seq & 0x3F, 7, "low bits preserve seq");
554 }
555 other => return Err(format!("expected VoiceEot, got {other:?}").into()),
556 }
557 Ok(())
558 }
559
560 #[test]
562 fn unknown_length_returns_error() -> TestResult {
563 let Err(err) = decode_client_to_server(&[0u8; 12], &mut NullSink) else {
564 return Err("expected error for bad length".into());
565 };
566 assert!(matches!(err, DExtraError::UnknownPacketLength { got: 12 }));
567 Ok(())
568 }
569
570 #[test]
571 fn server_rejects_11_byte_client_link() -> TestResult {
572 let Err(err) = decode_server_to_client(&[0u8; 11], &mut NullSink) else {
574 return Err("expected error for server rejecting 11-byte".into());
575 };
576 assert!(matches!(err, DExtraError::UnknownPacketLength { got: 11 }));
577 Ok(())
578 }
579
580 #[test]
581 fn client_rejects_14_byte_server_reply() -> TestResult {
582 let Err(err) = decode_client_to_server(&[0u8; 14], &mut NullSink) else {
584 return Err("expected error for client rejecting 14-byte".into());
585 };
586 assert!(matches!(err, DExtraError::UnknownPacketLength { got: 14 }));
587 Ok(())
588 }
589
590 #[test]
591 fn link_with_invalid_client_module_byte() -> TestResult {
592 let mut bytes = [b' '; 11];
594 bytes[..4].copy_from_slice(b"W1AW");
595 bytes[8] = b'b'; bytes[9] = b'C';
597 bytes[10] = 0x00;
598 let Err(err) = decode_client_to_server(&bytes, &mut NullSink) else {
599 return Err("expected error for invalid module byte".into());
600 };
601 assert!(matches!(
602 err,
603 DExtraError::InvalidModuleByte {
604 offset: 8,
605 byte: b'b'
606 }
607 ));
608 Ok(())
609 }
610
611 #[test]
612 fn link_with_invalid_reflector_module_byte() -> TestResult {
613 let mut bytes = [b' '; 11];
614 bytes[..4].copy_from_slice(b"W1AW");
615 bytes[8] = b'B';
616 bytes[9] = b'1'; bytes[10] = 0x00;
618 let Err(err) = decode_client_to_server(&bytes, &mut NullSink) else {
619 return Err("expected error for invalid reflector module byte".into());
620 };
621 assert!(matches!(
622 err,
623 DExtraError::InvalidModuleByte {
624 offset: 9,
625 byte: b'1'
626 }
627 ));
628 Ok(())
629 }
630
631 #[test]
632 fn voice_data_with_zero_stream_id_rejected() -> TestResult {
633 let mut bytes = [0u8; 27];
634 bytes[..4].copy_from_slice(b"DSVT");
635 bytes[4] = 0x20;
636 bytes[8] = 0x20;
637 let Err(err) = decode_client_to_server(&bytes, &mut NullSink) else {
639 return Err("expected error for zero stream id".into());
640 };
641 assert!(matches!(err, DExtraError::StreamIdZero));
642 Ok(())
643 }
644
645 #[test]
646 fn voice_header_missing_dsvt_magic_rejected() -> TestResult {
647 let mut bytes = [0u8; 56];
648 bytes[..4].copy_from_slice(b"XXXX"); bytes[4] = 0x10;
650 bytes[12] = 0x34;
651 bytes[13] = 0x12;
652 let Err(err) = decode_client_to_server(&bytes, &mut NullSink) else {
653 return Err("expected error for bad DSVT magic".into());
654 };
655 assert!(matches!(err, DExtraError::DsvtMagicMissing { .. }));
656 Ok(())
657 }
658
659 #[test]
660 fn connect_reply_with_unknown_tag() -> TestResult {
661 let mut bytes = [b' '; 14];
662 bytes[..4].copy_from_slice(b"XRF0");
663 bytes[8] = b'C';
664 bytes[9] = b'C';
665 bytes[10..13].copy_from_slice(b"FOO");
667 bytes[13] = 0x00;
668 let Err(err) = decode_server_to_client(&bytes, &mut NullSink) else {
669 return Err("expected error for unknown connect tag".into());
670 };
671 assert!(matches!(err, DExtraError::UnknownConnectTag { .. }));
672 Ok(())
673 }
674}