dstar_gateway_core/session/server/
forwardable.rs

1//! `ForwardableFrame` — encoded voice frame ready for fan-out.
2//!
3//! The fan-out engine in `dstar-gateway-server` uses this type to
4//! describe a voice frame in "ready to re-send" form. The idea is
5//! that the frame is already encoded in its wire format, so the
6//! fan-out loop can write the same bytes to N clients without
7//! re-encoding.
8
9use crate::types::{ProtocolKind, StreamId};
10
11/// A voice frame ready to be forwarded to other connected clients.
12#[non_exhaustive]
13#[derive(Debug, Clone, Copy)]
14pub enum ForwardableFrame<'a> {
15    /// Voice header (first packet of a stream).
16    Header {
17        /// Protocol of the originating client.
18        protocol: ProtocolKind,
19        /// Stream id.
20        stream_id: StreamId,
21        /// Encoded wire bytes (borrowed from the receive buffer).
22        bytes: &'a [u8],
23    },
24    /// Voice data frame (middle of a stream).
25    Data {
26        /// Protocol of the originating client.
27        protocol: ProtocolKind,
28        /// Stream id.
29        stream_id: StreamId,
30        /// Frame seq.
31        seq: u8,
32        /// Encoded wire bytes.
33        bytes: &'a [u8],
34    },
35    /// Voice EOT (final packet of a stream).
36    Eot {
37        /// Protocol of the originating client.
38        protocol: ProtocolKind,
39        /// Stream id.
40        stream_id: StreamId,
41        /// Final seq.
42        seq: u8,
43        /// Encoded wire bytes.
44        bytes: &'a [u8],
45    },
46}
47
48impl<'a> ForwardableFrame<'a> {
49    /// The protocol this frame belongs to.
50    #[must_use]
51    pub const fn protocol(&self) -> ProtocolKind {
52        match self {
53            Self::Header { protocol, .. }
54            | Self::Data { protocol, .. }
55            | Self::Eot { protocol, .. } => *protocol,
56        }
57    }
58
59    /// The stream id this frame belongs to.
60    #[must_use]
61    pub const fn stream_id(&self) -> StreamId {
62        match self {
63            Self::Header { stream_id, .. }
64            | Self::Data { stream_id, .. }
65            | Self::Eot { stream_id, .. } => *stream_id,
66        }
67    }
68
69    /// The wire bytes ready for forwarding.
70    #[must_use]
71    pub const fn bytes(&self) -> &'a [u8] {
72        match self {
73            Self::Header { bytes, .. } | Self::Data { bytes, .. } | Self::Eot { bytes, .. } => {
74                bytes
75            }
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::{ForwardableFrame, ProtocolKind, StreamId};
83
84    #[expect(clippy::unwrap_used, reason = "const-validated: n is non-zero")]
85    const fn sid(n: u16) -> StreamId {
86        StreamId::new(n).unwrap()
87    }
88
89    #[test]
90    fn header_accessors_work() {
91        let sid = sid(0x1234);
92        let frame = ForwardableFrame::Header {
93            protocol: ProtocolKind::DExtra,
94            stream_id: sid,
95            bytes: &[1, 2, 3, 4, 5],
96        };
97        assert_eq!(frame.protocol(), ProtocolKind::DExtra);
98        assert_eq!(frame.stream_id(), sid);
99        assert_eq!(frame.bytes(), &[1, 2, 3, 4, 5]);
100    }
101
102    #[test]
103    fn data_accessors_work() {
104        let sid = sid(0xABCD);
105        let frame = ForwardableFrame::Data {
106            protocol: ProtocolKind::DPlus,
107            stream_id: sid,
108            seq: 5,
109            bytes: &[0xAA; 27],
110        };
111        assert_eq!(frame.protocol(), ProtocolKind::DPlus);
112        assert_eq!(frame.stream_id(), sid);
113        assert_eq!(frame.bytes().len(), 27);
114    }
115
116    #[test]
117    fn eot_accessors_work() {
118        let sid = sid(0x0101);
119        let frame = ForwardableFrame::Eot {
120            protocol: ProtocolKind::Dcs,
121            stream_id: sid,
122            seq: 0x40,
123            bytes: &[0xFF; 100],
124        };
125        assert_eq!(frame.protocol(), ProtocolKind::Dcs);
126        assert_eq!(frame.stream_id(), sid);
127        assert_eq!(frame.bytes().len(), 100);
128    }
129}