]> code.octet-stream.net Git - m17rt/blobdiff - m17core/src/tnc.rs
More unit tests for TNC rx/assembly - packet mode
[m17rt] / m17core / src / tnc.rs
index 0d8bf4afa17feba8769426d76de7ef88cb4ee3d4..93f14203e956ee8ca3f517b281c646a98323082e 100644 (file)
@@ -1,4 +1,7 @@
-use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM};
+use crate::address::{Address, Callsign};
+use crate::kiss::{
+    KissBuffer, KissCommand, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM,
+};
 use crate::modem::ModulatorFrame;
 use crate::protocol::{
     Frame, LichCollection, LsfFrame, Mode, PacketFrame, PacketFrameCounter, StreamFrame,
@@ -21,6 +24,9 @@ pub struct SoftTnc {
     /// Latest state of data carrier detect from demodulator - controls whether we can go to TX
     dcd: bool,
 
+    /// If CSMA declined to transmit into an idle slot, at what point do we next check it?
+    next_csma_check: Option<u64>,
+
     /// Current monotonic time, counted in samples
     now: u64,
 
@@ -62,6 +68,12 @@ pub struct SoftTnc {
 
     /// Should PTT be on right now? Polled by external
     ptt: bool,
+
+    /// TxDelay raw value, number of 10ms units. We will optimistically start with default 0.
+    tx_delay: u8,
+
+    /// This is a full duplex channel so we do not need to monitor DCD or use CSMA. Default false.
+    full_duplex: bool,
 }
 
 impl SoftTnc {
@@ -71,6 +83,7 @@ impl SoftTnc {
             outgoing_kiss: None,
             state: State::Idle,
             dcd: false,
+            next_csma_check: None,
             now: 0,
             packet_queue: Default::default(),
             packet_next: 0,
@@ -82,11 +95,17 @@ impl SoftTnc {
             stream_curr: 0,
             stream_full: false,
             ptt: false,
+            tx_delay: 0,
+            full_duplex: false,
         }
     }
 
     /// Process an individual `Frame` that has been decoded by the modem.
     pub fn handle_frame(&mut self, frame: Frame) {
+        if self.ptt {
+            // Ignore self-decodes
+            return;
+        }
         match frame {
             Frame::Lsf(lsf) => {
                 // A new LSF implies a clean slate.
@@ -102,7 +121,10 @@ impl SoftTnc {
                     Mode::Stream => {
                         let kiss = KissFrame::new_stream_setup(&lsf.0).unwrap();
                         self.kiss_to_host(kiss);
-                        self.state = State::RxStream(RxStreamState { lsf, index: 0 });
+                        self.state = State::RxStream(RxStreamState {
+                            _lsf: lsf,
+                            index: 0,
+                        });
                     }
                 }
             }
@@ -124,7 +146,7 @@ impl SoftTnc {
                                 let start = 25 * rx.count;
                                 let end = start + payload_len;
                                 rx.packet[start..(start + payload_len)]
-                                    .copy_from_slice(&packet.payload);
+                                    .copy_from_slice(&packet.payload[0..payload_len]);
                                 // TODO: compatible packets should be sent on port 0 too
                                 let kiss =
                                     KissFrame::new_full_packet(&rx.lsf.0, &rx.packet[0..end])
@@ -171,7 +193,7 @@ impl SoftTnc {
                                 // TODO: avoid discarding the first data payload here
                                 // need a queue depth of 2 for outgoing kiss
                                 self.state = State::RxStream(RxStreamState {
-                                    lsf,
+                                    _lsf: lsf,
                                     index: stream.frame_number + 1,
                                 });
                             }
@@ -195,15 +217,16 @@ impl SoftTnc {
 
     pub fn set_now(&mut self, now_samples: u64) {
         self.now = now_samples;
-        match self.state {
-            State::TxEndingAtTime(time) => {
-                if now_samples >= time {
-                    self.ptt = false;
-                    self.state = State::Idle;
-                }
+        // TODO: expose this to higher layer so we can schedule a precise delay
+        // rather than waiting for some soundcard I/O event
+        if let State::TxEndingAtTime(time) = self.state {
+            if now_samples >= time {
+                self.ptt = false;
+                self.state = State::Idle;
             }
-            _ => (),
         }
+        // TODO: should expire packet rx if we have not received a frame for a while
+        // otherwise we could pick up the LSF from one station and FinalFrame from another
     }
 
     pub fn ptt(&self) -> bool {
@@ -211,32 +234,65 @@ impl SoftTnc {
     }
 
     pub fn set_tx_end_time(&mut self, in_samples: usize) {
-        match self.state {
-            State::TxEnding => {
-                self.state = State::TxEndingAtTime(self.now + in_samples as u64);
-            }
-            _ => (),
+        log::debug!("tnc has been told that tx will complete in {in_samples} samples");
+        if let State::TxEnding = self.state {
+            self.state = State::TxEndingAtTime(self.now + in_samples as u64);
         }
     }
 
     pub fn read_tx_frame(&mut self) -> Option<ModulatorFrame> {
         match self.state {
             State::Idle | State::RxAcquiringStream(_) | State::RxStream(_) | State::RxPacket(_) => {
-                // We will let CSMA decide whether to actually go ahead.
-                // That's not implemented yet, so let's just check DCD.
-                let channel_free = !self.dcd;
                 let stream_wants_to_tx = self.stream_pending_lsf.is_some();
                 let packet_wants_to_tx = self.packet_full || (self.packet_next != self.packet_curr);
-                if channel_free && stream_wants_to_tx {
+                if !stream_wants_to_tx && !packet_wants_to_tx {
+                    return None;
+                }
+
+                // We have something we might send if the channel is free
+
+                // TODO: Proper full duplex support
+                // A true full duplex TNC should be able to rx and tx concurrently, implying
+                // separate states.
+                if !self.full_duplex {
+                    match self.next_csma_check {
+                        None => {
+                            if self.dcd {
+                                self.next_csma_check = Some(self.now + 1920);
+                                return None;
+                            } else {
+                                // channel is idle at the moment we get a frame to send
+                                // go right ahead
+                            }
+                        }
+                        Some(at_time) => {
+                            if self.now < at_time {
+                                return None;
+                            }
+                            // 25% chance that we'll transmit this slot.
+                            // Using self.now as random is probably fine so long as it's not being set in
+                            // a lumpy manner. m17app's soundmodem should be fine.
+                            // TODO: bring in prng to help in cases where `now` never ends in 0b11
+                            let p1_4 = (self.now & 3) == 3;
+                            if !self.dcd || !p1_4 {
+                                self.next_csma_check = Some(self.now + 1920);
+                                return None;
+                            } else {
+                                self.next_csma_check = None;
+                            }
+                        }
+                    }
+                }
+
+                if stream_wants_to_tx {
                     self.state = State::TxStream;
-                } else if channel_free && packet_wants_to_tx {
-                    self.state = State::TxPacket;
                 } else {
-                    return None;
+                    self.state = State::TxPacket;
                 }
                 self.ptt = true;
-                // TODO: true txdelay
-                Some(ModulatorFrame::Preamble { tx_delay: 0 })
+                Some(ModulatorFrame::Preamble {
+                    tx_delay: self.tx_delay,
+                })
             }
             State::TxStream => {
                 if !self.stream_full && self.stream_next == self.stream_curr {
@@ -251,7 +307,7 @@ impl SoftTnc {
                 }
                 self.stream_curr = (self.stream_curr + 1) % 8;
                 if frame.end_of_stream {
-                    self.state = State::Idle;
+                    self.state = State::TxStreamSentEndOfStream;
                 }
                 Some(ModulatorFrame::Stream(frame))
             }
@@ -314,9 +370,112 @@ impl SoftTnc {
             let Ok(port) = kiss_frame.port() else {
                 continue;
             };
+            let Ok(command) = kiss_frame.command() else {
+                continue;
+            };
+            if port != PORT_PACKET_BASIC && port != PORT_PACKET_FULL && port != PORT_STREAM {
+                continue;
+            }
+            if command == KissCommand::TxDelay {
+                let mut new_delay = [0u8; 1];
+                if kiss_frame.decode_payload(&mut new_delay) == Ok(1) {
+                    self.tx_delay = new_delay[0];
+                }
+                continue;
+            }
+            if command == KissCommand::FullDuplex {
+                let mut new_duplex = [0u8; 1];
+                if kiss_frame.decode_payload(&mut new_duplex) == Ok(1) {
+                    self.full_duplex = new_duplex[0] != 0;
+                }
+                continue;
+            }
+            if command != KissCommand::DataFrame {
+                // Not supporting any other settings yet
+                // TODO: allow adjusting P persistence parameter for CSMA
+                continue;
+            }
             if port == PORT_PACKET_BASIC {
+                if self.packet_full {
+                    continue;
+                }
+                let mut pending = PendingPacket::new();
+                pending.app_data[0] = 0x00; // RAW
+                let Ok(mut len) = kiss_frame.decode_payload(&mut pending.app_data[1..]) else {
+                    continue;
+                };
+                len += 1; // for RAW prefix
+                let packet_crc = crate::crc::m17_crc(&pending.app_data[0..len]);
+                pending.app_data[len..len + 2].copy_from_slice(&packet_crc.to_be_bytes());
+                pending.app_data_len = len + 2;
+                pending.lsf = Some(LsfFrame::new_packet(
+                    &Address::Callsign(Callsign(*b"M17RT-PKT")),
+                    &Address::Broadcast,
+                ));
+                self.packet_queue[self.packet_next] = pending;
+                self.packet_next = (self.packet_next + 1) % 4;
+                if self.packet_next == self.packet_curr {
+                    self.packet_full = true;
+                }
             } else if port == PORT_PACKET_FULL {
+                if self.packet_full {
+                    continue;
+                }
+                let mut pending = PendingPacket::new();
+                let mut payload = [0u8; 855];
+                let Ok(len) = kiss_frame.decode_payload(&mut payload) else {
+                    continue;
+                };
+                if len < 33 {
+                    continue;
+                }
+                let mut lsf = LsfFrame([0u8; 30]);
+                lsf.0.copy_from_slice(&payload[0..30]);
+                if lsf.check_crc() != 0 {
+                    continue;
+                }
+                pending.lsf = Some(lsf);
+                let app_data_len = len - 30;
+                pending.app_data[0..app_data_len].copy_from_slice(&payload[30..len]);
+                pending.app_data_len = app_data_len;
+                self.packet_queue[self.packet_next] = pending;
+                self.packet_next = (self.packet_next + 1) % 4;
+                if self.packet_next == self.packet_curr {
+                    self.packet_full = true;
+                }
             } else if port == PORT_STREAM {
+                let mut payload = [0u8; 30];
+                let Ok(len) = kiss_frame.decode_payload(&mut payload) else {
+                    continue;
+                };
+                if len < 26 {
+                    log::debug!("payload len too short");
+                    continue;
+                }
+                if len == 30 {
+                    let lsf = LsfFrame(payload);
+                    if lsf.check_crc() != 0 {
+                        continue;
+                    }
+                    self.stream_pending_lsf = Some(lsf);
+                } else {
+                    if self.stream_full {
+                        log::debug!("stream full");
+                        continue;
+                    }
+                    let frame_num_part = u16::from_be_bytes([payload[6], payload[7]]);
+                    self.stream_queue[self.stream_next] = StreamFrame {
+                        lich_idx: payload[5] >> 5,
+                        lich_part: payload[0..5].try_into().unwrap(),
+                        frame_number: frame_num_part & 0x7fff,
+                        end_of_stream: frame_num_part & 0x8000 > 0,
+                        stream_data: payload[8..24].try_into().unwrap(),
+                    };
+                    self.stream_next = (self.stream_next + 1) % 8;
+                    if self.stream_next == self.stream_curr {
+                        self.stream_full = true;
+                    }
+                }
             }
         }
         n
@@ -330,6 +489,12 @@ impl SoftTnc {
     }
 }
 
+impl Default for SoftTnc {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub enum SoftTncError {
     General(&'static str),
@@ -341,6 +506,7 @@ struct OutgoingKiss {
     sent: usize,
 }
 
+#[allow(clippy::large_enum_variant)]
 enum State {
     /// Nothing happening. We may have TX data queued but we won't act on it until CSMA opens up.
     Idle,
@@ -377,7 +543,7 @@ struct RxAcquiringStreamState {
 
 struct RxStreamState {
     /// Track identifying information for this transmission so we can tell if it changes.
-    lsf: LsfFrame,
+    _lsf: LsfFrame,
 
     /// Expected next frame number. Allowed to skip values on RX, but not go backwards.
     index: u16,
@@ -404,6 +570,15 @@ struct PendingPacket {
 }
 
 impl PendingPacket {
+    fn new() -> Self {
+        Self {
+            lsf: None,
+            app_data: [0u8; 825],
+            app_data_len: 0,
+            app_data_transmitted: 0,
+        }
+    }
+
     /// Returns next frame, not including preamble or EOT.
     ///
     /// False means all data frames have been sent.
@@ -431,7 +606,7 @@ impl PendingPacket {
             )
         };
         let mut payload = [0u8; 25];
-        payload.copy_from_slice(
+        payload[0..data_len].copy_from_slice(
             &self.app_data[self.app_data_transmitted..(self.app_data_transmitted + data_len)],
         );
         self.app_data_transmitted += data_len;
@@ -454,16 +629,156 @@ impl Default for PendingPacket {
 mod tests {
     use super::*;
     use crate::kiss::{KissCommand, PORT_STREAM};
-    use crate::protocol::StreamFrame;
+    use crate::protocol::{PacketType, StreamFrame};
+
+    #[test]
+    fn tnc_receive_single_frame_packet() {
+        let lsf = LsfFrame::new_packet(
+            &Address::Callsign(Callsign(*b"VK7XT    ")),
+            &Address::Broadcast,
+        );
+        let mut payload = [0u8; 25];
+        let (pt, pt_len) = PacketType::Sms.as_proto();
+        payload[0..pt_len].copy_from_slice(&pt[0..pt_len]);
+        payload[pt_len] = 0x41; // a message
+        let crc = crate::crc::m17_crc(&payload[0..=pt_len]).to_be_bytes();
+        payload[pt_len + 1] = crc[0];
+        payload[pt_len + 2] = crc[1];
+
+        let packet = PacketFrame {
+            payload,
+            counter: PacketFrameCounter::FinalFrame {
+                payload_len: pt_len + 3,
+            },
+        };
+        let mut tnc = SoftTnc::new();
+        let mut kiss = KissFrame::new_empty();
+
+        // TNC consumes LSF but has nothing to report yet
+        tnc.handle_frame(Frame::Lsf(lsf));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
 
-    // TODO: finish all handle_frame tests as below
-    // this will be much more straightforward when we have a way to create LSFs programatically
+        // TODO: when support is added for the basic packet port they could arrive in either order
 
-    // receiving a single-frame packet
+        tnc.handle_frame(Frame::Packet(packet));
+        kiss.len = tnc.read_kiss(&mut kiss.data);
+        assert_eq!(kiss.command().unwrap(), KissCommand::DataFrame);
+        assert_eq!(kiss.port().unwrap(), PORT_PACKET_FULL);
 
-    // receiving a multi-frame packet
+        let mut payload_buf = [0u8; 2048];
+        let n = kiss.decode_payload(&mut payload_buf).unwrap();
+        assert_eq!(n, 30 + 1 + pt_len + 2);
 
-    // part of one packet and then another
+        // did we receive our message? (after the LSF)
+        assert_eq!(payload_buf[pt_len + 30], 0x41);
+    }
+
+    #[test]
+    fn tnc_receive_multiple_frame_packet() {
+        let lsf = LsfFrame::new_packet(
+            &Address::Callsign(Callsign(*b"VK7XT    ")),
+            &Address::Broadcast,
+        );
+        let mut payload = [0x41u8; 26]; // spans two frames
+        let (pt, pt_len) = PacketType::Sms.as_proto();
+        payload[0..pt_len].copy_from_slice(&pt[0..pt_len]);
+        let crc = crate::crc::m17_crc(&payload[0..24]).to_be_bytes();
+        payload[24] = crc[0];
+        payload[25] = crc[1];
+
+        let packet1 = PacketFrame {
+            payload: payload[0..25].try_into().unwrap(),
+            counter: PacketFrameCounter::Frame { index: 0 },
+        };
+        let mut payload2 = [0u8; 25];
+        payload2[0] = payload[25];
+        let packet2 = PacketFrame {
+            payload: payload2,
+            counter: PacketFrameCounter::FinalFrame { payload_len: 1 },
+        };
+
+        let mut tnc = SoftTnc::new();
+        let mut kiss = KissFrame::new_empty();
+
+        // Nothing to report until second final packet frame received
+        tnc.handle_frame(Frame::Lsf(lsf));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
+        tnc.handle_frame(Frame::Packet(packet1));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
+
+        tnc.handle_frame(Frame::Packet(packet2));
+        kiss.len = tnc.read_kiss(&mut kiss.data);
+        assert_eq!(kiss.command().unwrap(), KissCommand::DataFrame);
+        assert_eq!(kiss.port().unwrap(), PORT_PACKET_FULL);
+
+        let mut payload_buf = [0u8; 2048];
+        let n = kiss.decode_payload(&mut payload_buf).unwrap();
+        assert_eq!(n, 30 + 26);
+
+        // did we receive our message? (after the LSF)
+        assert_eq!(payload_buf[pt_len + 30], 0x41);
+    }
+
+    #[test]
+    fn tnc_receive_partial_packet() {
+        let lsf = LsfFrame::new_packet(
+            &Address::Callsign(Callsign(*b"VK7XT    ")),
+            &Address::Broadcast,
+        );
+        let mut payload = [0x41u8; 26]; // spans two frames
+        let (pt, pt_len) = PacketType::Sms.as_proto();
+        payload[0..pt_len].copy_from_slice(&pt[0..pt_len]);
+        let crc = crate::crc::m17_crc(&payload[0..24]).to_be_bytes();
+        payload[24] = crc[0];
+        payload[25] = crc[1];
+
+        let packet1 = PacketFrame {
+            payload: payload[0..25].try_into().unwrap(),
+            counter: PacketFrameCounter::Frame { index: 0 },
+        };
+        // final frame of this transmission is dropped
+
+        let lsf2 = LsfFrame::new_packet(
+            &Address::Callsign(Callsign(*b"VK7XT    ")),
+            &Address::Broadcast,
+        );
+        let mut payload = [0u8; 25];
+        let (pt, pt_len) = PacketType::Sms.as_proto();
+        payload[0..pt_len].copy_from_slice(&pt[0..pt_len]);
+        payload[pt_len] = 0x42;
+        let crc = crate::crc::m17_crc(&payload[0..=pt_len]).to_be_bytes();
+        payload[pt_len + 1] = crc[0];
+        payload[pt_len + 2] = crc[1];
+
+        let packet2 = PacketFrame {
+            payload: payload[0..25].try_into().unwrap(),
+            counter: PacketFrameCounter::FinalFrame {
+                payload_len: pt_len + 3,
+            },
+        };
+
+        let mut tnc = SoftTnc::new();
+        let mut kiss = KissFrame::new_empty();
+
+        // Nothing to report until second packet received in its entirety
+        tnc.handle_frame(Frame::Lsf(lsf));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
+        tnc.handle_frame(Frame::Packet(packet1));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
+        tnc.handle_frame(Frame::Lsf(lsf2));
+        assert_eq!(tnc.read_kiss(&mut kiss.data), 0);
+        tnc.handle_frame(Frame::Packet(packet2));
+
+        kiss.len = tnc.read_kiss(&mut kiss.data);
+        assert_eq!(kiss.command().unwrap(), KissCommand::DataFrame);
+        assert_eq!(kiss.port().unwrap(), PORT_PACKET_FULL);
+
+        let mut payload_buf = [0u8; 2048];
+        let n = kiss.decode_payload(&mut payload_buf).unwrap();
+        assert_eq!(n, 30 + 1 + pt_len + 2);
+        // we have received the second packet which has 0x42 in it
+        assert_eq!(payload_buf[pt_len + 30], 0x42);
+    }
 
     #[test]
     fn tnc_receive_stream() {