From 7c22bfc75bf2e3b5ca553ac2baf251790f57a10d Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Mon, 3 Feb 2025 10:35:55 +1100 Subject: [PATCH] Correctly call all adapter lifecycle methods, docs and test --- m17app/src/adapter.rs | 42 +++++++++++++++++++++++ m17app/src/app.rs | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/m17app/src/adapter.rs b/m17app/src/adapter.rs index 8fdf9d9..81fdb5b 100644 --- a/m17app/src/adapter.rs +++ b/m17app/src/adapter.rs @@ -2,14 +2,29 @@ use crate::{app::TxHandle, link_setup::LinkSetup}; use m17core::protocol::PacketType; use std::sync::Arc; +/// Can be connected to an `M17App` to receive incoming packet data. +/// +/// The `packet_received` callback will be fired once for each incoming packet type. Any filtering +/// must be done by the receiver. There are also some lifecycle callbacks, one of which will provide +/// a `TxHandle` when the adapter is first added to the app. This means the adapter can transmit as +/// well as receive. pub trait PacketAdapter: Send + Sync + 'static { + /// This adapter was added to an `M17App`. fn adapter_registered(&self, id: usize, handle: TxHandle) { let _ = id; let _ = handle; } + + /// This adapter was removed from an `M17App`. fn adapter_removed(&self) {} + + /// The TNC has been started and incoming packets may now arrive. fn tnc_started(&self) {} + + /// The TNC has been shut down. There will be no more tx/rx. fn tnc_closed(&self) {} + + /// A packet has been received and assembled by the radio. fn packet_received(&self, link_setup: LinkSetup, packet_type: PacketType, content: Arc<[u8]>) { let _ = link_setup; let _ = packet_type; @@ -17,17 +32,44 @@ pub trait PacketAdapter: Send + Sync + 'static { } } +/// Can be connected to an `M17App` to receive incoming streams (voice or data). +/// +/// Once an incoming stream has been acquired (either by receiving a Link Setup Frame or by decoding +/// an ongoing LICH), all stream frames will be provided to this adapter. +/// +/// There are also some lifecycle callbacks, one of which will provide a `TxHandle` when the adapter +/// is first added to the app. This means the adapter can transmit as well as receive. pub trait StreamAdapter: Send + Sync + 'static { + /// This adapter was added to an `M17App`. fn adapter_registered(&self, id: usize, handle: TxHandle) { let _ = id; let _ = handle; } + + /// This adapter was removed from an `M17App`. fn adapter_removed(&self) {} + + /// The TNC has been started and incoming streams may now arrive. fn tnc_started(&self) {} + + /// The TNC has been shut down. There will be no more tx/rx. fn tnc_closed(&self) {} + + /// A new incoming stream has begun. + /// + /// If we did not receive the end of the previous stream, this may occur even there was never a + /// `stream_data` where `is_final` is true. fn stream_began(&self, link_setup: LinkSetup) { let _ = link_setup; } + + /// A frame has been received for an ongoing incoming stream. + /// + /// It is not guaranteed to receive every frame. Frame numbers may not start from 0, and they will + /// wrap around to 0 after 0x7fff. If we receive an indication that the frame is the final one then + /// `is_final` is set. If the transmitter never sends that frame or we fail to receive it then the + /// stream may trail off without that being set. Implementors should consider setting an appropriate + /// timeout to consider a stream "dead" and wait for the next `stream_began`. fn stream_data(&self, frame_number: u16, is_final: bool, data: Arc<[u8; 16]>) { let _ = frame_number; let _ = is_final; diff --git a/m17app/src/app.rs b/m17app/src/app.rs index 0abfab6..d7c3fb2 100644 --- a/m17app/src/app.rs +++ b/m17app/src/app.rs @@ -71,10 +71,28 @@ impl M17App { } pub fn start(&self) { + { + let adapters = self.adapters.read().unwrap(); + for (_, p) in &adapters.packet { + p.tnc_started(); + } + for (_, s) in &adapters.stream { + s.tnc_started(); + } + } let _ = self.event_tx.send(TncControlEvent::Start); } pub fn close(&self) { + { + let adapters = self.adapters.read().unwrap(); + for (_, p) in &adapters.packet { + p.tnc_closed(); + } + for (_, s) in &adapters.stream { + s.tnc_closed(); + } + } // TODO: blocking function to indicate TNC has finished closing // then we could call this in a signal handler to ensure PTT is dropped before quit let _ = self.event_tx.send(TncControlEvent::Close); @@ -321,4 +339,65 @@ mod tests { }) ); } + + #[test] + fn adapter_lifecycle() { + #[derive(Debug, PartialEq)] + enum Event { + Registered(usize), + Removed, + Started, + Closed, + } + macro_rules! event_impl { + ($target:ty, $trait:ty) => { + impl $trait for $target { + fn adapter_registered(&self, id: usize, _handle: TxHandle) { + self.0.send(Event::Registered(id)).unwrap(); + } + + fn adapter_removed(&self) { + self.0.send(Event::Removed).unwrap(); + } + + fn tnc_started(&self) { + self.0.send(Event::Started).unwrap(); + } + + fn tnc_closed(&self) { + self.0.send(Event::Closed).unwrap(); + } + } + }; + } + struct FakePacket(mpsc::SyncSender); + struct FakeStream(mpsc::SyncSender); + event_impl!(FakePacket, PacketAdapter); + event_impl!(FakeStream, StreamAdapter); + + let app = M17App::new(NullTnc); + let (tx_p, rx_p) = mpsc::sync_channel(128); + let (tx_s, rx_s) = mpsc::sync_channel(128); + let packet = FakePacket(tx_p); + let stream = FakeStream(tx_s); + + let id_p = app.add_packet_adapter(packet); + let id_s = app.add_stream_adapter(stream); + app.start(); + app.close(); + app.remove_packet_adapter(id_p); + app.remove_stream_adapter(id_s); + + assert_eq!(rx_p.try_recv(), Ok(Event::Registered(0))); + assert_eq!(rx_p.try_recv(), Ok(Event::Started)); + assert_eq!(rx_p.try_recv(), Ok(Event::Closed)); + assert_eq!(rx_p.try_recv(), Ok(Event::Removed)); + assert_eq!(rx_p.try_recv(), Err(mpsc::TryRecvError::Disconnected)); + + assert_eq!(rx_s.try_recv(), Ok(Event::Registered(1))); + assert_eq!(rx_s.try_recv(), Ok(Event::Started)); + assert_eq!(rx_s.try_recv(), Ok(Event::Closed)); + assert_eq!(rx_s.try_recv(), Ok(Event::Removed)); + assert_eq!(rx_s.try_recv(), Err(mpsc::TryRecvError::Disconnected)); + } } -- 2.39.5