]> code.octet-stream.net Git - m17rt/commitdiff
Correctly call all adapter lifecycle methods, docs and test
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Sun, 2 Feb 2025 23:35:55 +0000 (10:35 +1100)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Sun, 2 Feb 2025 23:35:55 +0000 (10:35 +1100)
m17app/src/adapter.rs
m17app/src/app.rs

index 8fdf9d9a3c7064a92fba563cd0b00044cec008c5..81fdb5b0fbbbc35044f749b1312bdf083919940b 100644 (file)
@@ -2,14 +2,29 @@ use crate::{app::TxHandle, link_setup::LinkSetup};
 use m17core::protocol::PacketType;
 use std::sync::Arc;
 
 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 {
 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;
     }
     fn adapter_registered(&self, id: usize, handle: TxHandle) {
         let _ = id;
         let _ = handle;
     }
+
+    /// This adapter was removed from an `M17App`.
     fn adapter_removed(&self) {}
     fn adapter_removed(&self) {}
+
+    /// The TNC has been started and incoming packets may now arrive.
     fn tnc_started(&self) {}
     fn tnc_started(&self) {}
+
+    /// The TNC has been shut down. There will be no more tx/rx.
     fn tnc_closed(&self) {}
     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;
     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 {
 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;
     }
     fn adapter_registered(&self, id: usize, handle: TxHandle) {
         let _ = id;
         let _ = handle;
     }
+
+    /// This adapter was removed from an `M17App`.
     fn adapter_removed(&self) {}
     fn adapter_removed(&self) {}
+
+    /// The TNC has been started and incoming streams may now arrive.
     fn tnc_started(&self) {}
     fn tnc_started(&self) {}
+
+    /// The TNC has been shut down. There will be no more tx/rx.
     fn tnc_closed(&self) {}
     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;
     }
     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;
     fn stream_data(&self, frame_number: u16, is_final: bool, data: Arc<[u8; 16]>) {
         let _ = frame_number;
         let _ = is_final;
index 0abfab640b51937b9076b45cea922ea43c4e954a..d7c3fb2c129ba43b776296f6cacfb7e5b6621d11 100644 (file)
@@ -71,10 +71,28 @@ impl M17App {
     }
 
     pub fn start(&self) {
     }
 
     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 _ = 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);
         // 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<Event>);
+        struct FakeStream(mpsc::SyncSender<Event>);
+        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));
+    }
 }
 }