dstar_gateway_core/slowdata/
block.rs

1//! Parsed slow data block types.
2
3use crate::header::DStarHeader;
4
5/// Slow data block type, recovered from the high nibble of byte 0
6/// after descrambling.
7///
8/// Reference: `ircDDBGateway/Common/DStarDefines.h:85-92`
9/// (`SLOW_DATA_TYPE_GPS = 0x30`, `SLOW_DATA_TYPE_TEXT = 0x40`, etc.).
10#[non_exhaustive]
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SlowDataBlockKind {
13    /// `0x3X` — GPS NMEA passthrough.
14    Gps,
15    /// `0x4X` — 20-character status text.
16    Text,
17    /// `0x5X` — header retransmission.
18    HeaderRetx,
19    /// `0x8X` — fast data variant 1.
20    FastData1,
21    /// `0x9X` — fast data variant 2.
22    FastData2,
23    /// `0xCX` — squelch / control.
24    Squelch,
25    /// Any other high nibble.
26    Unknown {
27        /// The high nibble that didn't match.
28        high_nibble: u8,
29    },
30}
31
32impl SlowDataBlockKind {
33    /// Decode the high nibble of the type byte into a kind.
34    #[must_use]
35    pub const fn from_type_byte(byte: u8) -> Self {
36        match byte & 0xF0 {
37            0x30 => Self::Gps,
38            0x40 => Self::Text,
39            0x50 => Self::HeaderRetx,
40            0x80 => Self::FastData1,
41            0x90 => Self::FastData2,
42            0xC0 => Self::Squelch,
43            other => Self::Unknown {
44                high_nibble: other >> 4,
45            },
46        }
47    }
48}
49
50/// 20-character status text frame.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct SlowDataText {
53    /// Trimmed text (UTF-8 lossy).
54    pub text: String,
55}
56
57/// A complete slow data block extracted from a stream of voice frames.
58#[non_exhaustive]
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum SlowDataBlock {
61    /// GPS NMEA sentence.
62    Gps(String),
63    /// 20-character text frame.
64    Text(SlowDataText),
65    /// Retransmitted header.
66    HeaderRetx(DStarHeader),
67    /// Fast data block 1 — opaque payload.
68    FastData(Vec<u8>),
69    /// Squelch / control marker.
70    Squelch {
71        /// Squelch code byte.
72        code: u8,
73    },
74    /// Unknown block type — payload preserved verbatim.
75    Unknown {
76        /// The type byte that didn't match a known kind.
77        type_byte: u8,
78        /// Block payload.
79        payload: Vec<u8>,
80    },
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn kind_from_text_byte() {
89        assert_eq!(
90            SlowDataBlockKind::from_type_byte(0x4F),
91            SlowDataBlockKind::Text
92        );
93    }
94
95    #[test]
96    fn kind_from_gps_byte() {
97        assert_eq!(
98            SlowDataBlockKind::from_type_byte(0x3A),
99            SlowDataBlockKind::Gps
100        );
101    }
102
103    #[test]
104    fn kind_from_header_retx_byte() {
105        assert_eq!(
106            SlowDataBlockKind::from_type_byte(0x52),
107            SlowDataBlockKind::HeaderRetx
108        );
109    }
110
111    #[test]
112    fn kind_from_fast_data1_byte() {
113        assert_eq!(
114            SlowDataBlockKind::from_type_byte(0x83),
115            SlowDataBlockKind::FastData1
116        );
117    }
118
119    #[test]
120    fn kind_from_fast_data2_byte() {
121        assert_eq!(
122            SlowDataBlockKind::from_type_byte(0x92),
123            SlowDataBlockKind::FastData2
124        );
125    }
126
127    #[test]
128    fn kind_from_squelch_byte() {
129        assert_eq!(
130            SlowDataBlockKind::from_type_byte(0xC5),
131            SlowDataBlockKind::Squelch
132        );
133    }
134
135    #[test]
136    fn kind_from_unknown_byte() {
137        let kind = SlowDataBlockKind::from_type_byte(0xA5);
138        assert!(matches!(
139            kind,
140            SlowDataBlockKind::Unknown { high_nibble: 0xA }
141        ));
142    }
143}