1use crate::error::EncodeError;
7use crate::header::DStarHeader;
8use crate::types::{Callsign, StreamId};
9use crate::voice::{AMBE_SILENCE, VoiceFrame};
10
11use super::consts::{
12 DSVT_MAGIC, DSVT_TYPE, DV_CLIENT_ID, LINK1_ACK_BYTES, LINK1_BYTES, LINK2_ACCEPT_TAG,
13 LINK2_BUSY_TAG, LINK2_HEADER, LINK2_REPLY_PREFIX, POLL_BYTES, POLL_ECHO_BYTES,
14 UNLINK_ACK_BYTES, UNLINK_BYTES, VOICE_DATA_PREFIX, VOICE_EOT_PREFIX, VOICE_EOT_TRAILER,
15 VOICE_HEADER_PREFIX,
16};
17use super::packet::Link2Result;
18
19pub fn encode_link1(out: &mut [u8]) -> Result<usize, EncodeError> {
30 write_fixed(out, &LINK1_BYTES)
31}
32
33pub fn encode_unlink(out: &mut [u8]) -> Result<usize, EncodeError> {
44 write_fixed(out, &UNLINK_BYTES)
45}
46
47pub fn encode_poll(out: &mut [u8]) -> Result<usize, EncodeError> {
57 write_fixed(out, &POLL_BYTES)
58}
59
60pub fn encode_link1_ack(out: &mut [u8]) -> Result<usize, EncodeError> {
70 write_fixed(out, &LINK1_ACK_BYTES)
71}
72
73pub fn encode_unlink_ack(out: &mut [u8]) -> Result<usize, EncodeError> {
83 write_fixed(out, &UNLINK_ACK_BYTES)
84}
85
86pub fn encode_poll_echo(out: &mut [u8]) -> Result<usize, EncodeError> {
97 write_fixed(out, &POLL_ECHO_BYTES)
98}
99
100fn write_fixed(out: &mut [u8], src: &[u8]) -> Result<usize, EncodeError> {
102 if out.len() < src.len() {
103 return Err(EncodeError::BufferTooSmall {
104 need: src.len(),
105 have: out.len(),
106 });
107 }
108 if let Some(dst) = out.get_mut(..src.len()) {
109 dst.copy_from_slice(src);
110 }
111 Ok(src.len())
112}
113
114pub fn encode_link2(out: &mut [u8], callsign: &Callsign) -> Result<usize, EncodeError> {
130 const LEN: usize = 28;
131 if out.len() < LEN {
132 return Err(EncodeError::BufferTooSmall {
133 need: LEN,
134 have: out.len(),
135 });
136 }
137 if let Some(dst) = out.get_mut(..LEN) {
139 for b in dst.iter_mut() {
140 *b = 0;
141 }
142 }
143 if let Some(dst) = out.get_mut(..LINK2_HEADER.len()) {
145 dst.copy_from_slice(&LINK2_HEADER);
146 }
147 let bytes = callsign.as_bytes();
151 let trimmed_len = bytes.iter().rposition(|&b| b != b' ').map_or(0, |p| p + 1);
152 if let Some(dst) = out.get_mut(4..4 + trimmed_len)
153 && let Some(src) = bytes.get(..trimmed_len)
154 {
155 dst.copy_from_slice(src);
156 }
157 if let Some(dst) = out.get_mut(20..28) {
159 dst.copy_from_slice(&DV_CLIENT_ID);
160 }
161 Ok(LEN)
162}
163
164pub fn encode_link2_reply(out: &mut [u8], result: Link2Result) -> Result<usize, EncodeError> {
180 const LEN: usize = 8;
181 if out.len() < LEN {
182 return Err(EncodeError::BufferTooSmall {
183 need: LEN,
184 have: out.len(),
185 });
186 }
187 if let Some(dst) = out.get_mut(..LINK2_REPLY_PREFIX.len()) {
188 dst.copy_from_slice(&LINK2_REPLY_PREFIX);
189 }
190 let tag: [u8; 4] = match result {
191 Link2Result::Accept => LINK2_ACCEPT_TAG,
192 Link2Result::Busy => LINK2_BUSY_TAG,
193 Link2Result::Unknown { reply } => reply,
194 };
195 if let Some(dst) = out.get_mut(4..8) {
196 dst.copy_from_slice(&tag);
197 }
198 Ok(LEN)
199}
200
201pub fn encode_voice_header(
227 out: &mut [u8],
228 stream_id: StreamId,
229 header: &DStarHeader,
230) -> Result<usize, EncodeError> {
231 const LEN: usize = 58;
232 if out.len() < LEN {
233 return Err(EncodeError::BufferTooSmall {
234 need: LEN,
235 have: out.len(),
236 });
237 }
238 if let Some(b) = out.get_mut(0) {
239 *b = VOICE_HEADER_PREFIX;
240 }
241 if let Some(b) = out.get_mut(1) {
242 *b = DSVT_TYPE;
243 }
244 if let Some(dst) = out.get_mut(2..6) {
245 dst.copy_from_slice(&DSVT_MAGIC);
246 }
247 if let Some(b) = out.get_mut(6) {
248 *b = 0x10;
249 }
250 if let Some(b) = out.get_mut(7) {
251 *b = 0x00;
252 }
253 if let Some(b) = out.get_mut(8) {
254 *b = 0x00;
255 }
256 if let Some(b) = out.get_mut(9) {
257 *b = 0x00;
258 }
259 if let Some(b) = out.get_mut(10) {
260 *b = 0x20;
261 }
262 if let Some(b) = out.get_mut(11) {
263 *b = 0x00;
264 }
265 if let Some(b) = out.get_mut(12) {
266 *b = 0x01;
267 }
268 if let Some(b) = out.get_mut(13) {
269 *b = 0x02;
270 }
271 let sid = stream_id.get().to_le_bytes();
272 if let Some(b) = out.get_mut(14) {
273 *b = sid[0];
274 }
275 if let Some(b) = out.get_mut(15) {
276 *b = sid[1];
277 }
278 if let Some(b) = out.get_mut(16) {
279 *b = 0x80;
280 }
281 let encoded = header.encode_for_dsvt();
282 if let Some(dst) = out.get_mut(17..58) {
283 dst.copy_from_slice(&encoded);
284 }
285 Ok(LEN)
286}
287
288pub fn encode_voice_data(
314 out: &mut [u8],
315 stream_id: StreamId,
316 seq: u8,
317 frame: &VoiceFrame,
318) -> Result<usize, EncodeError> {
319 const LEN: usize = 29;
320 if out.len() < LEN {
321 return Err(EncodeError::BufferTooSmall {
322 need: LEN,
323 have: out.len(),
324 });
325 }
326 write_voice_prefix(out, VOICE_DATA_PREFIX, stream_id, seq);
327 if let Some(dst) = out.get_mut(17..26) {
328 dst.copy_from_slice(&frame.ambe);
329 }
330 if let Some(dst) = out.get_mut(26..29) {
331 dst.copy_from_slice(&frame.slow_data);
332 }
333 Ok(LEN)
334}
335
336pub fn encode_voice_eot(
354 out: &mut [u8],
355 stream_id: StreamId,
356 seq: u8,
357) -> Result<usize, EncodeError> {
358 const LEN: usize = 32;
359 if out.len() < LEN {
360 return Err(EncodeError::BufferTooSmall {
361 need: LEN,
362 have: out.len(),
363 });
364 }
365 write_voice_prefix(out, VOICE_EOT_PREFIX, stream_id, seq | 0x40);
366 if let Some(dst) = out.get_mut(17..26) {
367 dst.copy_from_slice(&AMBE_SILENCE);
368 }
369 if let Some(dst) = out.get_mut(26..32) {
370 dst.copy_from_slice(&VOICE_EOT_TRAILER);
371 }
372 Ok(LEN)
373}
374
375fn write_voice_prefix(out: &mut [u8], prefix: u8, stream_id: StreamId, seq: u8) {
378 if let Some(b) = out.get_mut(0) {
379 *b = prefix;
380 }
381 if let Some(b) = out.get_mut(1) {
382 *b = DSVT_TYPE;
383 }
384 if let Some(dst) = out.get_mut(2..6) {
385 dst.copy_from_slice(&DSVT_MAGIC);
386 }
387 if let Some(b) = out.get_mut(6) {
388 *b = 0x20;
389 }
390 if let Some(b) = out.get_mut(7) {
391 *b = 0x00;
392 }
393 if let Some(b) = out.get_mut(8) {
394 *b = 0x00;
395 }
396 if let Some(b) = out.get_mut(9) {
397 *b = 0x00;
398 }
399 if let Some(b) = out.get_mut(10) {
400 *b = 0x20;
401 }
402 if let Some(b) = out.get_mut(11) {
403 *b = 0x00;
404 }
405 if let Some(b) = out.get_mut(12) {
406 *b = 0x01;
407 }
408 if let Some(b) = out.get_mut(13) {
409 *b = 0x02;
410 }
411 let sid = stream_id.get().to_le_bytes();
412 if let Some(b) = out.get_mut(14) {
413 *b = sid[0];
414 }
415 if let Some(b) = out.get_mut(15) {
416 *b = sid[1];
417 }
418 if let Some(b) = out.get_mut(16) {
419 *b = seq;
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use crate::types::Suffix;
427
428 type TestResult = Result<(), Box<dyn std::error::Error>>;
429
430 const fn cs(bytes: [u8; 8]) -> Callsign {
431 Callsign::from_wire_bytes(bytes)
432 }
433
434 #[expect(clippy::unwrap_used, reason = "compile-time validated: n != 0")]
435 const fn sid(n: u16) -> StreamId {
436 StreamId::new(n).unwrap()
437 }
438
439 fn test_header() -> DStarHeader {
440 DStarHeader {
441 flag1: 0,
442 flag2: 0,
443 flag3: 0,
444 rpt2: Callsign::from_wire_bytes(*b"REF030 G"),
445 rpt1: Callsign::from_wire_bytes(*b"REF030 C"),
446 ur_call: Callsign::from_wire_bytes(*b"CQCQCQ "),
447 my_call: Callsign::from_wire_bytes(*b"W1AW "),
448 my_suffix: Suffix::EMPTY,
449 }
450 }
451
452 #[test]
454 fn encode_link1_writes_five_bytes() -> TestResult {
455 let mut buf = [0u8; 16];
456 let n = encode_link1(&mut buf)?;
457 assert_eq!(n, 5);
458 assert_eq!(&buf[..5], &[0x05, 0x00, 0x18, 0x00, 0x01]);
459 Ok(())
460 }
461
462 #[test]
463 fn encode_unlink_writes_five_bytes() -> TestResult {
464 let mut buf = [0u8; 16];
465 let n = encode_unlink(&mut buf)?;
466 assert_eq!(n, 5);
467 assert_eq!(&buf[..5], &[0x05, 0x00, 0x18, 0x00, 0x00]);
468 Ok(())
469 }
470
471 #[test]
472 fn encode_poll_writes_three_bytes() -> TestResult {
473 let mut buf = [0u8; 16];
474 let n = encode_poll(&mut buf)?;
475 assert_eq!(n, 3);
476 assert_eq!(&buf[..3], &[0x03, 0x60, 0x00]);
477 Ok(())
478 }
479
480 #[test]
481 fn encode_link1_ack_matches_link1() -> TestResult {
482 let mut buf = [0u8; 16];
483 let n = encode_link1_ack(&mut buf)?;
484 assert_eq!(
485 buf.get(..n).ok_or("n within buf")?,
486 &[0x05, 0x00, 0x18, 0x00, 0x01]
487 );
488 Ok(())
489 }
490
491 #[test]
492 fn encode_unlink_ack_matches_unlink() -> TestResult {
493 let mut buf = [0u8; 16];
494 let n = encode_unlink_ack(&mut buf)?;
495 assert_eq!(
496 buf.get(..n).ok_or("n within buf")?,
497 &[0x05, 0x00, 0x18, 0x00, 0x00]
498 );
499 Ok(())
500 }
501
502 #[test]
503 fn encode_poll_echo_matches_poll() -> TestResult {
504 let mut buf = [0u8; 16];
505 let n = encode_poll_echo(&mut buf)?;
506 assert_eq!(buf.get(..n).ok_or("n within buf")?, &[0x03, 0x60, 0x00]);
507 Ok(())
508 }
509
510 #[test]
511 fn encode_link1_rejects_small_buffer() -> TestResult {
512 let mut buf = [0u8; 4];
513 let Err(err) = encode_link1(&mut buf) else {
514 return Err("expected BufferTooSmall error".into());
515 };
516 assert_eq!(err, EncodeError::BufferTooSmall { need: 5, have: 4 });
517 Ok(())
518 }
519
520 #[test]
521 fn encode_poll_rejects_two_byte_buffer() -> TestResult {
522 let mut buf = [0u8; 2];
523 let Err(err) = encode_poll(&mut buf) else {
524 return Err("expected BufferTooSmall error".into());
525 };
526 assert_eq!(err, EncodeError::BufferTooSmall { need: 3, have: 2 });
527 Ok(())
528 }
529
530 #[test]
532 fn encode_link2_w1aw_writes_28_bytes() -> TestResult {
533 let mut buf = [0u8; 32];
534 let n = encode_link2(&mut buf, &cs(*b"W1AW "))?;
535 assert_eq!(n, 28);
536 assert_eq!(&buf[..4], &[0x1C, 0xC0, 0x04, 0x00], "header");
537 assert_eq!(
538 &buf[4..12],
539 b"W1AW\0\0\0\0",
540 "callsign followed by zeros to offset 12"
541 );
542 assert_eq!(
543 &buf[12..20],
544 &[0u8; 8],
545 "zero-pad between callsign and DV id"
546 );
547 assert_eq!(&buf[20..28], b"DV019999", "client identifier");
548 Ok(())
549 }
550
551 #[test]
552 fn encode_link2_rejects_buffer_too_small() -> TestResult {
553 let mut buf = [0u8; 16];
554 let Err(err) = encode_link2(&mut buf, &cs(*b"W1AW ")) else {
555 return Err("expected BufferTooSmall error".into());
556 };
557 assert_eq!(err, EncodeError::BufferTooSmall { need: 28, have: 16 });
558 Ok(())
559 }
560
561 #[test]
563 fn encode_link2_reply_accept_writes_okrw() -> TestResult {
564 let mut buf = [0u8; 16];
565 let n = encode_link2_reply(&mut buf, Link2Result::Accept)?;
566 assert_eq!(n, 8);
567 assert_eq!(&buf[..4], &[0x08, 0xC0, 0x04, 0x00]);
568 assert_eq!(&buf[4..8], b"OKRW");
569 Ok(())
570 }
571
572 #[test]
573 fn encode_link2_reply_busy_writes_busy() -> TestResult {
574 let mut buf = [0u8; 16];
575 let n = encode_link2_reply(&mut buf, Link2Result::Busy)?;
576 assert_eq!(n, 8);
577 assert_eq!(&buf[4..8], b"BUSY");
578 Ok(())
579 }
580
581 #[test]
582 fn encode_link2_reply_unknown_writes_custom_tag() -> TestResult {
583 let mut buf = [0u8; 16];
584 let n = encode_link2_reply(&mut buf, Link2Result::Unknown { reply: *b"FAIL" })?;
585 assert_eq!(&buf[4..8], b"FAIL");
586 assert_eq!(n, 8);
587 Ok(())
588 }
589
590 #[test]
592 fn encode_voice_header_writes_58_bytes() -> TestResult {
593 let mut buf = [0u8; 64];
594 let n = encode_voice_header(&mut buf, sid(0xCAFE), &test_header())?;
595 assert_eq!(n, 58);
596 assert_eq!(buf[0], 0x3A, "DPlus prefix");
597 assert_eq!(buf[1], 0x80, "DSVT type");
598 assert_eq!(&buf[2..6], b"DSVT");
599 assert_eq!(buf[6], 0x10, "header type");
600 assert_eq!(buf[14], 0xFE, "stream id LE low byte");
601 assert_eq!(buf[15], 0xCA, "stream id LE high byte");
602 assert_eq!(buf[16], 0x80, "header indicator");
603 assert_eq!(buf[17], 0, "flag1 zeroed by encode_for_dsvt");
604 assert_eq!(buf[18], 0, "flag2 zeroed by encode_for_dsvt");
605 assert_eq!(buf[19], 0, "flag3 zeroed by encode_for_dsvt");
606 Ok(())
607 }
608
609 #[test]
610 fn encode_voice_header_rejects_small_buffer() -> TestResult {
611 let mut buf = [0u8; 32];
612 let Err(err) = encode_voice_header(&mut buf, sid(0x1234), &test_header()) else {
613 return Err("expected BufferTooSmall error".into());
614 };
615 assert_eq!(err, EncodeError::BufferTooSmall { need: 58, have: 32 });
616 Ok(())
617 }
618
619 #[test]
621 fn encode_voice_data_writes_29_bytes() -> TestResult {
622 let mut buf = [0u8; 64];
623 let frame = VoiceFrame {
624 ambe: [0x11; 9],
625 slow_data: [0x22; 3],
626 };
627 let n = encode_voice_data(&mut buf, sid(0x1234), 5, &frame)?;
628 assert_eq!(n, 29);
629 assert_eq!(buf[0], 0x1D, "DPlus prefix");
630 assert_eq!(buf[1], 0x80, "DSVT type");
631 assert_eq!(&buf[2..6], b"DSVT");
632 assert_eq!(buf[14], 0x34, "stream id LE low byte");
633 assert_eq!(buf[15], 0x12, "stream id LE high byte");
634 assert_eq!(buf[16], 5, "seq");
635 assert_eq!(&buf[17..26], &[0x11; 9]);
636 assert_eq!(&buf[26..29], &[0x22; 3]);
637 Ok(())
638 }
639
640 #[test]
642 fn encode_voice_eot_writes_32_bytes() -> TestResult {
643 let mut buf = [0u8; 64];
644 let n = encode_voice_eot(&mut buf, sid(0x1234), 7)?;
645 assert_eq!(n, 32);
646 assert_eq!(buf[0], 0x20, "EOT prefix");
647 assert_eq!(buf[1], 0x80, "DSVT type");
648 assert_eq!(buf[16] & 0x40, 0x40, "EOT bit set");
649 assert_eq!(buf[16] & 0x3F, 7, "low bits preserve seq");
650 assert_eq!(&buf[17..26], &AMBE_SILENCE);
651 assert_eq!(&buf[26..32], &[0x55, 0x55, 0x55, 0x55, 0xC8, 0x7A]);
652 Ok(())
653 }
654}