X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/4cfda08117c4288a5408d45db1ef4be82f4facaa..23b1648b69b4fa11fdff5f43d704af216c1da401:/m17core/src/tnc.rs diff --git a/m17core/src/tnc.rs b/m17core/src/tnc.rs index ee0fa30..93f1420 100644 --- a/m17core/src/tnc.rs +++ b/m17core/src/tnc.rs @@ -1,5 +1,7 @@ use crate::address::{Address, Callsign}; -use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM}; +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, @@ -22,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, + /// Current monotonic time, counted in samples now: u64, @@ -63,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 { @@ -72,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, @@ -83,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. @@ -103,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, + }); } } } @@ -125,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]) @@ -172,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, }); } @@ -196,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 { @@ -212,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 { 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 { @@ -315,6 +370,31 @@ 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; @@ -329,7 +409,7 @@ impl SoftTnc { 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::Callsign(Callsign(*b"M17RT-PKT")), &Address::Broadcast, )); self.packet_queue[self.packet_next] = pending; @@ -356,7 +436,7 @@ impl SoftTnc { } 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[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; @@ -409,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), @@ -420,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, @@ -456,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, @@ -519,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; @@ -542,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: when support is added for the basic packet port they could arrive in either order + + 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); + + let mut payload_buf = [0u8; 2048]; + let n = kiss.decode_payload(&mut payload_buf).unwrap(); + assert_eq!(n, 30 + 1 + pt_len + 2); + + // 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 }, + }; - // TODO: finish all handle_frame tests as below - // this will be much more straightforward when we have a way to create LSFs programatically + let mut tnc = SoftTnc::new(); + let mut kiss = KissFrame::new_empty(); - // receiving a single-frame packet + // 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); - // receiving a multi-frame packet + let mut payload_buf = [0u8; 2048]; + let n = kiss.decode_payload(&mut payload_buf).unwrap(); + assert_eq!(n, 30 + 26); - // 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_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() {