]> code.octet-stream.net Git - m17rt/blobdiff - m17core/src/tnc.rs
Implement p-persistent CSMA
[m17rt] / m17core / src / tnc.rs
index 0d8bf4afa17feba8769426d76de7ef88cb4ee3d4..a7cbc53b893dee9c6b18df2b6623678f58c9d3ec 100644 (file)
@@ -1,3 +1,4 @@
+use crate::address::{Address, Callsign};
 use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM};
 use crate::modem::ModulatorFrame;
 use crate::protocol::{
 use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM};
 use crate::modem::ModulatorFrame;
 use crate::protocol::{
@@ -21,6 +22,9 @@ pub struct SoftTnc {
     /// Latest state of data carrier detect from demodulator - controls whether we can go to TX
     dcd: bool,
 
     /// 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,
 
     /// Current monotonic time, counted in samples
     now: u64,
 
@@ -71,6 +75,7 @@ impl SoftTnc {
             outgoing_kiss: None,
             state: State::Idle,
             dcd: false,
             outgoing_kiss: None,
             state: State::Idle,
             dcd: false,
+            next_csma_check: None,
             now: 0,
             packet_queue: Default::default(),
             packet_next: 0,
             now: 0,
             packet_queue: Default::default(),
             packet_next: 0,
@@ -211,6 +216,7 @@ impl SoftTnc {
     }
 
     pub fn set_tx_end_time(&mut self, in_samples: usize) {
     }
 
     pub fn set_tx_end_time(&mut self, in_samples: usize) {
+        log::debug!("tnc has been told that tx will complete in {in_samples} samples");
         match self.state {
             State::TxEnding => {
                 self.state = State::TxEndingAtTime(self.now + in_samples as u64);
         match self.state {
             State::TxEnding => {
                 self.state = State::TxEndingAtTime(self.now + in_samples as u64);
@@ -222,17 +228,45 @@ impl SoftTnc {
     pub fn read_tx_frame(&mut self) -> Option<ModulatorFrame> {
         match self.state {
             State::Idle | State::RxAcquiringStream(_) | State::RxStream(_) | State::RxPacket(_) => {
     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);
                 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
+                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;
                     self.state = State::TxStream;
-                } else if channel_free && packet_wants_to_tx {
-                    self.state = State::TxPacket;
                 } else {
                 } else {
-                    return None;
+                    self.state = State::TxPacket;
                 }
                 self.ptt = true;
                 // TODO: true txdelay
                 }
                 self.ptt = true;
                 // TODO: true txdelay
@@ -251,7 +285,7 @@ impl SoftTnc {
                 }
                 self.stream_curr = (self.stream_curr + 1) % 8;
                 if frame.end_of_stream {
                 }
                 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))
             }
                 }
                 Some(ModulatorFrame::Stream(frame))
             }
@@ -315,8 +349,86 @@ impl SoftTnc {
                 continue;
             };
             if port == PORT_PACKET_BASIC {
                 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".clone())),
+                    &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 {
             } 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..]);
+                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 {
             } 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
             }
         }
         n
@@ -404,6 +516,15 @@ struct PendingPacket {
 }
 
 impl 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.
     /// Returns next frame, not including preamble or EOT.
     ///
     /// False means all data frames have been sent.