dstar_gateway/tokio_shell/
command.rs

1//! `Command` enum for the channel between an `AsyncSession` handle
2//! and the spawned `SessionLoop` task.
3
4use dstar_gateway_core::header::DStarHeader;
5use dstar_gateway_core::types::StreamId;
6use dstar_gateway_core::voice::VoiceFrame;
7
8use super::ShellError;
9
10/// Commands sent from a user-facing [`super::AsyncSession`] handle
11/// to the spawned tokio task that drives the sans-io core.
12#[derive(Debug)]
13pub enum Command {
14    /// Send a voice header and start a new outbound stream.
15    SendHeader {
16        /// The header to transmit.
17        header: Box<DStarHeader>,
18        /// Stream id for the voice burst.
19        stream_id: StreamId,
20        /// Reply channel — `Ok(())` on success, or the shell error.
21        reply: tokio::sync::oneshot::Sender<Result<(), ShellError>>,
22    },
23
24    /// Send a voice data frame.
25    SendVoice {
26        /// Stream id.
27        stream_id: StreamId,
28        /// Frame seq (0..21 cycle).
29        seq: u8,
30        /// 9 AMBE bytes + 3 slow data bytes.
31        frame: Box<VoiceFrame>,
32        /// Reply channel.
33        reply: tokio::sync::oneshot::Sender<Result<(), ShellError>>,
34    },
35
36    /// Send a voice EOT and close the outbound stream.
37    SendEot {
38        /// Stream id.
39        stream_id: StreamId,
40        /// Final seq.
41        seq: u8,
42        /// Reply channel.
43        reply: tokio::sync::oneshot::Sender<Result<(), ShellError>>,
44    },
45
46    /// Initiate a graceful disconnect and wait for UNLINK ACK or timeout.
47    Disconnect {
48        /// Reply channel — fires when disconnect is complete.
49        reply: tokio::sync::oneshot::Sender<()>,
50    },
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use dstar_gateway_core::types::StreamId;
57
58    #[expect(clippy::unwrap_used, reason = "const-validated: 0x1234 is non-zero")]
59    const fn sid(n: u16) -> StreamId {
60        StreamId::new(n).unwrap()
61    }
62
63    #[test]
64    fn command_send_eot_constructs() {
65        let (tx, _rx) = tokio::sync::oneshot::channel();
66        let cmd = Command::SendEot {
67            stream_id: sid(0x1234),
68            seq: 5,
69            reply: tx,
70        };
71        assert!(matches!(cmd, Command::SendEot { .. }));
72    }
73}