1use crate::error::EncodeError;
7use crate::header::DStarHeader;
8use crate::types::{Callsign, Module, StreamId};
9use crate::voice::VoiceFrame;
10
11use super::consts::{
12 CONNECT_ACK_TAG, CONNECT_NAK_TAG, CONNECT_REPLY_LEN, LINK_HTML_DONGLE, LINK_HTML_HOTSPOT,
13 LINK_HTML_REPEATER, LINK_HTML_STARNET, LINK_LEN, POLL_LEN, UNLINK_LEN, VOICE_EOT_MARKER,
14 VOICE_LEN, VOICE_MAGIC,
15};
16use super::packet::GatewayType;
17
18pub fn encode_connect_link(
44 out: &mut [u8],
45 callsign: &Callsign,
46 client_module: Module,
47 reflector_module: Module,
48 reflector_callsign: &Callsign,
49 gateway_type: GatewayType,
50) -> Result<usize, EncodeError> {
51 if out.len() < LINK_LEN {
52 return Err(EncodeError::BufferTooSmall {
53 need: LINK_LEN,
54 have: out.len(),
55 });
56 }
57 if let Some(region) = out.get_mut(..LINK_LEN) {
60 for b in region {
61 *b = 0x00;
62 }
63 }
64 write_connect_prefix(
65 out,
66 *callsign,
67 client_module.as_byte(),
68 reflector_module.as_byte(),
69 );
70 if let Some(region) = out.get_mut(11..19) {
77 region.fill(b' ');
78 }
79 let rc = reflector_callsign.as_bytes();
80 if let Some(dst) = out.get_mut(11..18)
81 && let Some(src) = rc.get(..7)
82 {
83 dst.copy_from_slice(src);
84 }
85 let html = html_for(gateway_type);
88 let copy_len = html.len().min(LINK_LEN - 19);
89 if let Some(dst) = out.get_mut(19..19 + copy_len)
90 && let Some(src) = html.get(..copy_len)
91 {
92 dst.copy_from_slice(src);
93 }
94 Ok(LINK_LEN)
95}
96
97pub fn encode_connect_unlink(
118 out: &mut [u8],
119 callsign: &Callsign,
120 client_module: Module,
121 reflector_callsign: &Callsign,
122) -> Result<usize, EncodeError> {
123 if out.len() < UNLINK_LEN {
124 return Err(EncodeError::BufferTooSmall {
125 need: UNLINK_LEN,
126 have: out.len(),
127 });
128 }
129 write_connect_prefix(out, *callsign, client_module.as_byte(), b' ');
130 if let Some(region) = out.get_mut(11..19) {
133 region.fill(b' ');
134 }
135 let rc = reflector_callsign.as_bytes();
136 if let Some(dst) = out.get_mut(11..18)
137 && let Some(src) = rc.get(..7)
138 {
139 dst.copy_from_slice(src);
140 }
141 Ok(UNLINK_LEN)
142}
143
144pub fn encode_connect_ack(
165 out: &mut [u8],
166 callsign: &Callsign,
167 reflector_module: Module,
168) -> Result<usize, EncodeError> {
169 write_connect_reply(out, *callsign, reflector_module, CONNECT_ACK_TAG)?;
170 Ok(CONNECT_REPLY_LEN)
171}
172
173pub fn encode_connect_nak(
186 out: &mut [u8],
187 callsign: &Callsign,
188 reflector_module: Module,
189) -> Result<usize, EncodeError> {
190 write_connect_reply(out, *callsign, reflector_module, CONNECT_NAK_TAG)?;
191 Ok(CONNECT_REPLY_LEN)
192}
193
194pub fn encode_poll_request(
211 out: &mut [u8],
212 callsign: &Callsign,
213 reflector_callsign: &Callsign,
214) -> Result<usize, EncodeError> {
215 write_poll(out, *callsign, *reflector_callsign)?;
216 Ok(POLL_LEN)
217}
218
219pub fn encode_poll_reply(
229 out: &mut [u8],
230 callsign: &Callsign,
231 reflector_callsign: &Callsign,
232) -> Result<usize, EncodeError> {
233 write_poll(out, *callsign, *reflector_callsign)?;
234 Ok(POLL_LEN)
235}
236
237pub fn encode_voice(
273 out: &mut [u8],
274 header: &DStarHeader,
275 stream_id: StreamId,
276 seq: u8,
277 frame: &VoiceFrame,
278 is_end: bool,
279) -> Result<usize, EncodeError> {
280 if out.len() < VOICE_LEN {
281 return Err(EncodeError::BufferTooSmall {
282 need: VOICE_LEN,
283 have: out.len(),
284 });
285 }
286 if let Some(region) = out.get_mut(..VOICE_LEN) {
289 for b in region {
290 *b = 0x00;
291 }
292 }
293 if let Some(dst) = out.get_mut(..4) {
295 dst.copy_from_slice(&VOICE_MAGIC);
296 }
297 if let Some(b) = out.get_mut(4) {
299 *b = header.flag1;
300 }
301 if let Some(b) = out.get_mut(5) {
302 *b = header.flag2;
303 }
304 if let Some(b) = out.get_mut(6) {
305 *b = header.flag3;
306 }
307 if let Some(dst) = out.get_mut(7..15) {
308 dst.copy_from_slice(header.rpt2.as_bytes());
309 }
310 if let Some(dst) = out.get_mut(15..23) {
311 dst.copy_from_slice(header.rpt1.as_bytes());
312 }
313 if let Some(dst) = out.get_mut(23..31) {
314 dst.copy_from_slice(header.ur_call.as_bytes());
315 }
316 if let Some(dst) = out.get_mut(31..39) {
317 dst.copy_from_slice(header.my_call.as_bytes());
318 }
319 if let Some(dst) = out.get_mut(39..43) {
320 dst.copy_from_slice(header.my_suffix.as_bytes());
321 }
322 let sid = stream_id.get().to_le_bytes();
324 if let Some(b) = out.get_mut(43) {
325 *b = sid[0];
326 }
327 if let Some(b) = out.get_mut(44) {
328 *b = sid[1];
329 }
330 let seq_byte = if is_end { seq | 0x40 } else { seq };
337 if let Some(b) = out.get_mut(45) {
338 *b = seq_byte;
339 }
340 if let Some(dst) = out.get_mut(46..55) {
342 dst.copy_from_slice(&frame.ambe);
343 }
344 if is_end {
346 if let Some(dst) = out.get_mut(55..58) {
347 dst.copy_from_slice(&VOICE_EOT_MARKER);
348 }
349 } else if let Some(dst) = out.get_mut(55..58) {
350 dst.copy_from_slice(&frame.slow_data);
351 }
352 if let Some(b) = out.get_mut(61) {
355 *b = 0x01;
356 }
357 if let Some(b) = out.get_mut(62) {
358 *b = 0x00;
359 }
360 if let Some(b) = out.get_mut(63) {
361 *b = 0x21;
362 }
363 Ok(VOICE_LEN)
365}
366
367const fn html_for(gateway_type: GatewayType) -> &'static [u8] {
369 match gateway_type {
370 GatewayType::Repeater => LINK_HTML_REPEATER,
371 GatewayType::Hotspot => LINK_HTML_HOTSPOT,
372 GatewayType::Dongle => LINK_HTML_DONGLE,
373 GatewayType::StarNet => LINK_HTML_STARNET,
374 }
375}
376
377fn write_connect_prefix(out: &mut [u8], callsign: Callsign, byte8: u8, byte9: u8) {
392 if let Some(region) = out.get_mut(..11) {
395 region.fill(b' ');
396 }
397 let cs = callsign.as_bytes();
400 if let Some(dst) = out.get_mut(..7)
401 && let Some(src) = cs.get(..7)
402 {
403 dst.copy_from_slice(src);
404 }
405 if let Some(b) = out.get_mut(8) {
407 *b = byte8;
408 }
409 if let Some(b) = out.get_mut(9) {
411 *b = byte9;
412 }
413 if let Some(b) = out.get_mut(10) {
415 *b = 0x00;
416 }
417}
418
419fn write_connect_reply(
431 out: &mut [u8],
432 callsign: Callsign,
433 reflector_module: Module,
434 tag: [u8; 3],
435) -> Result<(), EncodeError> {
436 if out.len() < CONNECT_REPLY_LEN {
437 return Err(EncodeError::BufferTooSmall {
438 need: CONNECT_REPLY_LEN,
439 have: out.len(),
440 });
441 }
442 if let Some(region) = out.get_mut(..CONNECT_REPLY_LEN) {
445 region.fill(b' ');
446 }
447 let cs = callsign.as_bytes();
449 if let Some(dst) = out.get_mut(..7)
450 && let Some(src) = cs.get(..7)
451 {
452 dst.copy_from_slice(src);
453 }
454 if let Some(b) = out.get_mut(8) {
457 *b = cs.get(7).copied().unwrap_or(b' ');
458 }
459 if let Some(b) = out.get_mut(9) {
461 *b = reflector_module.as_byte();
462 }
463 if let Some(dst) = out.get_mut(10..13) {
465 dst.copy_from_slice(&tag);
466 }
467 if let Some(b) = out.get_mut(13) {
469 *b = 0x00;
470 }
471 Ok(())
472}
473
474fn write_poll(
476 out: &mut [u8],
477 callsign: Callsign,
478 reflector_callsign: Callsign,
479) -> Result<(), EncodeError> {
480 if out.len() < POLL_LEN {
481 return Err(EncodeError::BufferTooSmall {
482 need: POLL_LEN,
483 have: out.len(),
484 });
485 }
486 if let Some(dst) = out.get_mut(..8) {
487 dst.copy_from_slice(callsign.as_bytes());
488 }
489 if let Some(b) = out.get_mut(8) {
490 *b = 0x00;
491 }
492 if let Some(dst) = out.get_mut(9..17) {
493 dst.copy_from_slice(reflector_callsign.as_bytes());
494 }
495 Ok(())
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use crate::types::Suffix;
502
503 type TestResult = Result<(), Box<dyn std::error::Error>>;
504
505 const fn cs(bytes: [u8; 8]) -> Callsign {
506 Callsign::from_wire_bytes(bytes)
507 }
508
509 #[expect(clippy::unwrap_used, reason = "compile-time validated: n != 0")]
510 const fn sid(n: u16) -> StreamId {
511 StreamId::new(n).unwrap()
512 }
513
514 const fn test_header() -> DStarHeader {
515 DStarHeader {
516 flag1: 0,
517 flag2: 0,
518 flag3: 0,
519 rpt2: Callsign::from_wire_bytes(*b"DCS001 G"),
520 rpt1: Callsign::from_wire_bytes(*b"DCS001 C"),
521 ur_call: Callsign::from_wire_bytes(*b"CQCQCQ "),
522 my_call: Callsign::from_wire_bytes(*b"W1AW "),
523 my_suffix: Suffix::EMPTY,
524 }
525 }
526
527 #[test]
529 fn encode_connect_link_writes_519_bytes() -> TestResult {
530 let mut buf = [0u8; 600];
531 let n = encode_connect_link(
532 &mut buf,
533 &cs(*b"W1AW "),
534 Module::B,
535 Module::C,
536 &cs(*b"DCS001 "),
537 GatewayType::Repeater,
538 )?;
539 assert_eq!(n, 519);
540 assert_eq!(&buf[..7], b"W1AW ", "client callsign chars 0..7");
543 assert_eq!(buf[7], b' ', "pad slot");
544 assert_eq!(buf[8], b'B', "client module at [8]");
545 assert_eq!(buf[9], b'C', "reflector module at [9]");
546 assert_eq!(buf[10], 0x00, "null at [10]");
547 assert_eq!(&buf[11..18], b"DCS001 ", "reflector callsign chars 0..7");
549 assert_eq!(buf[18], b' ', "reflector pad slot");
550 assert!(
552 buf[19..].iter().any(|&b| b != 0),
553 "HTML region not all zero"
554 );
555 Ok(())
556 }
557
558 #[test]
559 fn encode_connect_link_rejects_small_buffer() -> TestResult {
560 let mut buf = [0u8; 500];
561 let Err(err) = encode_connect_link(
562 &mut buf,
563 &cs(*b"W1AW "),
564 Module::B,
565 Module::C,
566 &cs(*b"DCS001 "),
567 GatewayType::Repeater,
568 ) else {
569 return Err("expected BufferTooSmall error".into());
570 };
571 assert_eq!(
572 err,
573 EncodeError::BufferTooSmall {
574 need: 519,
575 have: 500,
576 }
577 );
578 Ok(())
579 }
580
581 #[test]
582 fn encode_connect_link_hotspot_banner_differs_from_repeater() -> TestResult {
583 let mut buf_rep = [0u8; 600];
584 let mut buf_hot = [0u8; 600];
585 let _n1 = encode_connect_link(
586 &mut buf_rep,
587 &cs(*b"W1AW "),
588 Module::B,
589 Module::C,
590 &cs(*b"DCS001 "),
591 GatewayType::Repeater,
592 )?;
593 let _n2 = encode_connect_link(
594 &mut buf_hot,
595 &cs(*b"W1AW "),
596 Module::B,
597 Module::C,
598 &cs(*b"DCS001 "),
599 GatewayType::Hotspot,
600 )?;
601 assert_ne!(
602 &buf_rep[19..519],
603 &buf_hot[19..519],
604 "HTML region should differ by gateway type"
605 );
606 Ok(())
607 }
608
609 #[test]
611 fn encode_connect_unlink_writes_19_bytes() -> TestResult {
612 let mut buf = [0u8; 32];
613 let n = encode_connect_unlink(&mut buf, &cs(*b"W1AW "), Module::B, &cs(*b"DCS001 "))?;
614 assert_eq!(n, 19);
615 assert_eq!(&buf[..7], b"W1AW ");
616 assert_eq!(buf[7], b' ', "pad slot");
617 assert_eq!(buf[8], b'B', "client module");
618 assert_eq!(buf[9], b' ', "space (unlink marker)");
619 assert_eq!(buf[10], 0x00);
620 assert_eq!(&buf[11..18], b"DCS001 ", "reflector callsign chars 0..7");
621 assert_eq!(buf[18], b' ', "reflector pad slot");
622 Ok(())
623 }
624
625 #[test]
626 fn encode_connect_unlink_rejects_small_buffer() -> TestResult {
627 let mut buf = [0u8; 10];
628 let Err(err) =
629 encode_connect_unlink(&mut buf, &cs(*b"W1AW "), Module::B, &cs(*b"DCS001 "))
630 else {
631 return Err("expected BufferTooSmall error".into());
632 };
633 assert_eq!(err, EncodeError::BufferTooSmall { need: 19, have: 10 });
634 Ok(())
635 }
636
637 #[test]
639 fn encode_connect_ack_writes_14_bytes() -> TestResult {
640 let mut buf = [0u8; 32];
641 let n = encode_connect_ack(&mut buf, &cs(*b"DCS001 C"), Module::B)?;
644 assert_eq!(n, 14);
645 assert_eq!(&buf[..7], b"DCS001 ", "callsign chars 0..7");
646 assert_eq!(buf[7], b' ', "pad slot");
647 assert_eq!(buf[8], b'C', "echoed repeater module");
648 assert_eq!(buf[9], b'B', "reflector module");
649 assert_eq!(&buf[10..13], b"ACK");
650 assert_eq!(buf[13], 0x00, "NUL terminator");
651 Ok(())
652 }
653
654 #[test]
655 fn encode_connect_nak_writes_14_bytes() -> TestResult {
656 let mut buf = [0u8; 32];
657 let n = encode_connect_nak(&mut buf, &cs(*b"DCS001 C"), Module::B)?;
658 assert_eq!(n, 14);
659 assert_eq!(&buf[..7], b"DCS001 ");
660 assert_eq!(buf[7], b' ');
661 assert_eq!(buf[8], b'C');
662 assert_eq!(buf[9], b'B');
663 assert_eq!(&buf[10..13], b"NAK");
664 assert_eq!(buf[13], 0x00);
665 Ok(())
666 }
667
668 #[test]
669 fn encode_connect_ack_rejects_small_buffer() -> TestResult {
670 let mut buf = [0u8; 13];
671 let Err(err) = encode_connect_ack(&mut buf, &cs(*b"DCS001 "), Module::C) else {
672 return Err("expected BufferTooSmall error".into());
673 };
674 assert_eq!(err, EncodeError::BufferTooSmall { need: 14, have: 13 });
675 Ok(())
676 }
677
678 #[test]
680 fn encode_poll_request_writes_17_bytes() -> TestResult {
681 let mut buf = [0u8; 32];
682 let n = encode_poll_request(&mut buf, &cs(*b"W1AW "), &cs(*b"DCS001 "))?;
683 assert_eq!(n, 17);
684 assert_eq!(&buf[..8], b"W1AW ");
685 assert_eq!(buf[8], 0x00);
686 assert_eq!(&buf[9..17], b"DCS001 ");
687 Ok(())
688 }
689
690 #[test]
691 fn encode_poll_reply_matches_poll_request() -> TestResult {
692 let mut req = [0u8; 17];
693 let mut reply = [0u8; 17];
694 let n1 = encode_poll_request(&mut req, &cs(*b"W1AW "), &cs(*b"DCS001 "))?;
695 let n2 = encode_poll_reply(&mut reply, &cs(*b"W1AW "), &cs(*b"DCS001 "))?;
696 assert_eq!(n1, n2);
697 assert_eq!(req, reply);
698 Ok(())
699 }
700
701 #[test]
702 fn encode_poll_request_rejects_small_buffer() -> TestResult {
703 let mut buf = [0u8; 16];
704 let Err(err) = encode_poll_request(&mut buf, &cs(*b"W1AW "), &cs(*b"DCS001 ")) else {
705 return Err("expected BufferTooSmall error".into());
706 };
707 assert_eq!(err, EncodeError::BufferTooSmall { need: 17, have: 16 });
708 Ok(())
709 }
710
711 #[test]
713 fn encode_voice_writes_100_bytes() -> TestResult {
714 let mut buf = [0u8; 128];
715 let frame = VoiceFrame {
716 ambe: [0x11; 9],
717 slow_data: [0x22; 3],
718 };
719 let n = encode_voice(&mut buf, &test_header(), sid(0xCAFE), 5, &frame, false)?;
720 assert_eq!(n, 100);
721 assert_eq!(&buf[..4], b"0001", "magic");
722 assert_eq!(buf[4], 0, "flag1");
723 assert_eq!(buf[5], 0, "flag2");
724 assert_eq!(buf[6], 0, "flag3");
725 assert_eq!(&buf[7..15], b"DCS001 G", "rpt2");
726 assert_eq!(&buf[15..23], b"DCS001 C", "rpt1");
727 assert_eq!(&buf[23..31], b"CQCQCQ ", "ur");
728 assert_eq!(&buf[31..39], b"W1AW ", "my");
729 assert_eq!(&buf[39..43], b" ", "suffix");
730 assert_eq!(buf[43], 0xFE, "stream id LE low");
731 assert_eq!(buf[44], 0xCA, "stream id LE high");
732 assert_eq!(buf[45], 5, "seq");
733 assert_eq!(&buf[46..55], &[0x11; 9], "AMBE");
734 assert_eq!(&buf[55..58], &[0x22; 3], "slow data");
735 assert_eq!(buf[61], 0x01);
736 assert_eq!(buf[62], 0x00);
737 assert_eq!(buf[63], 0x21);
738 Ok(())
739 }
740
741 #[test]
742 fn encode_voice_eot_sets_marker_and_seq_bit() -> TestResult {
743 let mut buf = [0u8; 128];
744 let frame = VoiceFrame {
745 ambe: [0x11; 9],
746 slow_data: [0x22; 3],
747 };
748 let n = encode_voice(&mut buf, &test_header(), sid(0xCAFE), 7, &frame, true)?;
749 assert_eq!(n, 100);
750 assert_eq!(buf[45] & 0x40, 0x40, "EOT bit set");
751 assert_eq!(buf[45] & 0x3F, 7, "low bits preserve seq");
752 assert_eq!(
753 &buf[55..58],
754 &VOICE_EOT_MARKER,
755 "EOT marker replaces slow data"
756 );
757 Ok(())
758 }
759
760 #[test]
761 fn encode_voice_rejects_small_buffer() -> TestResult {
762 let mut buf = [0u8; 64];
763 let frame = VoiceFrame {
764 ambe: [0; 9],
765 slow_data: [0; 3],
766 };
767 let Err(err) = encode_voice(&mut buf, &test_header(), sid(1), 0, &frame, false) else {
768 return Err("expected BufferTooSmall error".into());
769 };
770 assert_eq!(
771 err,
772 EncodeError::BufferTooSmall {
773 need: 100,
774 have: 64,
775 }
776 );
777 Ok(())
778 }
779
780 #[test]
781 fn encode_voice_embeds_flag_bytes_verbatim() -> TestResult {
782 let mut buf = [0u8; 128];
783 let header = DStarHeader {
784 flag1: 0xAA,
785 flag2: 0xBB,
786 flag3: 0xCC,
787 ..test_header()
788 };
789 let frame = VoiceFrame {
790 ambe: [0; 9],
791 slow_data: [0; 3],
792 };
793 let _n = encode_voice(&mut buf, &header, sid(1), 0, &frame, false)?;
794 assert_eq!(buf[4], 0xAA, "flag1 verbatim (not zeroed like DSVT)");
795 assert_eq!(buf[5], 0xBB);
796 assert_eq!(buf[6], 0xCC);
797 Ok(())
798 }
799}