From bbd6cec8ac825911ba0ea4afe369383c16ea5de8 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 14 Sep 2023 23:51:17 +0200 Subject: [PATCH 01/50] tcp server init --- satrs-core/src/hal/host/mod.rs | 1 + satrs-core/src/hal/host/tcp_server.rs | 9 +++++++++ satrs-core/src/hal/host/udp_server.rs | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 satrs-core/src/hal/host/tcp_server.rs diff --git a/satrs-core/src/hal/host/mod.rs b/satrs-core/src/hal/host/mod.rs index 8057db1..8b23ae9 100644 --- a/satrs-core/src/hal/host/mod.rs +++ b/satrs-core/src/hal/host/mod.rs @@ -1,2 +1,3 @@ //! Helper modules intended to be used on hosts with a full [std] runtime pub mod udp_server; +pub mod tcp_server; diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs new file mode 100644 index 0000000..fe92351 --- /dev/null +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -0,0 +1,9 @@ + +#[cfg(test)] +mod tests { + + #[test] + fn basic_test() { + + } +} diff --git a/satrs-core/src/hal/host/udp_server.rs b/satrs-core/src/hal/host/udp_server.rs index b91a239..de5a3f0 100644 --- a/satrs-core/src/hal/host/udp_server.rs +++ b/satrs-core/src/hal/host/udp_server.rs @@ -51,9 +51,9 @@ use std::vec::Vec; /// .expect("Error sending PUS TC via UDP"); /// ``` /// -/// The [satrs-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/-example) +/// The [fsrc-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example) /// server code also includes -/// [example code](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/tmtc.rs#L67) +/// [example code](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example/src/bin/obsw/tmtc.rs) /// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port /// and then forwards them to a generic CCSDS packet receiver. pub struct UdpTcServer { -- 2.43.0 From 3e9a07b73259b71dce78ad2c131772ddb9204faa Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 14 Sep 2023 23:52:14 +0200 Subject: [PATCH 02/50] link correction --- NOTICE | 2 ++ README.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index 717a583..c39ecb7 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1,3 @@ This software contains code developed at the University of Stuttgart's Institute of Space Systems. + +The sat-rs logo was designed by Nadine Eunous. diff --git a/README.md b/README.md index 75a6b0d..585d3a9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

+ sat-rs ========= @@ -21,7 +23,7 @@ This project currently contains following crates: on a host computer or on any system with a standard runtime like a Raspberry Pi. * [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-mib): Components to build a mission information base from the on-board software directly. -* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/satrs-example-stm32f3-disco): +* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco): Example of a simple example on-board software using sat-rs components on a bare-metal system with constrained resources. -- 2.43.0 From 28801a895214bc561f7ddf8f616d446ca55e69ab Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 Sep 2023 15:37:57 +0200 Subject: [PATCH 03/50] this is actually quite tricky.. --- satrs-core/Cargo.toml | 5 + satrs-core/src/hal/host/mod.rs | 2 +- satrs-core/src/hal/host/tcp_server.rs | 289 +++++++++++++++++++++++++- 3 files changed, 294 insertions(+), 2 deletions(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 2d25971..7b6697f 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -68,6 +68,11 @@ version = "0.7.0-beta.1" # branch = "" default-features = false +[dependencies.cobs] +git = "https://github.com/robamu/cobs.rs.git" +branch = "all_features" +default-features = false + [dev-dependencies] serde = "1" zerocopy = "0.7" diff --git a/satrs-core/src/hal/host/mod.rs b/satrs-core/src/hal/host/mod.rs index 8b23ae9..051a2f7 100644 --- a/satrs-core/src/hal/host/mod.rs +++ b/satrs-core/src/hal/host/mod.rs @@ -1,3 +1,3 @@ //! Helper modules intended to be used on hosts with a full [std] runtime -pub mod udp_server; pub mod tcp_server; +pub mod udp_server; diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs index fe92351..1b3bd7c 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -1,9 +1,296 @@ +use alloc::boxed::Box; +use alloc::vec; +use cobs::decode_in_place; +use std::net::ToSocketAddrs; +use std::vec::Vec; +use std::{io::Read, net::TcpListener}; + +use crate::tmtc::ReceivesTc; + +pub struct TcpTcServer { + listener: TcpListener, + tc_receiver: Box>, + reader_vec: Vec, +} + +impl TcpTcServer { + pub fn new( + addr: A, + tc_receiver: Box>, + reader_vec_size: usize, + ) -> Result { + Ok(TcpTcServer { + listener: TcpListener::bind(addr)?, + tc_receiver, + reader_vec: vec![0; reader_vec_size], + }) + } + + pub fn handle_connections(&mut self) -> Result<(), std::io::Error> { + let mut current_write_idx; + let mut next_write_idx = 0; + for stream in self.listener.incoming() { + current_write_idx = next_write_idx; + next_write_idx = 0; + let mut stream = stream?; + loop { + let read_len = stream.read(&mut self.reader_vec[current_write_idx..])?; + if read_len > 0 { + current_write_idx += read_len; + if current_write_idx == self.reader_vec.capacity() { + // Reader vec full, need to parse for packets. + let parse_result = parse_buffer_for_cobs_encoded_packets( + &mut self.reader_vec, + self.tc_receiver.as_mut(), + &mut next_write_idx, + ); + } + continue; + } + break; + } + if current_write_idx > 0 { + let parse_result = parse_buffer_for_cobs_encoded_packets( + &mut self.reader_vec[..current_write_idx], + self.tc_receiver.as_mut(), + &mut next_write_idx, + ); + } + } + Ok(()) + } +} + +pub fn parse_buffer_for_cobs_encoded_packets( + buf: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let mut start_index_packet = 0; + let mut start_found = false; + let mut last_byte = false; + let mut packets_found = 0; + for i in 0..buf.len() { + if i == buf.len() - 1 { + last_byte = true; + } + if buf[i] == 0 { + if !start_found && !last_byte && buf[i + 1] == 0 { + // Special case: Consecutive sentinel values or all zeroes. + // Skip. + continue; + } + if start_found { + let decode_result = decode_in_place(&mut buf[start_index_packet..i]); + if let Ok(packet_len) = decode_result { + packets_found += 1; + tc_receiver + .pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?; + } + start_found = false; + } else { + start_index_packet = i + 1; + start_found = true; + } + } + } + // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. + if start_index_packet > 0 && start_found && packets_found > 0 { + let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); + first_seg[..last_seg.len()].copy_from_slice(last_seg); + *next_write_idx = last_seg.len(); + } + Ok(packets_found) +} #[cfg(test)] mod tests { + use crate::tmtc::ReceivesTcCore; + use alloc::vec::Vec; + use cobs::encode; + + use super::parse_buffer_for_cobs_encoded_packets; + + const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + + #[derive(Default)] + struct TestSender { + received_tcs: Vec>, + } + + impl ReceivesTcCore for TestSender { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + self.received_tcs.push(tc_raw.to_vec()); + Ok(()) + } + } + + fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; + } #[test] - fn basic_test() { + fn test_parsing_simple_packet() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + let packet = &test_sender.received_tcs[0]; + assert_eq!(packet, &SIMPLE_PACKET); + } + #[test] + fn test_parsing_consecutive_packets() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + + let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; + // Second packet + encoded_buf[current_idx] = 0; + current_idx += 1; + current_idx += encode(&inverted_packet, &mut encoded_buf[current_idx..]); + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 2); + assert_eq!(test_sender.received_tcs.len(), 2); + let packet0 = &test_sender.received_tcs[0]; + assert_eq!(packet0, &SIMPLE_PACKET); + let packet1 = &test_sender.received_tcs[1]; + assert_eq!(packet1, &inverted_packet); + } + + #[test] + fn test_split_tail_packet_only() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - 1], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert_eq!(test_sender.received_tcs.len(), 0); + assert_eq!(next_read_idx, 0); + } + + fn generic_test_split_packet(cut_off: usize) { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; + assert!(cut_off < inverted_packet.len() + 1); + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + // Second packet + encoded_buf[current_idx] = 0; + let packet_start = current_idx; + current_idx += 1; + let encoded_len = encode(&inverted_packet, &mut encoded_buf[current_idx..]); + assert_eq!(encoded_len, 6); + current_idx += encoded_len; + // We cut off the sentinel byte, so we expecte the write index to be the length of the + // packet minus the sentinel byte plus the first sentinel byte. + let next_expected_write_idx = 1 + encoded_len - cut_off + 1; + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_write_idx = 0; + let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec(); + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - cut_off], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, next_expected_write_idx); + assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); + } + + #[test] + fn test_one_packet_and_split_tail_packet_0() { + generic_test_split_packet(1); + } + + #[test] + fn test_one_packet_and_split_tail_packet_1() { + generic_test_split_packet(2); + } + + #[test] + fn test_one_packet_and_split_tail_packet_2() { + generic_test_split_packet(3); + } + + #[test] + fn test_zero_at_end() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut next_write_idx = 0; + let mut current_idx = 0; + encoded_buf[current_idx] = 5; + current_idx += 1; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + encoded_buf[current_idx] = 0; + current_idx += 1; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, 1); + assert_eq!(encoded_buf[0], 0); + } + + #[test] + fn test_all_zeroes() { + let mut test_sender = TestSender::default(); + let mut all_zeroes: [u8; 5] = [0; 5]; + let mut next_write_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut all_zeroes, + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert!(test_sender.received_tcs.is_empty()); + assert_eq!(next_write_idx, 0); } } -- 2.43.0 From 13cacb0b53eeaf5a2b344cf1b1b4f108d269d771 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 Sep 2023 18:34:05 +0200 Subject: [PATCH 04/50] this should do the job --- satrs-core/src/hal/host/tcp_server.rs | 88 +++++++++++++++++++++------ 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs index 1b3bd7c..cfa3963 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -1,32 +1,68 @@ use alloc::boxed::Box; use alloc::vec; use cobs::decode_in_place; +use core::fmt::Display; +use std::io::Write; use std::net::ToSocketAddrs; use std::vec::Vec; use std::{io::Read, net::TcpListener}; +use thiserror::Error; use crate::tmtc::ReceivesTc; -pub struct TcpTcServer { - listener: TcpListener, - tc_receiver: Box>, - reader_vec: Vec, +pub trait TmPacketSource { + type Error; + fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result; } -impl TcpTcServer { +pub struct TcpTmtcServer { + listener: TcpListener, + tm_source: Box>, + tm_buffer: Vec, + tc_receiver: Box>, + tc_buffer: Vec, + num_received_tcs: u32, + num_sent_tms: u32, +} + +#[derive(Error, Debug)] +pub enum TcpTmtcError { + #[error("TM retrieval error: {0}")] + TmError(TmError), + #[error("TC retrieval error: {0}")] + TcError(TcError), + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} + +impl TcpTmtcServer { pub fn new( addr: A, - tc_receiver: Box>, - reader_vec_size: usize, + tm_buffer_size: usize, + tm_source: Box>, + tc_buffer_size: usize, + tc_receiver: Box>, ) -> Result { - Ok(TcpTcServer { + Ok(Self { listener: TcpListener::bind(addr)?, + tm_source, + tm_buffer: vec![0; tm_buffer_size], tc_receiver, - reader_vec: vec![0; reader_vec_size], + tc_buffer: vec![0; tc_buffer_size], + num_received_tcs: 0, + num_sent_tms: 0, }) } - pub fn handle_connections(&mut self) -> Result<(), std::io::Error> { + pub fn number_of_received_tcs(&self) -> u32 { + self.num_received_tcs + } + + pub fn number_of_sent_tms(&self) -> u32 { + self.num_sent_tms + } + + pub fn handle_connections(&mut self) -> Result<(), TcpTmtcError> { let mut current_write_idx; let mut next_write_idx = 0; for stream in self.listener.incoming() { @@ -34,27 +70,43 @@ impl TcpTcServer { next_write_idx = 0; let mut stream = stream?; loop { - let read_len = stream.read(&mut self.reader_vec[current_write_idx..])?; + let read_len = stream.read(&mut self.tc_buffer[current_write_idx..])?; if read_len > 0 { current_write_idx += read_len; - if current_write_idx == self.reader_vec.capacity() { + if current_write_idx == self.tc_buffer.capacity() { // Reader vec full, need to parse for packets. - let parse_result = parse_buffer_for_cobs_encoded_packets( - &mut self.reader_vec, + self.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut self.tc_buffer[..current_write_idx], self.tc_receiver.as_mut(), &mut next_write_idx, - ); + ) + .map_err(|e| TcpTmtcError::TcError(e))?; } + current_write_idx = next_write_idx; continue; } break; } if current_write_idx > 0 { - let parse_result = parse_buffer_for_cobs_encoded_packets( - &mut self.reader_vec[..current_write_idx], + self.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut self.tc_buffer[..current_write_idx], self.tc_receiver.as_mut(), &mut next_write_idx, - ); + ) + .map_err(|e| TcpTmtcError::TcError(e))?; + } + loop { + // Write TM until TM source is exhausted. For now, there is no limit for the amount + // of TM written this way. + let read_tm_len = self + .tm_source + .retrieve_packet(&mut self.tm_buffer) + .map_err(|e| TcpTmtcError::TmError(e))?; + if read_tm_len == 0 { + break; + } + self.num_sent_tms += 1; + stream.write_all(&self.tm_buffer[..read_tm_len])?; } } Ok(()) -- 2.43.0 From 3d6e33bc001a117062a97ee141635b575e88aac9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 Sep 2023 19:15:26 +0200 Subject: [PATCH 05/50] not sure if this is the best structure --- satrs-core/src/hal/host/mod.rs | 1 + satrs-core/src/hal/host/tcp_server.rs | 344 ++--------------- .../src/hal/host/tcp_spacepackets_server.rs | 0 .../src/hal/host/tcp_with_cobs_server.rs | 347 ++++++++++++++++++ satrs-core/src/tmtc/mod.rs | 7 + 5 files changed, 381 insertions(+), 318 deletions(-) create mode 100644 satrs-core/src/hal/host/tcp_spacepackets_server.rs create mode 100644 satrs-core/src/hal/host/tcp_with_cobs_server.rs diff --git a/satrs-core/src/hal/host/mod.rs b/satrs-core/src/hal/host/mod.rs index 051a2f7..dfd02c1 100644 --- a/satrs-core/src/hal/host/mod.rs +++ b/satrs-core/src/hal/host/mod.rs @@ -1,3 +1,4 @@ //! Helper modules intended to be used on hosts with a full [std] runtime +mod tcp_with_cobs_server; pub mod tcp_server; pub mod udp_server; diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs index cfa3963..16bcce4 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -1,29 +1,14 @@ -use alloc::boxed::Box; use alloc::vec; -use cobs::decode_in_place; +use alloc::{boxed::Box, vec::Vec}; +use std::net::SocketAddr; +use std::net::{TcpListener, ToSocketAddrs}; + +use crate::tmtc::{ReceivesTc, TmPacketSource}; use core::fmt::Display; -use std::io::Write; -use std::net::ToSocketAddrs; -use std::vec::Vec; -use std::{io::Read, net::TcpListener}; use thiserror::Error; -use crate::tmtc::ReceivesTc; - -pub trait TmPacketSource { - type Error; - fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result; -} - -pub struct TcpTmtcServer { - listener: TcpListener, - tm_source: Box>, - tm_buffer: Vec, - tc_receiver: Box>, - tc_buffer: Vec, - num_received_tcs: u32, - num_sent_tms: u32, -} +// Re-export the TMTC in COBS server. +pub use crate::hal::host::tcp_with_cobs_server::TcpTmtcInCobsServer; #[derive(Error, Debug)] pub enum TcpTmtcError { @@ -35,8 +20,25 @@ pub enum TcpTmtcError { Io(#[from] std::io::Error), } -impl TcpTmtcServer { - pub fn new( +/// Result of one connection attempt. Contains the client address if a connection was established, +/// in addition to the number of telecommands and telemetry packets exchanged. +#[derive(Debug, Default)] +pub struct ConnectionResult { + pub addr: Option, + pub num_received_tcs: u32, + pub num_sent_tms: u32, +} + +pub(crate) struct TcpTmtcServerBase { + pub (crate) listener: TcpListener, + pub (crate) tm_source: Box>, + pub (crate) tm_buffer: Vec, + pub (crate) tc_receiver: Box>, + pub (crate) tc_buffer: Vec, +} + +impl TcpTmtcServerBase { + pub(crate) fn new( addr: A, tm_buffer_size: usize, tm_source: Box>, @@ -49,300 +51,6 @@ impl TcpTmtcServer u32 { - self.num_received_tcs - } - - pub fn number_of_sent_tms(&self) -> u32 { - self.num_sent_tms - } - - pub fn handle_connections(&mut self) -> Result<(), TcpTmtcError> { - let mut current_write_idx; - let mut next_write_idx = 0; - for stream in self.listener.incoming() { - current_write_idx = next_write_idx; - next_write_idx = 0; - let mut stream = stream?; - loop { - let read_len = stream.read(&mut self.tc_buffer[current_write_idx..])?; - if read_len > 0 { - current_write_idx += read_len; - if current_write_idx == self.tc_buffer.capacity() { - // Reader vec full, need to parse for packets. - self.num_received_tcs += parse_buffer_for_cobs_encoded_packets( - &mut self.tc_buffer[..current_write_idx], - self.tc_receiver.as_mut(), - &mut next_write_idx, - ) - .map_err(|e| TcpTmtcError::TcError(e))?; - } - current_write_idx = next_write_idx; - continue; - } - break; - } - if current_write_idx > 0 { - self.num_received_tcs += parse_buffer_for_cobs_encoded_packets( - &mut self.tc_buffer[..current_write_idx], - self.tc_receiver.as_mut(), - &mut next_write_idx, - ) - .map_err(|e| TcpTmtcError::TcError(e))?; - } - loop { - // Write TM until TM source is exhausted. For now, there is no limit for the amount - // of TM written this way. - let read_tm_len = self - .tm_source - .retrieve_packet(&mut self.tm_buffer) - .map_err(|e| TcpTmtcError::TmError(e))?; - if read_tm_len == 0 { - break; - } - self.num_sent_tms += 1; - stream.write_all(&self.tm_buffer[..read_tm_len])?; - } - } - Ok(()) - } -} - -pub fn parse_buffer_for_cobs_encoded_packets( - buf: &mut [u8], - tc_receiver: &mut dyn ReceivesTc, - next_write_idx: &mut usize, -) -> Result { - let mut start_index_packet = 0; - let mut start_found = false; - let mut last_byte = false; - let mut packets_found = 0; - for i in 0..buf.len() { - if i == buf.len() - 1 { - last_byte = true; - } - if buf[i] == 0 { - if !start_found && !last_byte && buf[i + 1] == 0 { - // Special case: Consecutive sentinel values or all zeroes. - // Skip. - continue; - } - if start_found { - let decode_result = decode_in_place(&mut buf[start_index_packet..i]); - if let Ok(packet_len) = decode_result { - packets_found += 1; - tc_receiver - .pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?; - } - start_found = false; - } else { - start_index_packet = i + 1; - start_found = true; - } - } - } - // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. - if start_index_packet > 0 && start_found && packets_found > 0 { - let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); - first_seg[..last_seg.len()].copy_from_slice(last_seg); - *next_write_idx = last_seg.len(); - } - Ok(packets_found) -} - -#[cfg(test)] -mod tests { - use crate::tmtc::ReceivesTcCore; - use alloc::vec::Vec; - use cobs::encode; - - use super::parse_buffer_for_cobs_encoded_packets; - - const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; - - #[derive(Default)] - struct TestSender { - received_tcs: Vec>, - } - - impl ReceivesTcCore for TestSender { - type Error = (); - - fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { - self.received_tcs.push(tc_raw.to_vec()); - Ok(()) - } - } - - fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encoded_buf[*current_idx] = 0; - *current_idx += 1; - *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); - encoded_buf[*current_idx] = 0; - *current_idx += 1; - } - - #[test] - fn test_parsing_simple_packet() { - let mut test_sender = TestSender::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - let packet = &test_sender.received_tcs[0]; - assert_eq!(packet, &SIMPLE_PACKET); - } - - #[test] - fn test_parsing_consecutive_packets() { - let mut test_sender = TestSender::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - - let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; - // Second packet - encoded_buf[current_idx] = 0; - current_idx += 1; - current_idx += encode(&inverted_packet, &mut encoded_buf[current_idx..]); - encoded_buf[current_idx] = 0; - current_idx += 1; - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 2); - assert_eq!(test_sender.received_tcs.len(), 2); - let packet0 = &test_sender.received_tcs[0]; - assert_eq!(packet0, &SIMPLE_PACKET); - let packet1 = &test_sender.received_tcs[1]; - assert_eq!(packet1, &inverted_packet); - } - - #[test] - fn test_split_tail_packet_only() { - let mut test_sender = TestSender::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx - 1], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 0); - assert_eq!(test_sender.received_tcs.len(), 0); - assert_eq!(next_read_idx, 0); - } - - fn generic_test_split_packet(cut_off: usize) { - let mut test_sender = TestSender::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; - assert!(cut_off < inverted_packet.len() + 1); - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - // Second packet - encoded_buf[current_idx] = 0; - let packet_start = current_idx; - current_idx += 1; - let encoded_len = encode(&inverted_packet, &mut encoded_buf[current_idx..]); - assert_eq!(encoded_len, 6); - current_idx += encoded_len; - // We cut off the sentinel byte, so we expecte the write index to be the length of the - // packet minus the sentinel byte plus the first sentinel byte. - let next_expected_write_idx = 1 + encoded_len - cut_off + 1; - encoded_buf[current_idx] = 0; - current_idx += 1; - let mut next_write_idx = 0; - let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec(); - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx - cut_off], - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); - assert_eq!(next_write_idx, next_expected_write_idx); - assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); - } - - #[test] - fn test_one_packet_and_split_tail_packet_0() { - generic_test_split_packet(1); - } - - #[test] - fn test_one_packet_and_split_tail_packet_1() { - generic_test_split_packet(2); - } - - #[test] - fn test_one_packet_and_split_tail_packet_2() { - generic_test_split_packet(3); - } - - #[test] - fn test_zero_at_end() { - let mut test_sender = TestSender::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut next_write_idx = 0; - let mut current_idx = 0; - encoded_buf[current_idx] = 5; - current_idx += 1; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - encoded_buf[current_idx] = 0; - current_idx += 1; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); - assert_eq!(next_write_idx, 1); - assert_eq!(encoded_buf[0], 0); - } - - #[test] - fn test_all_zeroes() { - let mut test_sender = TestSender::default(); - let mut all_zeroes: [u8; 5] = [0; 5]; - let mut next_write_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut all_zeroes, - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 0); - assert!(test_sender.received_tcs.is_empty()); - assert_eq!(next_write_idx, 0); - } } diff --git a/satrs-core/src/hal/host/tcp_spacepackets_server.rs b/satrs-core/src/hal/host/tcp_spacepackets_server.rs new file mode 100644 index 0000000..e69de29 diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/host/tcp_with_cobs_server.rs new file mode 100644 index 0000000..6c72254 --- /dev/null +++ b/satrs-core/src/hal/host/tcp_with_cobs_server.rs @@ -0,0 +1,347 @@ +use alloc::boxed::Box; +use alloc::vec; +use cobs::decode_in_place; +use cobs::encode; +use cobs::max_encoding_length; +use core::fmt::Display; +use std::io::Read; +use std::io::Write; +use std::net::ToSocketAddrs; +use std::vec::Vec; + +use crate::tmtc::ReceivesTc; +use crate::tmtc::TmPacketSource; +use crate::hal::host::tcp_server::TcpTmtcServerBase; + +use super::tcp_server::ConnectionResult; +use super::tcp_server::TcpTmtcError; + +/// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the +/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). +/// +/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data +/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full +/// packets even from a data stream which is split up. +pub struct TcpTmtcInCobsServer { + base: TcpTmtcServerBase, + tm_encoding_buffer: Vec, +} + +impl TcpTmtcInCobsServer { + pub fn new( + addr: A, + tm_buffer_size: usize, + tm_source: Box>, + tc_buffer_size: usize, + tc_receiver: Box>, + ) -> Result { + Ok(Self { + base: TcpTmtcServerBase::new( + addr, + tm_buffer_size, + tm_source, + tc_buffer_size, + tc_receiver, + )?, + tm_encoding_buffer: vec![0; max_encoding_length(tc_buffer_size)], + }) + } + + pub fn handle_next_connection( + &mut self, + ) -> Result> { + let mut connection_result = ConnectionResult::default(); + let mut current_write_idx; + let mut next_write_idx = 0; + let (mut stream, addr) = self.base.listener.accept()?; + connection_result.addr = Some(addr); + current_write_idx = next_write_idx; + next_write_idx = 0; + loop { + let read_len = stream.read(&mut self.base.tc_buffer[current_write_idx..])?; + if read_len > 0 { + current_write_idx += read_len; + if current_write_idx == self.base.tc_buffer.capacity() { + // Reader vec full, need to parse for packets. + connection_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut self.base.tc_buffer[..current_write_idx], + self.base.tc_receiver.as_mut(), + &mut next_write_idx, + ) + .map_err(|e| TcpTmtcError::TcError(e))?; + } + current_write_idx = next_write_idx; + continue; + } + break; + } + if current_write_idx > 0 { + connection_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut self.base.tc_buffer[..current_write_idx], + self.base.tc_receiver.as_mut(), + &mut next_write_idx, + ) + .map_err(|e| TcpTmtcError::TcError(e))?; + } + loop { + // Write TM until TM source is exhausted. For now, there is no limit for the amount + // of TM written this way. + let read_tm_len = self + .base + .tm_source + .retrieve_packet(&mut self.base.tm_buffer) + .map_err(|e| TcpTmtcError::TmError(e))?; + if read_tm_len == 0 { + break; + } + connection_result.num_sent_tms += 1; + + // Encode into COBS and sent to client. + let mut current_idx = 0; + self.tm_encoding_buffer[current_idx] = 0; + current_idx += 1; + current_idx += encode( + &self.base.tm_buffer[..read_tm_len], + &mut self.tm_encoding_buffer, + ); + self.tm_encoding_buffer[current_idx] = 0; + current_idx += 1; + stream.write_all(&self.tm_encoding_buffer[..current_idx])?; + } + Ok(connection_result) + } +} + +pub fn parse_buffer_for_cobs_encoded_packets( + buf: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let mut start_index_packet = 0; + let mut start_found = false; + let mut last_byte = false; + let mut packets_found = 0; + for i in 0..buf.len() { + if i == buf.len() - 1 { + last_byte = true; + } + if buf[i] == 0 { + if !start_found && !last_byte && buf[i + 1] == 0 { + // Special case: Consecutive sentinel values or all zeroes. + // Skip. + continue; + } + if start_found { + let decode_result = decode_in_place(&mut buf[start_index_packet..i]); + if let Ok(packet_len) = decode_result { + packets_found += 1; + tc_receiver + .pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?; + } + start_found = false; + } else { + start_index_packet = i + 1; + start_found = true; + } + } + } + // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. + if start_index_packet > 0 && start_found && packets_found > 0 { + let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); + first_seg[..last_seg.len()].copy_from_slice(last_seg); + *next_write_idx = last_seg.len(); + } + Ok(packets_found) +} + +#[cfg(test)] +mod tests { + use crate::tmtc::ReceivesTcCore; + use alloc::vec::Vec; + use cobs::encode; + + use super::parse_buffer_for_cobs_encoded_packets; + + const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + + #[derive(Default)] + struct TestSender { + received_tcs: Vec>, + } + + impl ReceivesTcCore for TestSender { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + self.received_tcs.push(tc_raw.to_vec()); + Ok(()) + } + } + + fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; + } + + #[test] + fn test_parsing_simple_packet() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + let packet = &test_sender.received_tcs[0]; + assert_eq!(packet, &SIMPLE_PACKET); + } + + #[test] + fn test_parsing_consecutive_packets() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + + let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; + // Second packet + encoded_buf[current_idx] = 0; + current_idx += 1; + current_idx += encode(&inverted_packet, &mut encoded_buf[current_idx..]); + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 2); + assert_eq!(test_sender.received_tcs.len(), 2); + let packet0 = &test_sender.received_tcs[0]; + assert_eq!(packet0, &SIMPLE_PACKET); + let packet1 = &test_sender.received_tcs[1]; + assert_eq!(packet1, &inverted_packet); + } + + #[test] + fn test_split_tail_packet_only() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - 1], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert_eq!(test_sender.received_tcs.len(), 0); + assert_eq!(next_read_idx, 0); + } + + fn generic_test_split_packet(cut_off: usize) { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; + assert!(cut_off < inverted_packet.len() + 1); + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + // Second packet + encoded_buf[current_idx] = 0; + let packet_start = current_idx; + current_idx += 1; + let encoded_len = encode(&inverted_packet, &mut encoded_buf[current_idx..]); + assert_eq!(encoded_len, 6); + current_idx += encoded_len; + // We cut off the sentinel byte, so we expecte the write index to be the length of the + // packet minus the sentinel byte plus the first sentinel byte. + let next_expected_write_idx = 1 + encoded_len - cut_off + 1; + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_write_idx = 0; + let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec(); + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - cut_off], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, next_expected_write_idx); + assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); + } + + #[test] + fn test_one_packet_and_split_tail_packet_0() { + generic_test_split_packet(1); + } + + #[test] + fn test_one_packet_and_split_tail_packet_1() { + generic_test_split_packet(2); + } + + #[test] + fn test_one_packet_and_split_tail_packet_2() { + generic_test_split_packet(3); + } + + #[test] + fn test_zero_at_end() { + let mut test_sender = TestSender::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut next_write_idx = 0; + let mut current_idx = 0; + encoded_buf[current_idx] = 5; + current_idx += 1; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + encoded_buf[current_idx] = 0; + current_idx += 1; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.received_tcs.len(), 1); + assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, 1); + assert_eq!(encoded_buf[0], 0); + } + + #[test] + fn test_all_zeroes() { + let mut test_sender = TestSender::default(); + let mut all_zeroes: [u8; 5] = [0; 5]; + let mut next_write_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut all_zeroes, + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert!(test_sender.received_tcs.is_empty()); + assert_eq!(next_write_idx, 0); + } +} diff --git a/satrs-core/src/tmtc/mod.rs b/satrs-core/src/tmtc/mod.rs index 7237196..2f8bdf3 100644 --- a/satrs-core/src/tmtc/mod.rs +++ b/satrs-core/src/tmtc/mod.rs @@ -92,3 +92,10 @@ pub trait ReceivesCcsdsTc { type Error; fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error>; } + +/// Generic trait for a TM packet source, with no restrictions on the type of TM. +/// Implementors write the telemetry into the provided buffer and return the size of the telemetry. +pub trait TmPacketSource { + type Error; + fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result; +} -- 2.43.0 From 1af5601d633cdd37b9eb89d2cacec13502b58416 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 Sep 2023 19:22:12 +0200 Subject: [PATCH 06/50] looking good --- satrs-core/src/hal/host/mod.rs | 2 +- satrs-core/src/hal/host/tcp_server.rs | 14 ++++++++------ satrs-core/src/hal/host/tcp_with_cobs_server.rs | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/satrs-core/src/hal/host/mod.rs b/satrs-core/src/hal/host/mod.rs index dfd02c1..5e98061 100644 --- a/satrs-core/src/hal/host/mod.rs +++ b/satrs-core/src/hal/host/mod.rs @@ -1,4 +1,4 @@ //! Helper modules intended to be used on hosts with a full [std] runtime -mod tcp_with_cobs_server; pub mod tcp_server; +mod tcp_with_cobs_server; pub mod udp_server; diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs index 16bcce4..c518bad 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -8,7 +8,9 @@ use core::fmt::Display; use thiserror::Error; // Re-export the TMTC in COBS server. -pub use crate::hal::host::tcp_with_cobs_server::TcpTmtcInCobsServer; +pub use crate::hal::host::tcp_with_cobs_server::{ + parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer, +}; #[derive(Error, Debug)] pub enum TcpTmtcError { @@ -30,11 +32,11 @@ pub struct ConnectionResult { } pub(crate) struct TcpTmtcServerBase { - pub (crate) listener: TcpListener, - pub (crate) tm_source: Box>, - pub (crate) tm_buffer: Vec, - pub (crate) tc_receiver: Box>, - pub (crate) tc_buffer: Vec, + pub(crate) listener: TcpListener, + pub(crate) tm_source: Box>, + pub(crate) tm_buffer: Vec, + pub(crate) tc_receiver: Box>, + pub(crate) tc_buffer: Vec, } impl TcpTmtcServerBase { diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/host/tcp_with_cobs_server.rs index 6c72254..359eec3 100644 --- a/satrs-core/src/hal/host/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/host/tcp_with_cobs_server.rs @@ -9,9 +9,9 @@ use std::io::Write; use std::net::ToSocketAddrs; use std::vec::Vec; +use crate::hal::host::tcp_server::TcpTmtcServerBase; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; -use crate::hal::host::tcp_server::TcpTmtcServerBase; use super::tcp_server::ConnectionResult; use super::tcp_server::TcpTmtcError; @@ -22,6 +22,9 @@ use super::tcp_server::TcpTmtcError; /// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data /// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full /// packets even from a data stream which is split up. +/// +/// The server wil use the [parse_buffer_for_cobs_encoded_packets] function to parse for packets +/// and pass them to a generic TC receiver. pub struct TcpTmtcInCobsServer { base: TcpTmtcServerBase, tm_encoding_buffer: Vec, @@ -112,6 +115,16 @@ impl TcpTmtcInCobsServer } } +/// This function parses a given buffer for COBS encoded packets. The packet structure is +/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter. +/// +/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 +/// +/// This function is also able to deal with broken tail packets at the end. If broken tail +/// packets are detected, they are moved to the front of the buffer, and the write index for +/// future write operations will be written to the `next_write_idx` argument. +/// +/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. pub fn parse_buffer_for_cobs_encoded_packets( buf: &mut [u8], tc_receiver: &mut dyn ReceivesTc, -- 2.43.0 From eb5c755dd322e47802d418e32efdc234d7679f9d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 Sep 2023 20:29:05 +0200 Subject: [PATCH 07/50] doc improvements --- satrs-core/src/hal/host/tcp_with_cobs_server.rs | 1 + satrs-core/src/hal/host/udp_server.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/host/tcp_with_cobs_server.rs index 359eec3..539e491 100644 --- a/satrs-core/src/hal/host/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/host/tcp_with_cobs_server.rs @@ -25,6 +25,7 @@ use super::tcp_server::TcpTmtcError; /// /// The server wil use the [parse_buffer_for_cobs_encoded_packets] function to parse for packets /// and pass them to a generic TC receiver. +/// pub struct TcpTmtcInCobsServer { base: TcpTmtcServerBase, tm_encoding_buffer: Vec, diff --git a/satrs-core/src/hal/host/udp_server.rs b/satrs-core/src/hal/host/udp_server.rs index de5a3f0..3459212 100644 --- a/satrs-core/src/hal/host/udp_server.rs +++ b/satrs-core/src/hal/host/udp_server.rs @@ -6,7 +6,8 @@ use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; use std::vec; use std::vec::Vec; -/// This TC server helper can be used to receive raw PUS telecommands thorough a UDP interface. +/// This UDP server can be used to receive CCSDS space packet telecommands or any other telecommand +/// format. /// /// It caches all received telecomands into a vector. The maximum expected telecommand size should /// be declared upfront. This avoids dynamic allocation during run-time. The user can specify a TC -- 2.43.0 From 0e6d903942e65eedde3f453e100acab3c97ea8b3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 16:23:42 +0200 Subject: [PATCH 08/50] add unittest for whole TCP server --- satrs-core/src/hal/host/tcp_server.rs | 11 +- .../src/hal/host/tcp_with_cobs_server.rs | 109 +++++++++++++++--- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/host/tcp_server.rs index c518bad..11818c5 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/host/tcp_server.rs @@ -4,7 +4,6 @@ use std::net::SocketAddr; use std::net::{TcpListener, ToSocketAddrs}; use crate::tmtc::{ReceivesTc, TmPacketSource}; -use core::fmt::Display; use thiserror::Error; // Re-export the TMTC in COBS server. @@ -13,7 +12,7 @@ pub use crate::hal::host::tcp_with_cobs_server::{ }; #[derive(Error, Debug)] -pub enum TcpTmtcError { +pub enum TcpTmtcError { #[error("TM retrieval error: {0}")] TmError(TmError), #[error("TC retrieval error: {0}")] @@ -33,9 +32,9 @@ pub struct ConnectionResult { pub(crate) struct TcpTmtcServerBase { pub(crate) listener: TcpListener, - pub(crate) tm_source: Box>, + pub(crate) tm_source: Box + Send>, pub(crate) tm_buffer: Vec, - pub(crate) tc_receiver: Box>, + pub(crate) tc_receiver: Box + Send>, pub(crate) tc_buffer: Vec, } @@ -43,9 +42,9 @@ impl TcpTmtcServerBase { pub(crate) fn new( addr: A, tm_buffer_size: usize, - tm_source: Box>, + tm_source: Box + Send>, tc_buffer_size: usize, - tc_receiver: Box>, + tc_receiver: Box + Send>, ) -> Result { Ok(Self { listener: TcpListener::bind(addr)?, diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/host/tcp_with_cobs_server.rs index 539e491..0dcb1b6 100644 --- a/satrs-core/src/hal/host/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/host/tcp_with_cobs_server.rs @@ -3,7 +3,6 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use cobs::max_encoding_length; -use core::fmt::Display; use std::io::Read; use std::io::Write; use std::net::ToSocketAddrs; @@ -25,19 +24,18 @@ use super::tcp_server::TcpTmtcError; /// /// The server wil use the [parse_buffer_for_cobs_encoded_packets] function to parse for packets /// and pass them to a generic TC receiver. -/// pub struct TcpTmtcInCobsServer { base: TcpTmtcServerBase, tm_encoding_buffer: Vec, } -impl TcpTmtcInCobsServer { +impl TcpTmtcInCobsServer { pub fn new( addr: A, tm_buffer_size: usize, - tm_source: Box>, + tm_source: Box + Send>, tc_buffer_size: usize, - tc_receiver: Box>, + tc_receiver: Box + Send>, ) -> Result { Ok(Self { base: TcpTmtcServerBase::new( @@ -170,20 +168,31 @@ pub fn parse_buffer_for_cobs_encoded_packets( #[cfg(test)] mod tests { - use crate::tmtc::ReceivesTcCore; - use alloc::vec::Vec; + use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, + }; + use std::{ + io::Write, + net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, + sync::Mutex, + thread, + }; + + use crate::tmtc::{ReceivesTcCore, TmPacketSource}; + use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; use cobs::encode; - use super::parse_buffer_for_cobs_encoded_packets; + use super::{parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer}; const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; #[derive(Default)] - struct TestSender { + struct TestTcSender { received_tcs: Vec>, } - impl ReceivesTcCore for TestSender { + impl ReceivesTcCore for TestTcSender { type Error = (); fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { @@ -192,6 +201,32 @@ mod tests { } } + #[derive(Default, Clone)] + struct TmSource { + shared_tm_source: Arc>>>, + } + + impl TmSource { + fn new() -> Self { + Self { + shared_tm_source: Default::default(), + } + } + + fn add_tm(&mut self, tm: &[u8]) { + let mut shared_tm_source = self.shared_tm_source.lock().unwrap(); + shared_tm_source.push_back(tm.to_vec()); + } + } + + impl TmPacketSource for TmSource { + type Error = (); + + fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { + Ok(0) + } + } + fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { encoded_buf[*current_idx] = 0; *current_idx += 1; @@ -202,7 +237,7 @@ mod tests { #[test] fn test_parsing_simple_packet() { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); @@ -221,7 +256,7 @@ mod tests { #[test] fn test_parsing_consecutive_packets() { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); @@ -250,7 +285,7 @@ mod tests { #[test] fn test_split_tail_packet_only() { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); @@ -268,7 +303,7 @@ mod tests { } fn generic_test_split_packet(cut_off: usize) { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; assert!(cut_off < inverted_packet.len() + 1); @@ -319,7 +354,7 @@ mod tests { #[test] fn test_zero_at_end() { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut next_write_idx = 0; let mut current_idx = 0; @@ -344,7 +379,7 @@ mod tests { #[test] fn test_all_zeroes() { - let mut test_sender = TestSender::default(); + let mut test_sender = TestTcSender::default(); let mut all_zeroes: [u8; 5] = [0; 5]; let mut next_write_idx = 0; let packets = parse_buffer_for_cobs_encoded_packets( @@ -358,4 +393,46 @@ mod tests { assert!(test_sender.received_tcs.is_empty()); assert_eq!(next_write_idx, 0); } + + #[test] + fn test_server_basic() { + let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); + let tc_receiver = TestTcSender::default(); + let tm_source = TmSource::default(); + let mut tcp_server = TcpTmtcInCobsServer::new( + dest_addr, + 1024, + Box::new(tm_source), + 1024, + Box::new(tc_receiver), + ) + .expect("TCP server generation failed"); + let conn_handled: Arc = Default::default(); + let set_if_done = conn_handled.clone(); + // Call the connection handler in separate thread, does block. + thread::spawn(move || { + let result = tcp_server.handle_next_connection(); + if result.is_err() { + panic!("handling connection failed: {:?}", result.unwrap_err()); + } + set_if_done.store(true, Ordering::Relaxed); + }); + // Send TC to server now. + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); + stream + .write_all(&encoded_buf) + .expect("writing to TCP server failed"); + // A certain amount of time is allowed for the transaction to complete. + for _ in 0..3 { + if !conn_handled.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(1)); + } + } + if !conn_handled.load(Ordering::Relaxed) { + panic!("connection was not handled properly"); + } + } } -- 2.43.0 From e3043ce2d76dcbbde3373b3da4299d354066c723 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 21:24:01 +0200 Subject: [PATCH 09/50] this already looks very promising --- .../src/hal/host/tcp_with_cobs_server.rs | 257 ++++++++++++++---- 1 file changed, 198 insertions(+), 59 deletions(-) diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/host/tcp_with_cobs_server.rs index 0dcb1b6..b2d4720 100644 --- a/satrs-core/src/hal/host/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/host/tcp_with_cobs_server.rs @@ -18,18 +18,45 @@ use super::tcp_server::TcpTmtcError; /// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// +/// TCP is stream oriented, so a client can read available telemetry using [std::io::Read] as well. +/// To allow flexibly specifying the telemetry sent back to clients, a generic TM abstraction +/// in form of the [TmPacketSource] trait is used. Telemetry will be encoded with the COBS +/// protocol using [cobs::encode] in addition to being wrapped with the sentinel value 0 as the +/// packet delimiter as well before being sent back to the client. Please note that the server +/// will send as much data as it can retrieve from the [TmPacketSource] in its current +/// implementation. +/// /// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data /// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full -/// packets even from a data stream which is split up. -/// -/// The server wil use the [parse_buffer_for_cobs_encoded_packets] function to parse for packets -/// and pass them to a generic TC receiver. +/// packets even from a data stream which is split up. The server wil use the +/// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a +/// generic TC receiver. pub struct TcpTmtcInCobsServer { base: TcpTmtcServerBase, tm_encoding_buffer: Vec, } impl TcpTmtcInCobsServer { + /// Create a new TMTC server which exchanges TMTC packets encoded with + /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). + /// + /// ## Parameter + /// + /// * `addr` - Address of the TCP server. + /// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and + /// encoding of that data. This buffer should at large enough to hold the maximum expected + /// TM size in addition to the COBS encoding overhead. You can use + /// [cobs::max_encoding_length] to calculate this size. + /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are + /// then sent back to the client. + /// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from + /// the client. It is recommended to make this buffer larger to allow reading multiple + /// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should + /// at the very least be large enough to hold the maximum expected telecommand size in + /// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to + /// calculate this size. + /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded + /// to this TC receiver. pub fn new( addr: A, tm_buffer_size: usize, @@ -49,6 +76,15 @@ impl TcpTmtcInCobsServer { }) } + /// This call is used to handle the next connection to a client. Right now, it performs + /// the following steps: + /// + /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API + /// until a client connects. + /// 2. It reads all the telecommands from the client, which are expected to be COBS + /// encoded packets. + /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve + /// from the user specified [TmPacketSource] back to the client. pub fn handle_next_connection( &mut self, ) -> Result> { @@ -71,8 +107,8 @@ impl TcpTmtcInCobsServer { &mut next_write_idx, ) .map_err(|e| TcpTmtcError::TcError(e))?; + current_write_idx = next_write_idx; } - current_write_idx = next_write_idx; continue; } break; @@ -173,7 +209,7 @@ mod tests { time::Duration, }; use std::{ - io::Write, + io::{Read, Write}, net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, sync::Mutex, thread, @@ -186,43 +222,64 @@ mod tests { use super::{parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer}; const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; - #[derive(Default)] - struct TestTcSender { - received_tcs: Vec>, + #[derive(Default, Clone)] + struct SyncTcCacher { + tc_queue: Arc>>>, } - - impl ReceivesTcCore for TestTcSender { + impl ReceivesTcCore for SyncTcCacher { type Error = (); fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { - self.received_tcs.push(tc_raw.to_vec()); + let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed"); + tc_queue.push_back(tc_raw.to_vec()); + Ok(()) + } + } + + #[derive(Default)] + struct TcCacher { + tc_queue: VecDeque>, + } + + impl ReceivesTcCore for TcCacher { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + self.tc_queue.push_back(tc_raw.to_vec()); Ok(()) } } #[derive(Default, Clone)] - struct TmSource { - shared_tm_source: Arc>>>, + struct SyncTmSource { + tm_queue: Arc>>>, } - impl TmSource { - fn new() -> Self { - Self { - shared_tm_source: Default::default(), - } - } - - fn add_tm(&mut self, tm: &[u8]) { - let mut shared_tm_source = self.shared_tm_source.lock().unwrap(); - shared_tm_source.push_back(tm.to_vec()); + impl SyncTmSource { + pub(crate) fn add_tm(&mut self, tm: &[u8]) { + let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec"); + tm_queue.push_back(tm.to_vec()); } } - impl TmPacketSource for TmSource { + impl TmPacketSource for SyncTmSource { type Error = (); fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { + let tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); + if !tm_queue.is_empty() { + let next_vec = tm_queue.front().unwrap(); + if buffer.len() < next_vec.len() { + panic!( + "provided buffer too small, must be at least {} bytes", + next_vec.len() + ); + } + buffer[0..next_vec.len()].copy_from_slice(next_vec); + return Ok(next_vec.len()); + } Ok(0) } } @@ -237,7 +294,7 @@ mod tests { #[test] fn test_parsing_simple_packet() { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); @@ -249,23 +306,22 @@ mod tests { ) .unwrap(); assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - let packet = &test_sender.received_tcs[0]; + assert_eq!(test_sender.tc_queue.len(), 1); + let packet = &test_sender.tc_queue[0]; assert_eq!(packet, &SIMPLE_PACKET); } #[test] fn test_parsing_consecutive_packets() { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); - let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; // Second packet encoded_buf[current_idx] = 0; current_idx += 1; - current_idx += encode(&inverted_packet, &mut encoded_buf[current_idx..]); + current_idx += encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); encoded_buf[current_idx] = 0; current_idx += 1; let mut next_read_idx = 0; @@ -276,16 +332,16 @@ mod tests { ) .unwrap(); assert_eq!(packets, 2); - assert_eq!(test_sender.received_tcs.len(), 2); - let packet0 = &test_sender.received_tcs[0]; + assert_eq!(test_sender.tc_queue.len(), 2); + let packet0 = &test_sender.tc_queue[0]; assert_eq!(packet0, &SIMPLE_PACKET); - let packet1 = &test_sender.received_tcs[1]; - assert_eq!(packet1, &inverted_packet); + let packet1 = &test_sender.tc_queue[1]; + assert_eq!(packet1, &INVERTED_PACKET); } #[test] fn test_split_tail_packet_only() { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); @@ -298,22 +354,21 @@ mod tests { ) .unwrap(); assert_eq!(packets, 0); - assert_eq!(test_sender.received_tcs.len(), 0); + assert_eq!(test_sender.tc_queue.len(), 0); assert_eq!(next_read_idx, 0); } fn generic_test_split_packet(cut_off: usize) { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut encoded_buf: [u8; 16] = [0; 16]; - let inverted_packet: [u8; 5] = [5, 4, 3, 2, 1]; - assert!(cut_off < inverted_packet.len() + 1); + assert!(cut_off < INVERTED_PACKET.len() + 1); let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); // Second packet encoded_buf[current_idx] = 0; let packet_start = current_idx; current_idx += 1; - let encoded_len = encode(&inverted_packet, &mut encoded_buf[current_idx..]); + let encoded_len = encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); assert_eq!(encoded_len, 6); current_idx += encoded_len; // We cut off the sentinel byte, so we expecte the write index to be the length of the @@ -331,8 +386,8 @@ mod tests { ) .unwrap(); assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(test_sender.tc_queue.len(), 1); + assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); assert_eq!(next_write_idx, next_expected_write_idx); assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); } @@ -354,7 +409,7 @@ mod tests { #[test] fn test_zero_at_end() { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut encoded_buf: [u8; 16] = [0; 16]; let mut next_write_idx = 0; let mut current_idx = 0; @@ -371,15 +426,15 @@ mod tests { ) .unwrap(); assert_eq!(packets, 1); - assert_eq!(test_sender.received_tcs.len(), 1); - assert_eq!(&test_sender.received_tcs[0], &SIMPLE_PACKET); + assert_eq!(test_sender.tc_queue.len(), 1); + assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); assert_eq!(next_write_idx, 1); assert_eq!(encoded_buf[0], 0); } #[test] fn test_all_zeroes() { - let mut test_sender = TestTcSender::default(); + let mut test_sender = TcCacher::default(); let mut all_zeroes: [u8; 5] = [0; 5]; let mut next_write_idx = 0; let packets = parse_buffer_for_cobs_encoded_packets( @@ -390,23 +445,32 @@ mod tests { ) .unwrap(); assert_eq!(packets, 0); - assert!(test_sender.received_tcs.is_empty()); + assert!(test_sender.tc_queue.is_empty()); assert_eq!(next_write_idx, 0); } - #[test] - fn test_server_basic() { - let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); - let tc_receiver = TestTcSender::default(); - let tm_source = TmSource::default(); - let mut tcp_server = TcpTmtcInCobsServer::new( - dest_addr, + fn generic_tmtc_server( + addr: &SocketAddr, + tc_receiver: SyncTcCacher, + tm_source: SyncTmSource, + ) -> TcpTmtcInCobsServer<(), ()> { + TcpTmtcInCobsServer::new( + addr, 1024, Box::new(tm_source), 1024, - Box::new(tc_receiver), + Box::new(tc_receiver.clone()), ) - .expect("TCP server generation failed"); + .expect("TCP server generation failed") + } + + #[test] + fn test_server_basic_no_tm() { + let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); + let tc_receiver = SyncTcCacher::default(); + let tm_source = SyncTmSource::default(); + let mut tcp_server = + generic_tmtc_server(&dest_addr, tc_receiver.clone(), tm_source.clone()); let conn_handled: Arc = Default::default(); let set_if_done = conn_handled.clone(); // Call the connection handler in separate thread, does block. @@ -415,6 +479,9 @@ mod tests { if result.is_err() { panic!("handling connection failed: {:?}", result.unwrap_err()); } + let conn_result = result.unwrap(); + assert_eq!(conn_result.num_received_tcs, 1); + assert_eq!(conn_result.num_sent_tms, 0); set_if_done.store(true, Ordering::Relaxed); }); // Send TC to server now. @@ -423,16 +490,88 @@ mod tests { encode_simple_packet(&mut encoded_buf, &mut current_idx); let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); stream - .write_all(&encoded_buf) + .write_all(&encoded_buf[..current_idx]) .expect("writing to TCP server failed"); + drop(stream); // A certain amount of time is allowed for the transaction to complete. for _ in 0..3 { if !conn_handled.load(Ordering::Relaxed) { - thread::sleep(Duration::from_millis(1)); + thread::sleep(Duration::from_millis(5)); } } if !conn_handled.load(Ordering::Relaxed) { panic!("connection was not handled properly"); } + // Check that the packet was received and decoded successfully. + let mut tc_queue = tc_receiver + .tc_queue + .lock() + .expect("locking tc queue failed"); + assert_eq!(tc_queue.len(), 1); + assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); + drop(tc_queue); + } + + #[test] + fn test_server_basic_no_tm_multi_tc() {} + + #[test] + fn test_server_basic_with_tm() { + let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); + let tc_receiver = SyncTcCacher::default(); + let mut tm_source = SyncTmSource::default(); + tm_source.add_tm(&INVERTED_PACKET); + let mut tcp_server = + generic_tmtc_server(&dest_addr, tc_receiver.clone(), tm_source.clone()); + let conn_handled: Arc = Default::default(); + let set_if_done = conn_handled.clone(); + // Call the connection handler in separate thread, does block. + thread::spawn(move || { + let result = tcp_server.handle_next_connection(); + if result.is_err() { + panic!("handling connection failed: {:?}", result.unwrap_err()); + } + let conn_result = result.unwrap(); + assert_eq!(conn_result.num_received_tcs, 1); + assert_eq!(conn_result.num_sent_tms, 1); + set_if_done.store(true, Ordering::Relaxed); + }); + // Send TC to server now. + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); + stream + .write_all(&encoded_buf[..current_idx]) + .expect("writing to TCP server failed"); + let mut read_buf: [u8; 16] = [0; 16]; + let read_len = stream.read(&mut read_buf).expect("read failed"); + // 1 byte encoding overhead, 2 sentinel bytes. + assert_eq!(read_len, 8); + assert_eq!(read_buf[0], 0); + assert_eq!(read_buf[read_len - 1], 0); + let decoded_len = + cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); + assert_eq!(decoded_len, 5); + assert_eq!(&read_buf[..INVERTED_PACKET.len()], &INVERTED_PACKET); + + drop(stream); + // A certain amount of time is allowed for the transaction to complete. + for _ in 0..3 { + if !conn_handled.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(5)); + } + } + if !conn_handled.load(Ordering::Relaxed) { + panic!("connection was not handled properly"); + } + // Check that the packet was received and decoded successfully. + let mut tc_queue = tc_receiver + .tc_queue + .lock() + .expect("locking tc queue failed"); + assert_eq!(tc_queue.len(), 1); + assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); + drop(tc_queue); } } -- 2.43.0 From 51e31f70f77ccc91335dc5bfab3ce04413a4f0aa Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 21:28:22 +0200 Subject: [PATCH 10/50] renamed host module to std module --- satrs-core/src/hal/host/mod.rs | 4 ---- satrs-core/src/hal/mod.rs | 2 +- satrs-core/src/hal/std/mod.rs | 5 +++++ satrs-core/src/hal/{host => std}/tcp_server.rs | 3 ++- satrs-core/src/hal/{host => std}/tcp_spacepackets_server.rs | 0 satrs-core/src/hal/{host => std}/tcp_with_cobs_server.rs | 2 +- satrs-core/src/hal/{host => std}/udp_server.rs | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 satrs-core/src/hal/host/mod.rs create mode 100644 satrs-core/src/hal/std/mod.rs rename satrs-core/src/hal/{host => std}/tcp_server.rs (93%) rename satrs-core/src/hal/{host => std}/tcp_spacepackets_server.rs (100%) rename satrs-core/src/hal/{host => std}/tcp_with_cobs_server.rs (99%) rename satrs-core/src/hal/{host => std}/udp_server.rs (99%) diff --git a/satrs-core/src/hal/host/mod.rs b/satrs-core/src/hal/host/mod.rs deleted file mode 100644 index 5e98061..0000000 --- a/satrs-core/src/hal/host/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Helper modules intended to be used on hosts with a full [std] runtime -pub mod tcp_server; -mod tcp_with_cobs_server; -pub mod udp_server; diff --git a/satrs-core/src/hal/mod.rs b/satrs-core/src/hal/mod.rs index c422a72..b6ab984 100644 --- a/satrs-core/src/hal/mod.rs +++ b/satrs-core/src/hal/mod.rs @@ -1,4 +1,4 @@ //! # Hardware Abstraction Layer module #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -pub mod host; +pub mod std; diff --git a/satrs-core/src/hal/std/mod.rs b/satrs-core/src/hal/std/mod.rs new file mode 100644 index 0000000..7ac107b --- /dev/null +++ b/satrs-core/src/hal/std/mod.rs @@ -0,0 +1,5 @@ +//! Helper modules intended to be used on systems with a full [std] runtime. +pub mod tcp_server; +pub mod udp_server; + +mod tcp_with_cobs_server; diff --git a/satrs-core/src/hal/host/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs similarity index 93% rename from satrs-core/src/hal/host/tcp_server.rs rename to satrs-core/src/hal/std/tcp_server.rs index 11818c5..1195dd1 100644 --- a/satrs-core/src/hal/host/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -1,3 +1,4 @@ +//! Generic TCP TMTC servers with different TMTC format flavours. use alloc::vec; use alloc::{boxed::Box, vec::Vec}; use std::net::SocketAddr; @@ -7,7 +8,7 @@ use crate::tmtc::{ReceivesTc, TmPacketSource}; use thiserror::Error; // Re-export the TMTC in COBS server. -pub use crate::hal::host::tcp_with_cobs_server::{ +pub use crate::hal::std::tcp_with_cobs_server::{ parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer, }; diff --git a/satrs-core/src/hal/host/tcp_spacepackets_server.rs b/satrs-core/src/hal/std/tcp_spacepackets_server.rs similarity index 100% rename from satrs-core/src/hal/host/tcp_spacepackets_server.rs rename to satrs-core/src/hal/std/tcp_spacepackets_server.rs diff --git a/satrs-core/src/hal/host/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs similarity index 99% rename from satrs-core/src/hal/host/tcp_with_cobs_server.rs rename to satrs-core/src/hal/std/tcp_with_cobs_server.rs index b2d4720..c59367f 100644 --- a/satrs-core/src/hal/host/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -8,7 +8,7 @@ use std::io::Write; use std::net::ToSocketAddrs; use std::vec::Vec; -use crate::hal::host::tcp_server::TcpTmtcServerBase; +use crate::hal::std::tcp_server::TcpTmtcServerBase; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; diff --git a/satrs-core/src/hal/host/udp_server.rs b/satrs-core/src/hal/std/udp_server.rs similarity index 99% rename from satrs-core/src/hal/host/udp_server.rs rename to satrs-core/src/hal/std/udp_server.rs index 3459212..83ad6f2 100644 --- a/satrs-core/src/hal/host/udp_server.rs +++ b/satrs-core/src/hal/std/udp_server.rs @@ -1,4 +1,4 @@ -//! UDP server helper components +//! Generic UDP TC server. use crate::tmtc::{ReceivesTc, ReceivesTcCore}; use std::boxed::Box; use std::io::{Error, ErrorKind}; -- 2.43.0 From b5813f9c901cf501a567d3070f9050b8eb85cc1b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 21:36:28 +0200 Subject: [PATCH 11/50] lets see if this fixes issues --- satrs-core/src/hal/std/tcp_with_cobs_server.rs | 4 ++-- satrs-core/src/hal/std/udp_server.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index c59367f..066143a 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -466,7 +466,7 @@ mod tests { #[test] fn test_server_basic_no_tm() { - let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); + let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let tm_source = SyncTmSource::default(); let mut tcp_server = @@ -517,7 +517,7 @@ mod tests { #[test] fn test_server_basic_with_tm() { - let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777); + let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let mut tm_source = SyncTmSource::default(); tm_source.add_tm(&INVERTED_PACKET); diff --git a/satrs-core/src/hal/std/udp_server.rs b/satrs-core/src/hal/std/udp_server.rs index 83ad6f2..931da1c 100644 --- a/satrs-core/src/hal/std/udp_server.rs +++ b/satrs-core/src/hal/std/udp_server.rs @@ -52,9 +52,9 @@ use std::vec::Vec; /// .expect("Error sending PUS TC via UDP"); /// ``` /// -/// The [fsrc-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example) +/// The [satrs-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/satrs-example) /// server code also includes -/// [example code](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example/src/bin/obsw/tmtc.rs) +/// [example code](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/tmtc.rs#L67) /// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port /// and then forwards them to a generic CCSDS packet receiver. pub struct UdpTcServer { @@ -141,7 +141,7 @@ impl UdpTcServer { #[cfg(test)] mod tests { - use crate::hal::host::udp_server::{ReceiveResult, UdpTcServer}; + use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer}; use crate::tmtc::ReceivesTcCore; use spacepackets::ecss::tc::PusTcCreator; use spacepackets::ecss::SerializablePusPacket; -- 2.43.0 From 706dde51c4bfff58b10170a9595ca42573d76432 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 21:51:06 +0200 Subject: [PATCH 12/50] okay, some stuff still not working --- satrs-core/src/hal/std/tcp_server.rs | 4 ++++ satrs-core/src/hal/std/tcp_with_cobs_server.rs | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 1195dd1..ada56a9 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -55,4 +55,8 @@ impl TcpTmtcServerBase { tc_buffer: vec![0; tc_buffer_size], }) } + + pub (crate) fn local_addr(&self) -> std::io::Result { + self.listener.local_addr() + } } diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 066143a..bd26401 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -3,6 +3,7 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use cobs::max_encoding_length; +use std::net::SocketAddr; use std::io::Read; use std::io::Write; use std::net::ToSocketAddrs; @@ -76,6 +77,12 @@ impl TcpTmtcInCobsServer { }) } + /// Can be used to retrieve the local assigned address of the TCP server. This is especially + /// useful if using the port number 0 for OS auto-assignment. + pub fn local_addr(&self) -> std::io::Result { + self.base.local_addr() + } + /// This call is used to handle the next connection to a client. Right now, it performs /// the following steps: /// @@ -466,11 +473,12 @@ mod tests { #[test] fn test_server_basic_no_tm() { - let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let tm_source = SyncTmSource::default(); let mut tcp_server = - generic_tmtc_server(&dest_addr, tc_receiver.clone(), tm_source.clone()); + generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); + let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); let conn_handled: Arc = Default::default(); let set_if_done = conn_handled.clone(); // Call the connection handler in separate thread, does block. @@ -517,12 +525,13 @@ mod tests { #[test] fn test_server_basic_with_tm() { - let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let mut tm_source = SyncTmSource::default(); tm_source.add_tm(&INVERTED_PACKET); let mut tcp_server = - generic_tmtc_server(&dest_addr, tc_receiver.clone(), tm_source.clone()); + generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); + let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); let conn_handled: Arc = Default::default(); let set_if_done = conn_handled.clone(); // Call the connection handler in separate thread, does block. -- 2.43.0 From d582ce212efb4e8b0fa9b8b76fc4cc251a4ab9df Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 16 Sep 2023 22:19:48 +0200 Subject: [PATCH 13/50] might require some more tweaks.. --- satrs-core/src/hal/std/tcp_server.rs | 4 ++-- .../src/hal/std/tcp_with_cobs_server.rs | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index ada56a9..f8cd16a 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -55,8 +55,8 @@ impl TcpTmtcServerBase { tc_buffer: vec![0; tc_buffer_size], }) } - - pub (crate) fn local_addr(&self) -> std::io::Result { + + pub(crate) fn local_addr(&self) -> std::io::Result { self.listener.local_addr() } } diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index bd26401..0d1248c 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -3,9 +3,9 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use cobs::max_encoding_length; -use std::net::SocketAddr; use std::io::Read; use std::io::Write; +use std::net::SocketAddr; use std::net::ToSocketAddrs; use std::vec::Vec; @@ -147,7 +147,7 @@ impl TcpTmtcInCobsServer { current_idx += 1; current_idx += encode( &self.base.tm_buffer[..read_tm_len], - &mut self.tm_encoding_buffer, + &mut self.tm_encoding_buffer[current_idx..], ); self.tm_encoding_buffer[current_idx] = 0; current_idx += 1; @@ -275,7 +275,7 @@ mod tests { type Error = (); fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { - let tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); + let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); if !tm_queue.is_empty() { let next_vec = tm_queue.front().unwrap(); if buffer.len() < next_vec.len() { @@ -284,7 +284,8 @@ mod tests { next_vec.len() ); } - buffer[0..next_vec.len()].copy_from_slice(next_vec); + let next_vec = tm_queue.pop_front().unwrap(); + buffer[0..next_vec.len()].copy_from_slice(&next_vec); return Ok(next_vec.len()); } Ok(0) @@ -478,7 +479,9 @@ mod tests { let tm_source = SyncTmSource::default(); let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); - let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); + let dest_addr = tcp_server + .local_addr() + .expect("retrieving dest addr failed"); let conn_handled: Arc = Default::default(); let set_if_done = conn_handled.clone(); // Call the connection handler in separate thread, does block. @@ -531,7 +534,9 @@ mod tests { tm_source.add_tm(&INVERTED_PACKET); let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); - let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); + let dest_addr = tcp_server + .local_addr() + .expect("retrieving dest addr failed"); let conn_handled: Arc = Default::default(); let set_if_done = conn_handled.clone(); // Call the connection handler in separate thread, does block. @@ -553,6 +558,10 @@ mod tests { stream .write_all(&encoded_buf[..current_idx]) .expect("writing to TCP server failed"); + // Done with writing. + stream + .shutdown(std::net::Shutdown::Write) + .expect("shutting down write failed"); let mut read_buf: [u8; 16] = [0; 16]; let read_len = stream.read(&mut read_buf).expect("read failed"); // 1 byte encoding overhead, 2 sentinel bytes. @@ -562,7 +571,8 @@ mod tests { let decoded_len = cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); assert_eq!(decoded_len, 5); - assert_eq!(&read_buf[..INVERTED_PACKET.len()], &INVERTED_PACKET); + // Skip first sentinel byte. + assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); drop(stream); // A certain amount of time is allowed for the transaction to complete. -- 2.43.0 From de690b3eede0186a9877825c913f4412eb64416f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 17 Sep 2023 01:32:18 +0200 Subject: [PATCH 14/50] some more improvements --- satrs-core/Cargo.toml | 8 ++++++- satrs-core/src/hal/std/tcp_server.rs | 22 +++++++++++++++---- .../src/hal/std/tcp_with_cobs_server.rs | 21 +++++++++++++++--- satrs-core/src/hal/std/udp_server.rs | 2 +- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 7b6697f..9ee7cc1 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -60,6 +60,11 @@ version = "1" default-features = false optional = true +[dependencies.socket2] +version = "0.5.4" +features = ["all"] +optional = true + [dependencies.spacepackets] version = "0.7.0-beta.1" # path = "../../spacepackets" @@ -93,7 +98,8 @@ std = [ "serde/std", "spacepackets/std", "num_enum/std", - "thiserror" + "thiserror", + "socket2" ] alloc = [ "serde/alloc", diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index f8cd16a..c98ab9d 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -1,8 +1,9 @@ //! Generic TCP TMTC servers with different TMTC format flavours. use alloc::vec; use alloc::{boxed::Box, vec::Vec}; +use socket2::{Domain, Socket, Type}; use std::net::SocketAddr; -use std::net::{TcpListener, ToSocketAddrs}; +use std::net::TcpListener; use crate::tmtc::{ReceivesTc, TmPacketSource}; use thiserror::Error; @@ -40,15 +41,24 @@ pub(crate) struct TcpTmtcServerBase { } impl TcpTmtcServerBase { - pub(crate) fn new( - addr: A, + pub(crate) fn new( + addr: &SocketAddr, + reuse_addr: bool, + reuse_port: bool, tm_buffer_size: usize, tm_source: Box + Send>, tc_buffer_size: usize, tc_receiver: Box + Send>, ) -> Result { + // Create a TCP listener bound to two addresses. + let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; + socket.set_reuse_address(reuse_addr)?; + socket.set_reuse_port(reuse_port)?; + let addr = (*addr).into(); + socket.bind(&addr)?; + socket.listen(128)?; Ok(Self { - listener: TcpListener::bind(addr)?, + listener: socket.into(), tm_source, tm_buffer: vec![0; tm_buffer_size], tc_receiver, @@ -56,6 +66,10 @@ impl TcpTmtcServerBase { }) } + pub(crate) fn listener(&mut self) -> &mut TcpListener { + &mut self.listener + } + pub(crate) fn local_addr(&self) -> std::io::Result { self.listener.local_addr() } diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 0d1248c..605595f 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -6,7 +6,7 @@ use cobs::max_encoding_length; use std::io::Read; use std::io::Write; use std::net::SocketAddr; -use std::net::ToSocketAddrs; +use std::net::TcpListener; use std::vec::Vec; use crate::hal::std::tcp_server::TcpTmtcServerBase; @@ -44,6 +44,10 @@ impl TcpTmtcInCobsServer { /// ## Parameter /// /// * `addr` - Address of the TCP server. + /// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is + /// especially useful if the address and port are static for the server. + /// * `reuse_port` - Can be used to set the `SO_REUSEPORT` option on the raw socket. This is + /// especially useful if the address and port are static for the server. /// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and /// encoding of that data. This buffer should at large enough to hold the maximum expected /// TM size in addition to the COBS encoding overhead. You can use @@ -58,8 +62,10 @@ impl TcpTmtcInCobsServer { /// calculate this size. /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded /// to this TC receiver. - pub fn new( - addr: A, + pub fn new( + addr: &SocketAddr, + reuse_addr: bool, + reuse_port: bool, tm_buffer_size: usize, tm_source: Box + Send>, tc_buffer_size: usize, @@ -68,6 +74,8 @@ impl TcpTmtcInCobsServer { Ok(Self { base: TcpTmtcServerBase::new( addr, + reuse_addr, + reuse_port, tm_buffer_size, tm_source, tc_buffer_size, @@ -77,6 +85,11 @@ impl TcpTmtcInCobsServer { }) } + /// Retrieve the internal [TcpListener] class. + pub fn listener(&mut self) -> &mut TcpListener { + self.base.listener() + } + /// Can be used to retrieve the local assigned address of the TCP server. This is especially /// useful if using the port number 0 for OS auto-assignment. pub fn local_addr(&self) -> std::io::Result { @@ -464,6 +477,8 @@ mod tests { ) -> TcpTmtcInCobsServer<(), ()> { TcpTmtcInCobsServer::new( addr, + false, + false, 1024, Box::new(tm_source), 1024, diff --git a/satrs-core/src/hal/std/udp_server.rs b/satrs-core/src/hal/std/udp_server.rs index 931da1c..28e0328 100644 --- a/satrs-core/src/hal/std/udp_server.rs +++ b/satrs-core/src/hal/std/udp_server.rs @@ -20,7 +20,7 @@ use std::vec::Vec; /// ``` /// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; /// use spacepackets::ecss::SerializablePusPacket; -/// use satrs_core::hal::host::udp_server::UdpTcServer; +/// use satrs_core::hal::std::udp_server::UdpTcServer; /// use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore}; /// use spacepackets::SpHeader; /// use spacepackets::ecss::tc::PusTcCreator; -- 2.43.0 From 8d8e319aee9746adf04c7900460f232a6e0480ae Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 17 Sep 2023 02:31:02 +0200 Subject: [PATCH 15/50] neat --- satrs-core/src/hal/std/tcp_server.rs | 4 + .../src/hal/std/tcp_with_cobs_server.rs | 201 +++++++++++++----- 2 files changed, 147 insertions(+), 58 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index c98ab9d..11dc9e1 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -1,6 +1,7 @@ //! Generic TCP TMTC servers with different TMTC format flavours. use alloc::vec; use alloc::{boxed::Box, vec::Vec}; +use core::time::Duration; use socket2::{Domain, Socket, Type}; use std::net::SocketAddr; use std::net::TcpListener; @@ -34,6 +35,7 @@ pub struct ConnectionResult { pub(crate) struct TcpTmtcServerBase { pub(crate) listener: TcpListener, + pub(crate) inner_loop_delay: Duration, pub(crate) tm_source: Box + Send>, pub(crate) tm_buffer: Vec, pub(crate) tc_receiver: Box + Send>, @@ -43,6 +45,7 @@ pub(crate) struct TcpTmtcServerBase { impl TcpTmtcServerBase { pub(crate) fn new( addr: &SocketAddr, + inner_loop_delay: Duration, reuse_addr: bool, reuse_port: bool, tm_buffer_size: usize, @@ -59,6 +62,7 @@ impl TcpTmtcServerBase { socket.listen(128)?; Ok(Self { listener: socket.into(), + inner_loop_delay, tm_source, tm_buffer: vec![0; tm_buffer_size], tc_receiver, diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 605595f..983e57c 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -3,10 +3,14 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use cobs::max_encoding_length; +use core::time::Duration; use std::io::Read; use std::io::Write; use std::net::SocketAddr; use std::net::TcpListener; +use std::net::TcpStream; +use std::println; +use std::thread; use std::vec::Vec; use crate::hal::std::tcp_server::TcpTmtcServerBase; @@ -16,6 +20,58 @@ use crate::tmtc::TmPacketSource; use super::tcp_server::ConnectionResult; use super::tcp_server::TcpTmtcError; +/// TCP configuration struct. +/// +/// ## Parameters +/// +/// * `addr` - Address of the TCP server. +/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or +/// no TM needs to be sent, the TCP server will delay for the specified amount of time +/// to reduce CPU load. +/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and +/// encoding of that data. This buffer should at large enough to hold the maximum expected +/// TM size in addition to the COBS encoding overhead. You can use +/// [cobs::max_encoding_length] to calculate this size. +/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from +/// the client. It is recommended to make this buffer larger to allow reading multiple +/// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should +/// at the very least be large enough to hold the maximum expected telecommand size in +/// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to +/// calculate this size. +/// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is +/// especially useful if the address and port are static for the server. Set to false by +/// default. +/// * `reuse_port` - Can be used to set the `SO_REUSEPORT` option on the raw socket. This is +/// especially useful if the address and port are static for the server. Set to false by +/// default. +#[derive(Debug, Copy, Clone)] +pub struct ServerConfig { + pub addr: SocketAddr, + pub inner_loop_delay: Duration, + pub tm_buffer_size: usize, + pub tc_buffer_size: usize, + pub reuse_addr: bool, + pub reuse_port: bool, +} + +impl ServerConfig { + pub fn new( + addr: SocketAddr, + inner_loop_delay: Duration, + tm_buffer_size: usize, + tc_buffer_size: usize, + ) -> Self { + Self { + addr, + inner_loop_delay, + tm_buffer_size, + tc_buffer_size, + reuse_addr: false, + reuse_port: false, + } + } +} + /// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// @@ -43,45 +99,27 @@ impl TcpTmtcInCobsServer { /// /// ## Parameter /// - /// * `addr` - Address of the TCP server. - /// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is - /// especially useful if the address and port are static for the server. - /// * `reuse_port` - Can be used to set the `SO_REUSEPORT` option on the raw socket. This is - /// especially useful if the address and port are static for the server. - /// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and - /// encoding of that data. This buffer should at large enough to hold the maximum expected - /// TM size in addition to the COBS encoding overhead. You can use - /// [cobs::max_encoding_length] to calculate this size. /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are /// then sent back to the client. - /// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from - /// the client. It is recommended to make this buffer larger to allow reading multiple - /// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should - /// at the very least be large enough to hold the maximum expected telecommand size in - /// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to - /// calculate this size. /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded /// to this TC receiver. pub fn new( - addr: &SocketAddr, - reuse_addr: bool, - reuse_port: bool, - tm_buffer_size: usize, + cfg: ServerConfig, tm_source: Box + Send>, - tc_buffer_size: usize, tc_receiver: Box + Send>, ) -> Result { Ok(Self { base: TcpTmtcServerBase::new( - addr, - reuse_addr, - reuse_port, - tm_buffer_size, + &cfg.addr, + cfg.inner_loop_delay, + cfg.reuse_addr, + cfg.reuse_port, + cfg.tm_buffer_size, tm_source, - tc_buffer_size, + cfg.tc_buffer_size, tc_receiver, )?, - tm_encoding_buffer: vec![0; max_encoding_length(tc_buffer_size)], + tm_encoding_buffer: vec![0; max_encoding_length(cfg.tc_buffer_size)], }) } @@ -112,35 +150,85 @@ impl TcpTmtcInCobsServer { let mut current_write_idx; let mut next_write_idx = 0; let (mut stream, addr) = self.base.listener.accept()?; + stream.set_nonblocking(true)?; connection_result.addr = Some(addr); current_write_idx = next_write_idx; - next_write_idx = 0; loop { - let read_len = stream.read(&mut self.base.tc_buffer[current_write_idx..])?; - if read_len > 0 { - current_write_idx += read_len; - if current_write_idx == self.base.tc_buffer.capacity() { - // Reader vec full, need to parse for packets. - connection_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( - &mut self.base.tc_buffer[..current_write_idx], - self.base.tc_receiver.as_mut(), - &mut next_write_idx, - ) - .map_err(|e| TcpTmtcError::TcError(e))?; - current_write_idx = next_write_idx; + let read_result = stream.read(&mut self.base.tc_buffer[current_write_idx..]); + match read_result { + Ok(0) => { + // Connection closed by client. If any TC was read, parse for complete packets. + // After that, break the outer loop. + if current_write_idx > 0 { + self.handle_tc_parsing( + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + } + break; } - continue; + Ok(read_len) => { + current_write_idx += read_len; + // TC buffer is full, we must parse for complete packets now. + if current_write_idx == self.base.tc_buffer.capacity() { + self.handle_tc_parsing( + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + current_write_idx = next_write_idx; + } + } + Err(e) => match e.kind() { + // As per [TcpStream::set_read_timeout] documentation, this should work for + // both UNIX and Windows. + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => { + println!("should be here.."); + self.handle_tc_parsing( + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + current_write_idx = next_write_idx; + if !self.handle_tm_sending(&mut connection_result, &mut stream)? { + // No TC read, no TM was sent, but the client has not disconnected. + // Perform an inner delay to avoid burning CPU time. + thread::sleep(self.base.inner_loop_delay); + } + } + _ => { + return Err(TcpTmtcError::Io(e)); + } + }, } - break; - } - if current_write_idx > 0 { - connection_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( - &mut self.base.tc_buffer[..current_write_idx], - self.base.tc_receiver.as_mut(), - &mut next_write_idx, - ) - .map_err(|e| TcpTmtcError::TcError(e))?; } + self.handle_tm_sending(&mut connection_result, &mut stream)?; + Ok(connection_result) + } + + fn handle_tc_parsing( + &mut self, + conn_result: &mut ConnectionResult, + current_write_idx: usize, + next_write_idx: &mut usize, + ) -> Result<(), TcpTmtcError> { + // Reader vec full, need to parse for packets. + conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut self.base.tc_buffer[..current_write_idx], + self.base.tc_receiver.as_mut(), + next_write_idx, + ) + .map_err(|e| TcpTmtcError::TcError(e))?; + Ok(()) + } + + fn handle_tm_sending( + &mut self, + conn_result: &mut ConnectionResult, + stream: &mut TcpStream, + ) -> Result> { + let mut tm_was_sent = false; loop { // Write TM until TM source is exhausted. For now, there is no limit for the amount // of TM written this way. @@ -149,10 +237,12 @@ impl TcpTmtcInCobsServer { .tm_source .retrieve_packet(&mut self.base.tm_buffer) .map_err(|e| TcpTmtcError::TmError(e))?; + if read_tm_len == 0 { - break; + return Ok(tm_was_sent); } - connection_result.num_sent_tms += 1; + tm_was_sent = true; + conn_result.num_sent_tms += 1; // Encode into COBS and sent to client. let mut current_idx = 0; @@ -166,7 +256,6 @@ impl TcpTmtcInCobsServer { current_idx += 1; stream.write_all(&self.tm_encoding_buffer[..current_idx])?; } - Ok(connection_result) } } @@ -239,7 +328,7 @@ mod tests { use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; use cobs::encode; - use super::{parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer}; + use super::{parse_buffer_for_cobs_encoded_packets, ServerConfig, TcpTmtcInCobsServer}; const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; @@ -476,12 +565,8 @@ mod tests { tm_source: SyncTmSource, ) -> TcpTmtcInCobsServer<(), ()> { TcpTmtcInCobsServer::new( - addr, - false, - false, - 1024, + ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024), Box::new(tm_source), - 1024, Box::new(tc_receiver.clone()), ) .expect("TCP server generation failed") -- 2.43.0 From 8582d226ec8f4ecf4a20189d1abd81689c477698 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 17 Sep 2023 02:35:08 +0200 Subject: [PATCH 16/50] improve API docs --- satrs-core/src/hal/std/tcp_server.rs | 52 +++++++++++++++++ .../src/hal/std/tcp_with_cobs_server.rs | 56 +------------------ 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 11dc9e1..9d6cc81 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -14,6 +14,58 @@ pub use crate::hal::std::tcp_with_cobs_server::{ parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer, }; +/// TCP configuration struct. +/// +/// ## Parameters +/// +/// * `addr` - Address of the TCP server. +/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or +/// no TM needs to be sent, the TCP server will delay for the specified amount of time +/// to reduce CPU load. +/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and +/// encoding of that data. This buffer should at large enough to hold the maximum expected +/// TM size in addition to the COBS encoding overhead. You can use +/// [cobs::max_encoding_length] to calculate this size. +/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from +/// the client. It is recommended to make this buffer larger to allow reading multiple +/// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should +/// at the very least be large enough to hold the maximum expected telecommand size in +/// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to +/// calculate this size. +/// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is +/// especially useful if the address and port are static for the server. Set to false by +/// default. +/// * `reuse_port` - Can be used to set the `SO_REUSEPORT` option on the raw socket. This is +/// especially useful if the address and port are static for the server. Set to false by +/// default. +#[derive(Debug, Copy, Clone)] +pub struct ServerConfig { + pub addr: SocketAddr, + pub inner_loop_delay: Duration, + pub tm_buffer_size: usize, + pub tc_buffer_size: usize, + pub reuse_addr: bool, + pub reuse_port: bool, +} + +impl ServerConfig { + pub fn new( + addr: SocketAddr, + inner_loop_delay: Duration, + tm_buffer_size: usize, + tc_buffer_size: usize, + ) -> Self { + Self { + addr, + inner_loop_delay, + tm_buffer_size, + tc_buffer_size, + reuse_addr: false, + reuse_port: false, + } + } +} + #[derive(Error, Debug)] pub enum TcpTmtcError { #[error("TM retrieval error: {0}")] diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 983e57c..d7d3c57 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -3,7 +3,6 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use cobs::max_encoding_length; -use core::time::Duration; use std::io::Read; use std::io::Write; use std::net::SocketAddr; @@ -13,65 +12,13 @@ use std::println; use std::thread; use std::vec::Vec; -use crate::hal::std::tcp_server::TcpTmtcServerBase; +use crate::hal::std::tcp_server::{TcpTmtcServerBase, ServerConfig}; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; use super::tcp_server::ConnectionResult; use super::tcp_server::TcpTmtcError; -/// TCP configuration struct. -/// -/// ## Parameters -/// -/// * `addr` - Address of the TCP server. -/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or -/// no TM needs to be sent, the TCP server will delay for the specified amount of time -/// to reduce CPU load. -/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and -/// encoding of that data. This buffer should at large enough to hold the maximum expected -/// TM size in addition to the COBS encoding overhead. You can use -/// [cobs::max_encoding_length] to calculate this size. -/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from -/// the client. It is recommended to make this buffer larger to allow reading multiple -/// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should -/// at the very least be large enough to hold the maximum expected telecommand size in -/// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to -/// calculate this size. -/// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is -/// especially useful if the address and port are static for the server. Set to false by -/// default. -/// * `reuse_port` - Can be used to set the `SO_REUSEPORT` option on the raw socket. This is -/// especially useful if the address and port are static for the server. Set to false by -/// default. -#[derive(Debug, Copy, Clone)] -pub struct ServerConfig { - pub addr: SocketAddr, - pub inner_loop_delay: Duration, - pub tm_buffer_size: usize, - pub tc_buffer_size: usize, - pub reuse_addr: bool, - pub reuse_port: bool, -} - -impl ServerConfig { - pub fn new( - addr: SocketAddr, - inner_loop_delay: Duration, - tm_buffer_size: usize, - tc_buffer_size: usize, - ) -> Self { - Self { - addr, - inner_loop_delay, - tm_buffer_size, - tc_buffer_size, - reuse_addr: false, - reuse_port: false, - } - } -} - /// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// @@ -99,6 +46,7 @@ impl TcpTmtcInCobsServer { /// /// ## Parameter /// + /// * `cfg` - Configuration of the server. /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are /// then sent back to the client. /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded -- 2.43.0 From 079da20640dc064ade39146f81229d48962d73c6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:11:01 +0200 Subject: [PATCH 17/50] holy shit, this actually worked --- satrs-core/src/hal/std/tcp_server.rs | 23 +- .../src/hal/std/tcp_with_cobs_server.rs | 276 ++++++++++++------ 2 files changed, 203 insertions(+), 96 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 9d6cc81..49592c0 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -85,7 +85,7 @@ pub struct ConnectionResult { pub num_sent_tms: u32, } -pub(crate) struct TcpTmtcServerBase { +pub(crate) struct TcpTmtcServerBase { pub(crate) listener: TcpListener, pub(crate) inner_loop_delay: Duration, pub(crate) tm_source: Box + Send>, @@ -94,31 +94,26 @@ pub(crate) struct TcpTmtcServerBase { pub(crate) tc_buffer: Vec, } -impl TcpTmtcServerBase { +impl TcpTmtcServerBase { pub(crate) fn new( - addr: &SocketAddr, - inner_loop_delay: Duration, - reuse_addr: bool, - reuse_port: bool, - tm_buffer_size: usize, + cfg: ServerConfig, tm_source: Box + Send>, - tc_buffer_size: usize, tc_receiver: Box + Send>, ) -> Result { // Create a TCP listener bound to two addresses. let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; - socket.set_reuse_address(reuse_addr)?; - socket.set_reuse_port(reuse_port)?; - let addr = (*addr).into(); + socket.set_reuse_address(cfg.reuse_addr)?; + socket.set_reuse_port(cfg.reuse_port)?; + let addr = (cfg.addr).into(); socket.bind(&addr)?; socket.listen(128)?; Ok(Self { listener: socket.into(), - inner_loop_delay, + inner_loop_delay: cfg.inner_loop_delay, tm_source, - tm_buffer: vec![0; tm_buffer_size], + tm_buffer: vec![0; cfg.tm_buffer_size], tc_receiver, - tc_buffer: vec![0; tc_buffer_size], + tc_buffer: vec![0; cfg.tc_buffer_size], }) } diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index d7d3c57..3b1edbf 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -2,7 +2,7 @@ use alloc::boxed::Box; use alloc::vec; use cobs::decode_in_place; use cobs::encode; -use cobs::max_encoding_length; +use delegate::delegate; use std::io::Read; use std::io::Write; use std::net::SocketAddr; @@ -12,35 +12,126 @@ use std::println; use std::thread; use std::vec::Vec; -use crate::hal::std::tcp_server::{TcpTmtcServerBase, ServerConfig}; +use crate::hal::std::tcp_server::{ServerConfig, TcpTmtcServerBase}; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; use super::tcp_server::ConnectionResult; use super::tcp_server::TcpTmtcError; -/// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the -/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). -/// -/// TCP is stream oriented, so a client can read available telemetry using [std::io::Read] as well. -/// To allow flexibly specifying the telemetry sent back to clients, a generic TM abstraction -/// in form of the [TmPacketSource] trait is used. Telemetry will be encoded with the COBS -/// protocol using [cobs::encode] in addition to being wrapped with the sentinel value 0 as the -/// packet delimiter as well before being sent back to the client. Please note that the server -/// will send as much data as it can retrieve from the [TmPacketSource] in its current -/// implementation. -/// -/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data -/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full -/// packets even from a data stream which is split up. The server wil use the -/// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a -/// generic TC receiver. -pub struct TcpTmtcInCobsServer { - base: TcpTmtcServerBase, +pub trait TcpTcHandler { + fn handle_tc_parsing( + &mut self, + tc_buffer: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + conn_result: &mut ConnectionResult, + current_write_idx: usize, + next_write_idx: &mut usize, + ) -> Result<(), TcpTmtcError>; +} + +#[derive(Default)] +struct CobsTcParser {} + +impl TcpTcHandler for CobsTcParser { + fn handle_tc_parsing( + &mut self, + tc_buffer: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + conn_result: &mut ConnectionResult, + current_write_idx: usize, + next_write_idx: &mut usize, + ) -> Result<(), TcpTmtcError> { + // Reader vec full, need to parse for packets. + conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( + &mut tc_buffer[..current_write_idx], + tc_receiver, + next_write_idx, + ) + .map_err(|e| TcpTmtcError::TcError(e))?; + Ok(()) + } +} + +pub trait TcpTmHandler { + fn handle_tm_sending( + &mut self, + tm_buffer: &mut [u8], + tm_source: &mut dyn TmPacketSource, + conn_result: &mut ConnectionResult, + stream: &mut TcpStream, + ) -> Result>; +} + +struct CobsTmParser { tm_encoding_buffer: Vec, } -impl TcpTmtcInCobsServer { +impl CobsTmParser { + fn new(tm_buffer_size: usize) -> Self { + Self { + // The buffer should be large enough to hold the maximum expected TM size encoded with + // COBS. + tm_encoding_buffer: vec![0; cobs::max_encoding_length(tm_buffer_size)], + } + } +} + +impl TcpTmHandler for CobsTmParser { + fn handle_tm_sending( + &mut self, + tm_buffer: &mut [u8], + tm_source: &mut dyn TmPacketSource, + conn_result: &mut ConnectionResult, + stream: &mut TcpStream, + ) -> Result> { + let mut tm_was_sent = false; + loop { + // Write TM until TM source is exhausted. For now, there is no limit for the amount + // of TM written this way. + let read_tm_len = tm_source + .retrieve_packet(tm_buffer) + .map_err(|e| TcpTmtcError::TmError(e))?; + + if read_tm_len == 0 { + return Ok(tm_was_sent); + } + tm_was_sent = true; + conn_result.num_sent_tms += 1; + + // Encode into COBS and sent to client. + let mut current_idx = 0; + self.tm_encoding_buffer[current_idx] = 0; + current_idx += 1; + current_idx += encode( + &tm_buffer[..read_tm_len], + &mut self.tm_encoding_buffer[current_idx..], + ); + self.tm_encoding_buffer[current_idx] = 0; + current_idx += 1; + stream.write_all(&self.tm_encoding_buffer[..current_idx])?; + } + } +} + +pub struct TcpTmtcGenericServer< + TmError, + TcError, + TmHandler: TcpTmHandler, + TcHandler: TcpTcHandler, +> { + base: TcpTmtcServerBase, + tc_handler: TcHandler, + tm_handler: TmHandler, +} + +impl< + TmError: 'static, + TcError: 'static, + TmHandler: TcpTmHandler, + TcHandler: TcpTcHandler, + > TcpTmtcGenericServer +{ /// Create a new TMTC server which exchanges TMTC packets encoded with /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// @@ -53,21 +144,15 @@ impl TcpTmtcInCobsServer { /// to this TC receiver. pub fn new( cfg: ServerConfig, + tc_handler: TcHandler, + tm_handler: TmHandler, tm_source: Box + Send>, tc_receiver: Box + Send>, - ) -> Result { + ) -> Result, std::io::Error> { Ok(Self { - base: TcpTmtcServerBase::new( - &cfg.addr, - cfg.inner_loop_delay, - cfg.reuse_addr, - cfg.reuse_port, - cfg.tm_buffer_size, - tm_source, - cfg.tc_buffer_size, - tc_receiver, - )?, - tm_encoding_buffer: vec![0; max_encoding_length(cfg.tc_buffer_size)], + base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, + tc_handler, + tm_handler, // tmtc_handler: CobsTmtcParser::new(cfg.tm_buffer_size), }) } @@ -108,7 +193,9 @@ impl TcpTmtcInCobsServer { // Connection closed by client. If any TC was read, parse for complete packets. // After that, break the outer loop. if current_write_idx > 0 { - self.handle_tc_parsing( + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), &mut connection_result, current_write_idx, &mut next_write_idx, @@ -120,7 +207,9 @@ impl TcpTmtcInCobsServer { current_write_idx += read_len; // TC buffer is full, we must parse for complete packets now. if current_write_idx == self.base.tc_buffer.capacity() { - self.handle_tc_parsing( + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), &mut connection_result, current_write_idx, &mut next_write_idx, @@ -133,13 +222,21 @@ impl TcpTmtcInCobsServer { // both UNIX and Windows. std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => { println!("should be here.."); - self.handle_tc_parsing( + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), &mut connection_result, current_write_idx, &mut next_write_idx, )?; current_write_idx = next_write_idx; - if !self.handle_tm_sending(&mut connection_result, &mut stream)? { + + if !self.tm_handler.handle_tm_sending( + &mut self.base.tm_buffer, + self.base.tm_source.as_mut(), + &mut connection_result, + &mut stream, + )? { // No TC read, no TM was sent, but the client has not disconnected. // Perform an inner delay to avoid burning CPU time. thread::sleep(self.base.inner_loop_delay); @@ -151,58 +248,73 @@ impl TcpTmtcInCobsServer { }, } } - self.handle_tm_sending(&mut connection_result, &mut stream)?; + self.tm_handler.handle_tm_sending( + &mut self.base.tm_buffer, + self.base.tm_source.as_mut(), + &mut connection_result, + &mut stream, + )?; Ok(connection_result) } +} - fn handle_tc_parsing( - &mut self, - conn_result: &mut ConnectionResult, - current_write_idx: usize, - next_write_idx: &mut usize, - ) -> Result<(), TcpTmtcError> { - // Reader vec full, need to parse for packets. - conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( - &mut self.base.tc_buffer[..current_write_idx], - self.base.tc_receiver.as_mut(), - next_write_idx, - ) - .map_err(|e| TcpTmtcError::TcError(e))?; - Ok(()) +/// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the +/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). +/// +/// TCP is stream oriented, so a client can read available telemetry using [std::io::Read] as well. +/// To allow flexibly specifying the telemetry sent back to clients, a generic TM abstraction +/// in form of the [TmPacketSource] trait is used. Telemetry will be encoded with the COBS +/// protocol using [cobs::encode] in addition to being wrapped with the sentinel value 0 as the +/// packet delimiter as well before being sent back to the client. Please note that the server +/// will send as much data as it can retrieve from the [TmPacketSource] in its current +/// implementation. +/// +/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data +/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full +/// packets even from a data stream which is split up. The server wil use the +/// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a +/// generic TC receiver. +pub struct TcpTmtcInCobsServer { + generic_server: TcpTmtcGenericServer, +} + +impl TcpTmtcInCobsServer { + pub fn new( + cfg: ServerConfig, + tm_source: Box + Send>, + tc_receiver: Box + Send>, + ) -> Result> { + Ok(Self { + generic_server: TcpTmtcGenericServer::new( + cfg, + CobsTcParser::default(), + CobsTmParser::new(cfg.tm_buffer_size), + tm_source, + tc_receiver, + )?, + }) } - fn handle_tm_sending( - &mut self, - conn_result: &mut ConnectionResult, - stream: &mut TcpStream, - ) -> Result> { - let mut tm_was_sent = false; - loop { - // Write TM until TM source is exhausted. For now, there is no limit for the amount - // of TM written this way. - let read_tm_len = self - .base - .tm_source - .retrieve_packet(&mut self.base.tm_buffer) - .map_err(|e| TcpTmtcError::TmError(e))?; + delegate! { + to self.generic_server { + pub fn listener(&mut self) -> &mut TcpListener; - if read_tm_len == 0 { - return Ok(tm_was_sent); - } - tm_was_sent = true; - conn_result.num_sent_tms += 1; + /// Can be used to retrieve the local assigned address of the TCP server. This is especially + /// useful if using the port number 0 for OS auto-assignment. + pub fn local_addr(&self) -> std::io::Result; - // Encode into COBS and sent to client. - let mut current_idx = 0; - self.tm_encoding_buffer[current_idx] = 0; - current_idx += 1; - current_idx += encode( - &self.base.tm_buffer[..read_tm_len], - &mut self.tm_encoding_buffer[current_idx..], - ); - self.tm_encoding_buffer[current_idx] = 0; - current_idx += 1; - stream.write_all(&self.tm_encoding_buffer[..current_idx])?; + /// This call is used to handle the next connection to a client. Right now, it performs + /// the following steps: + /// + /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API + /// until a client connects. + /// 2. It reads all the telecommands from the client, which are expected to be COBS + /// encoded packets. + /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve + /// from the user specified [TmPacketSource] back to the client. + pub fn handle_next_connection( + &mut self, + ) -> Result>; } } } -- 2.43.0 From b622c3871a5b57056e0ec7f0206709b9686740fa Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:11:44 +0200 Subject: [PATCH 18/50] remove dump printout --- satrs-core/src/hal/std/tcp_with_cobs_server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 3b1edbf..c013c4c 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -152,7 +152,7 @@ impl< Ok(Self { base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, tc_handler, - tm_handler, // tmtc_handler: CobsTmtcParser::new(cfg.tm_buffer_size), + tm_handler, }) } @@ -221,7 +221,6 @@ impl< // As per [TcpStream::set_read_timeout] documentation, this should work for // both UNIX and Windows. std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => { - println!("should be here.."); self.tc_handler.handle_tc_parsing( &mut self.base.tc_buffer, self.base.tc_receiver.as_mut(), -- 2.43.0 From 5aa339286acc97084dcddf284e38d113a2bf7bfe Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:18:01 +0200 Subject: [PATCH 19/50] move generic server --- satrs-core/src/hal/std/tcp_server.rs | 167 ++++++++++++++++- .../src/hal/std/tcp_with_cobs_server.rs | 173 +----------------- 2 files changed, 169 insertions(+), 171 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 49592c0..436cbfc 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -3,8 +3,10 @@ use alloc::vec; use alloc::{boxed::Box, vec::Vec}; use core::time::Duration; use socket2::{Domain, Socket, Type}; -use std::net::SocketAddr; +use std::io::Read; use std::net::TcpListener; +use std::net::{SocketAddr, TcpStream}; +use std::thread; use crate::tmtc::{ReceivesTc, TmPacketSource}; use thiserror::Error; @@ -85,6 +87,169 @@ pub struct ConnectionResult { pub num_sent_tms: u32, } +pub trait TcpTcHandler { + fn handle_tc_parsing( + &mut self, + tc_buffer: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + conn_result: &mut ConnectionResult, + current_write_idx: usize, + next_write_idx: &mut usize, + ) -> Result<(), TcpTmtcError>; +} +pub trait TcpTmHandler { + fn handle_tm_sending( + &mut self, + tm_buffer: &mut [u8], + tm_source: &mut dyn TmPacketSource, + conn_result: &mut ConnectionResult, + stream: &mut TcpStream, + ) -> Result>; +} + +pub struct TcpTmtcGenericServer< + TmError, + TcError, + TmHandler: TcpTmHandler, + TcHandler: TcpTcHandler, +> { + base: TcpTmtcServerBase, + tc_handler: TcHandler, + tm_handler: TmHandler, +} + +impl< + TmError: 'static, + TcError: 'static, + TmHandler: TcpTmHandler, + TcHandler: TcpTcHandler, + > TcpTmtcGenericServer +{ + /// Create a new TMTC server which exchanges TMTC packets encoded with + /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). + /// + /// ## Parameter + /// + /// * `cfg` - Configuration of the server. + /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are + /// then sent back to the client. + /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded + /// to this TC receiver. + pub fn new( + cfg: ServerConfig, + tc_handler: TcHandler, + tm_handler: TmHandler, + tm_source: Box + Send>, + tc_receiver: Box + Send>, + ) -> Result, std::io::Error> { + Ok(Self { + base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, + tc_handler, + tm_handler, + }) + } + + /// Retrieve the internal [TcpListener] class. + pub fn listener(&mut self) -> &mut TcpListener { + self.base.listener() + } + + /// Can be used to retrieve the local assigned address of the TCP server. This is especially + /// useful if using the port number 0 for OS auto-assignment. + pub fn local_addr(&self) -> std::io::Result { + self.base.local_addr() + } + + /// This call is used to handle the next connection to a client. Right now, it performs + /// the following steps: + /// + /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API + /// until a client connects. + /// 2. It reads all the telecommands from the client, which are expected to be COBS + /// encoded packets. + /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve + /// from the user specified [TmPacketSource] back to the client. + pub fn handle_next_connection( + &mut self, + ) -> Result> { + let mut connection_result = ConnectionResult::default(); + let mut current_write_idx; + let mut next_write_idx = 0; + let (mut stream, addr) = self.base.listener.accept()?; + stream.set_nonblocking(true)?; + connection_result.addr = Some(addr); + current_write_idx = next_write_idx; + loop { + let read_result = stream.read(&mut self.base.tc_buffer[current_write_idx..]); + match read_result { + Ok(0) => { + // Connection closed by client. If any TC was read, parse for complete packets. + // After that, break the outer loop. + if current_write_idx > 0 { + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + } + break; + } + Ok(read_len) => { + current_write_idx += read_len; + // TC buffer is full, we must parse for complete packets now. + if current_write_idx == self.base.tc_buffer.capacity() { + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + current_write_idx = next_write_idx; + } + } + Err(e) => match e.kind() { + // As per [TcpStream::set_read_timeout] documentation, this should work for + // both UNIX and Windows. + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => { + self.tc_handler.handle_tc_parsing( + &mut self.base.tc_buffer, + self.base.tc_receiver.as_mut(), + &mut connection_result, + current_write_idx, + &mut next_write_idx, + )?; + current_write_idx = next_write_idx; + + if !self.tm_handler.handle_tm_sending( + &mut self.base.tm_buffer, + self.base.tm_source.as_mut(), + &mut connection_result, + &mut stream, + )? { + // No TC read, no TM was sent, but the client has not disconnected. + // Perform an inner delay to avoid burning CPU time. + thread::sleep(self.base.inner_loop_delay); + } + } + _ => { + return Err(TcpTmtcError::Io(e)); + } + }, + } + } + self.tm_handler.handle_tm_sending( + &mut self.base.tm_buffer, + self.base.tm_source.as_mut(), + &mut connection_result, + &mut stream, + )?; + Ok(connection_result) + } +} + pub(crate) struct TcpTmtcServerBase { pub(crate) listener: TcpListener, pub(crate) inner_loop_delay: Duration, diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index c013c4c..ad1913a 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -3,32 +3,18 @@ use alloc::vec; use cobs::decode_in_place; use cobs::encode; use delegate::delegate; -use std::io::Read; use std::io::Write; use std::net::SocketAddr; use std::net::TcpListener; use std::net::TcpStream; -use std::println; -use std::thread; use std::vec::Vec; -use crate::hal::std::tcp_server::{ServerConfig, TcpTmtcServerBase}; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; -use super::tcp_server::ConnectionResult; -use super::tcp_server::TcpTmtcError; - -pub trait TcpTcHandler { - fn handle_tc_parsing( - &mut self, - tc_buffer: &mut [u8], - tc_receiver: &mut dyn ReceivesTc, - conn_result: &mut ConnectionResult, - current_write_idx: usize, - next_write_idx: &mut usize, - ) -> Result<(), TcpTmtcError>; -} +use crate::hal::std::tcp_server::{ + ConnectionResult, ServerConfig, TcpTcHandler, TcpTmHandler, TcpTmtcError, TcpTmtcGenericServer, +}; #[derive(Default)] struct CobsTcParser {} @@ -53,16 +39,6 @@ impl TcpTcHandler for CobsTcParser { } } -pub trait TcpTmHandler { - fn handle_tm_sending( - &mut self, - tm_buffer: &mut [u8], - tm_source: &mut dyn TmPacketSource, - conn_result: &mut ConnectionResult, - stream: &mut TcpStream, - ) -> Result>; -} - struct CobsTmParser { tm_encoding_buffer: Vec, } @@ -114,149 +90,6 @@ impl TcpTmHandler for CobsTmParser { } } -pub struct TcpTmtcGenericServer< - TmError, - TcError, - TmHandler: TcpTmHandler, - TcHandler: TcpTcHandler, -> { - base: TcpTmtcServerBase, - tc_handler: TcHandler, - tm_handler: TmHandler, -} - -impl< - TmError: 'static, - TcError: 'static, - TmHandler: TcpTmHandler, - TcHandler: TcpTcHandler, - > TcpTmtcGenericServer -{ - /// Create a new TMTC server which exchanges TMTC packets encoded with - /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). - /// - /// ## Parameter - /// - /// * `cfg` - Configuration of the server. - /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are - /// then sent back to the client. - /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded - /// to this TC receiver. - pub fn new( - cfg: ServerConfig, - tc_handler: TcHandler, - tm_handler: TmHandler, - tm_source: Box + Send>, - tc_receiver: Box + Send>, - ) -> Result, std::io::Error> { - Ok(Self { - base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, - tc_handler, - tm_handler, - }) - } - - /// Retrieve the internal [TcpListener] class. - pub fn listener(&mut self) -> &mut TcpListener { - self.base.listener() - } - - /// Can be used to retrieve the local assigned address of the TCP server. This is especially - /// useful if using the port number 0 for OS auto-assignment. - pub fn local_addr(&self) -> std::io::Result { - self.base.local_addr() - } - - /// This call is used to handle the next connection to a client. Right now, it performs - /// the following steps: - /// - /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API - /// until a client connects. - /// 2. It reads all the telecommands from the client, which are expected to be COBS - /// encoded packets. - /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve - /// from the user specified [TmPacketSource] back to the client. - pub fn handle_next_connection( - &mut self, - ) -> Result> { - let mut connection_result = ConnectionResult::default(); - let mut current_write_idx; - let mut next_write_idx = 0; - let (mut stream, addr) = self.base.listener.accept()?; - stream.set_nonblocking(true)?; - connection_result.addr = Some(addr); - current_write_idx = next_write_idx; - loop { - let read_result = stream.read(&mut self.base.tc_buffer[current_write_idx..]); - match read_result { - Ok(0) => { - // Connection closed by client. If any TC was read, parse for complete packets. - // After that, break the outer loop. - if current_write_idx > 0 { - self.tc_handler.handle_tc_parsing( - &mut self.base.tc_buffer, - self.base.tc_receiver.as_mut(), - &mut connection_result, - current_write_idx, - &mut next_write_idx, - )?; - } - break; - } - Ok(read_len) => { - current_write_idx += read_len; - // TC buffer is full, we must parse for complete packets now. - if current_write_idx == self.base.tc_buffer.capacity() { - self.tc_handler.handle_tc_parsing( - &mut self.base.tc_buffer, - self.base.tc_receiver.as_mut(), - &mut connection_result, - current_write_idx, - &mut next_write_idx, - )?; - current_write_idx = next_write_idx; - } - } - Err(e) => match e.kind() { - // As per [TcpStream::set_read_timeout] documentation, this should work for - // both UNIX and Windows. - std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => { - self.tc_handler.handle_tc_parsing( - &mut self.base.tc_buffer, - self.base.tc_receiver.as_mut(), - &mut connection_result, - current_write_idx, - &mut next_write_idx, - )?; - current_write_idx = next_write_idx; - - if !self.tm_handler.handle_tm_sending( - &mut self.base.tm_buffer, - self.base.tm_source.as_mut(), - &mut connection_result, - &mut stream, - )? { - // No TC read, no TM was sent, but the client has not disconnected. - // Perform an inner delay to avoid burning CPU time. - thread::sleep(self.base.inner_loop_delay); - } - } - _ => { - return Err(TcpTmtcError::Io(e)); - } - }, - } - } - self.tm_handler.handle_tm_sending( - &mut self.base.tm_buffer, - self.base.tm_source.as_mut(), - &mut connection_result, - &mut stream, - )?; - Ok(connection_result) - } -} - /// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// -- 2.43.0 From 047256f2f896bcb20af0874ca60f1db0244210d9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:45:13 +0200 Subject: [PATCH 20/50] finishing up --- satrs-core/src/hal/std/tcp_server.rs | 69 ++++++++++++++----- .../src/hal/std/tcp_with_cobs_server.rs | 47 +++++++------ 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 436cbfc..3e75784 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -13,7 +13,7 @@ use thiserror::Error; // Re-export the TMTC in COBS server. pub use crate::hal::std::tcp_with_cobs_server::{ - parse_buffer_for_cobs_encoded_packets, TcpTmtcInCobsServer, + parse_buffer_for_cobs_encoded_packets, CobsTcParser, CobsTmSender, TcpTmtcInCobsServer, }; /// TCP configuration struct. @@ -87,7 +87,10 @@ pub struct ConnectionResult { pub num_sent_tms: u32, } -pub trait TcpTcHandler { +/// Generic parser abstraction for an object which can parse for telecommands given a raw +/// bytestream received from a TCP socket and send them to a generic [ReceivesTc] telecommand +/// receiver. This allows different encoding schemes for telecommands. +pub trait TcpTcParser { fn handle_tc_parsing( &mut self, tc_buffer: &mut [u8], @@ -97,7 +100,12 @@ pub trait TcpTcHandler { next_write_idx: &mut usize, ) -> Result<(), TcpTmtcError>; } -pub trait TcpTmHandler { + +/// Generic sender abstraction for an object which can pull telemetry from a given TM source +/// using a [TmPacketSource] and then send them back to a client using a given [TcpStream]. +/// The concrete implementation can also perform any encoding steps which are necessary before +/// sending back the data to a client. +pub trait TcpTmSender { fn handle_tm_sending( &mut self, tm_buffer: &mut [u8], @@ -107,11 +115,28 @@ pub trait TcpTmHandler { ) -> Result>; } +/// TCP TMTC server implementation for exchange of generic TMTC packets in a generic way which +/// stays agnostic to the encoding scheme and format used for both telecommands and telemetry. +/// +/// This server implements a generic TMTC handling logic and allows modifying its behaviour +/// through the following 4 core abstractions: +/// +/// 1. [TcpTcParser] to parse for telecommands from the raw bytestream received from a client. +/// 2. Parsed telecommands will be sent to the [ReceivesTc] telecommand receiver. +/// 3. [TcpTmSender] to send telemetry pulled from a TM source back to the client. +/// 4. [TmPacketSource] as a generic TM source used by the [TcpTmSender]. +/// +/// It is possible to specify custom abstractions to build a dedicated TCP TMTC server without +/// having to re-implement common logic. +/// +/// Currently, this framework offers the following concrete implementations: +/// +/// 1. [TcpTmtcInCobsServer] to exchange TMTC wrapped inside the COBS framing protocol. pub struct TcpTmtcGenericServer< TmError, TcError, - TmHandler: TcpTmHandler, - TcHandler: TcpTcHandler, + TmHandler: TcpTmSender, + TcHandler: TcpTcParser, > { base: TcpTmtcServerBase, tc_handler: TcHandler, @@ -121,31 +146,33 @@ pub struct TcpTmtcGenericServer< impl< TmError: 'static, TcError: 'static, - TmHandler: TcpTmHandler, - TcHandler: TcpTcHandler, - > TcpTmtcGenericServer + TmSender: TcpTmSender, + TcParser: TcpTcParser, + > TcpTmtcGenericServer { - /// Create a new TMTC server which exchanges TMTC packets encoded with - /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). + /// Create a new generic TMTC server instance. /// /// ## Parameter /// /// * `cfg` - Configuration of the server. + /// * `tc_parser` - Parser which extracts telecommands from the raw bytestream received from + /// the client. + /// * `tm_sender` - Sends back telemetry to the client using the specified TM source. /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are /// then sent back to the client. /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded /// to this TC receiver. pub fn new( cfg: ServerConfig, - tc_handler: TcHandler, - tm_handler: TmHandler, + tc_parser: TcParser, + tm_sender: TmSender, tm_source: Box + Send>, tc_receiver: Box + Send>, - ) -> Result, std::io::Error> { + ) -> Result, std::io::Error> { Ok(Self { base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, - tc_handler, - tm_handler, + tc_handler: tc_parser, + tm_handler: tm_sender, }) } @@ -165,10 +192,14 @@ impl< /// /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API /// until a client connects. - /// 2. It reads all the telecommands from the client, which are expected to be COBS - /// encoded packets. - /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve - /// from the user specified [TmPacketSource] back to the client. + /// 2. It reads all the telecommands from the client and parses all received data using the + /// user specified [TcpTcParser]. + /// 3. After reading and parsing all telecommands, it sends back all telemetry using the + /// user specified [TcpTmSender]. + /// + /// The server will delay for a user-specified period if the client connects to the server + /// for prolonged periods and there is no traffic for the server. This is the case if the + /// client does not send any telecommands and no telemetry needs to be sent back to the client. pub fn handle_next_connection( &mut self, ) -> Result> { diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index ad1913a..ec39615 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -13,13 +13,13 @@ use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; use crate::hal::std::tcp_server::{ - ConnectionResult, ServerConfig, TcpTcHandler, TcpTmHandler, TcpTmtcError, TcpTmtcGenericServer, + ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer, }; #[derive(Default)] -struct CobsTcParser {} +pub struct CobsTcParser {} -impl TcpTcHandler for CobsTcParser { +impl TcpTcParser for CobsTcParser { fn handle_tc_parsing( &mut self, tc_buffer: &mut [u8], @@ -39,11 +39,11 @@ impl TcpTcHandler for CobsTcParser { } } -struct CobsTmParser { +pub struct CobsTmSender { tm_encoding_buffer: Vec, } -impl CobsTmParser { +impl CobsTmSender { fn new(tm_buffer_size: usize) -> Self { Self { // The buffer should be large enough to hold the maximum expected TM size encoded with @@ -53,7 +53,7 @@ impl CobsTmParser { } } -impl TcpTmHandler for CobsTmParser { +impl TcpTmSender for CobsTmSender { fn handle_tm_sending( &mut self, tm_buffer: &mut [u8], @@ -93,13 +93,10 @@ impl TcpTmHandler for CobsTmParser { /// TCP TMTC server implementation for exchange of generic TMTC packets which are framed with the /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). /// -/// TCP is stream oriented, so a client can read available telemetry using [std::io::Read] as well. -/// To allow flexibly specifying the telemetry sent back to clients, a generic TM abstraction -/// in form of the [TmPacketSource] trait is used. Telemetry will be encoded with the COBS -/// protocol using [cobs::encode] in addition to being wrapped with the sentinel value 0 as the -/// packet delimiter as well before being sent back to the client. Please note that the server -/// will send as much data as it can retrieve from the [TmPacketSource] in its current -/// implementation. +/// Telemetry will be encoded with the COBS protocol using [cobs::encode] in addition to being +/// wrapped with the sentinel value 0 as the packet delimiter as well before being sent back to +/// the client. Please note that the server will send as much data as it can retrieve from the +/// [TmPacketSource] in its current implementation. /// /// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data /// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full @@ -107,10 +104,20 @@ impl TcpTmHandler for CobsTmParser { /// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a /// generic TC receiver. pub struct TcpTmtcInCobsServer { - generic_server: TcpTmtcGenericServer, + generic_server: TcpTmtcGenericServer, } impl TcpTmtcInCobsServer { + /// Create a new TCP TMTC server which exchanges TMTC packets encoded with + /// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). + /// + /// ## Parameter + /// + /// * `cfg` - Configuration of the server. + /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are + /// then sent back to the client. + /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded + /// to this TC receiver. pub fn new( cfg: ServerConfig, tm_source: Box + Send>, @@ -120,7 +127,7 @@ impl TcpTmtcInCobsServer { generic_server: TcpTmtcGenericServer::new( cfg, CobsTcParser::default(), - CobsTmParser::new(cfg.tm_buffer_size), + CobsTmSender::new(cfg.tm_buffer_size), tm_source, tc_receiver, )?, @@ -135,15 +142,7 @@ impl TcpTmtcInCobsServer { /// useful if using the port number 0 for OS auto-assignment. pub fn local_addr(&self) -> std::io::Result; - /// This call is used to handle the next connection to a client. Right now, it performs - /// the following steps: - /// - /// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API - /// until a client connects. - /// 2. It reads all the telecommands from the client, which are expected to be COBS - /// encoded packets. - /// 3. After reading and parsing all telecommands, it sends back all telemetry it can retrieve - /// from the user specified [TmPacketSource] back to the client. + /// Delegation to the [TcpTmtcGenericServer::handle_next_connection] call. pub fn handle_next_connection( &mut self, ) -> Result>; -- 2.43.0 From 3207be7ffe1388f47f200e02b999accf7e3df2e9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:51:38 +0200 Subject: [PATCH 21/50] great, work on the CCSDS TCP server can start.. --- satrs-core/src/hal/std/tcp_server.rs | 12 +++++------- satrs-core/src/hal/std/tcp_with_cobs_server.rs | 8 +++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index 3e75784..befcfc0 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -16,7 +16,7 @@ pub use crate::hal::std::tcp_with_cobs_server::{ parse_buffer_for_cobs_encoded_packets, CobsTcParser, CobsTmSender, TcpTmtcInCobsServer, }; -/// TCP configuration struct. +/// Configuration struct for the generic TCP TMTC server /// /// ## Parameters /// @@ -26,14 +26,12 @@ pub use crate::hal::std::tcp_with_cobs_server::{ /// to reduce CPU load. /// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and /// encoding of that data. This buffer should at large enough to hold the maximum expected -/// TM size in addition to the COBS encoding overhead. You can use -/// [cobs::max_encoding_length] to calculate this size. +/// TM size read from the packet source. /// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from /// the client. It is recommended to make this buffer larger to allow reading multiple -/// consecutive packets as well, for example by using 4096 or 8192 byte. The buffer should -/// at the very least be large enough to hold the maximum expected telecommand size in -/// addition to its COBS encoding overhead. You can use [cobs::max_encoding_length] to -/// calculate this size. +/// consecutive packets as well, for example by using common buffer sizes like 4096 or 8192 +/// byte. The buffer should at the very least be large enough to hold the maximum expected +/// telecommand size. /// * `reuse_addr` - Can be used to set the `SO_REUSEADDR` option on the raw socket. This is /// especially useful if the address and port are static for the server. Set to false by /// default. diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index ec39615..5eabd69 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -16,6 +16,7 @@ use crate::hal::std::tcp_server::{ ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer, }; +/// Concrete [TcpTcParser] implementation for the [TcpTmtcInCobsServer]. #[derive(Default)] pub struct CobsTcParser {} @@ -39,6 +40,7 @@ impl TcpTcParser for CobsTcParser { } } +/// Concrete [TcpTmSender] implementation for the [TcpTmtcInCobsServer]. pub struct CobsTmSender { tm_encoding_buffer: Vec, } @@ -116,8 +118,8 @@ impl TcpTmtcInCobsServer { /// * `cfg` - Configuration of the server. /// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are /// then sent back to the client. - /// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded - /// to this TC receiver. + /// * `tc_receiver` - Any received telecommands which were decoded successfully will be + /// forwarded to this TC receiver. pub fn new( cfg: ServerConfig, tm_source: Box + Send>, @@ -151,7 +153,7 @@ impl TcpTmtcInCobsServer { } /// This function parses a given buffer for COBS encoded packets. The packet structure is -/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter. +/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: /// /// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 /// -- 2.43.0 From d42999d2adce5dffb864dc082c6db43c16d83f6b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 00:57:25 +0200 Subject: [PATCH 22/50] thats enough for today --- satrs-core/src/hal/std/mod.rs | 1 + satrs-core/src/hal/std/tcp_server.rs | 1 + .../src/hal/std/tcp_spacepackets_server.rs | 32 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/satrs-core/src/hal/std/mod.rs b/satrs-core/src/hal/std/mod.rs index 7ac107b..17ec012 100644 --- a/satrs-core/src/hal/std/mod.rs +++ b/satrs-core/src/hal/std/mod.rs @@ -2,4 +2,5 @@ pub mod tcp_server; pub mod udp_server; +mod tcp_spacepackets_server; mod tcp_with_cobs_server; diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index befcfc0..c2d4869 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -12,6 +12,7 @@ use crate::tmtc::{ReceivesTc, TmPacketSource}; use thiserror::Error; // Re-export the TMTC in COBS server. +pub use crate::hal::std::tcp_spacepackets_server::parse_buffer_for_ccsds_space_packets; pub use crate::hal::std::tcp_with_cobs_server::{ parse_buffer_for_cobs_encoded_packets, CobsTcParser, CobsTmSender, TcpTmtcInCobsServer, }; diff --git a/satrs-core/src/hal/std/tcp_spacepackets_server.rs b/satrs-core/src/hal/std/tcp_spacepackets_server.rs index e69de29..e541602 100644 --- a/satrs-core/src/hal/std/tcp_spacepackets_server.rs +++ b/satrs-core/src/hal/std/tcp_spacepackets_server.rs @@ -0,0 +1,32 @@ +use crate::tmtc::ReceivesTc; + +pub trait ApidLookup { + fn validate(&self, apid: u16) -> bool; +} +/// This function parses a given buffer for COBS encoded packets. The packet structure is +/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: +/// +/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 +/// +/// This function is also able to deal with broken tail packets at the end. If broken tail +/// packets are detected, they are moved to the front of the buffer, and the write index for +/// future write operations will be written to the `next_write_idx` argument. +/// +/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. +pub fn parse_buffer_for_ccsds_space_packets( + buf: &mut [u8], + apid_lookup: &dyn ApidLookup, + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let mut start_index_packet = 0; + let mut start_found = false; + let mut last_byte = false; + let mut packets_found = 0; + for i in 0..buf.len() { + todo!(); + } + // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. + if start_index_packet > 0 && start_found && packets_found > 0 {} + Ok(packets_found) +} -- 2.43.0 From 54bc37b0861252e1262cc27b4eb70a1f5a5b2753 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 14:57:27 +0200 Subject: [PATCH 23/50] fix clippy --- satrs-core/src/hal/std/tcp_spacepackets_server.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_spacepackets_server.rs b/satrs-core/src/hal/std/tcp_spacepackets_server.rs index e541602..2509db1 100644 --- a/satrs-core/src/hal/std/tcp_spacepackets_server.rs +++ b/satrs-core/src/hal/std/tcp_spacepackets_server.rs @@ -15,18 +15,14 @@ pub trait ApidLookup { /// The parser will write all packets which were decoded successfully to the given `tc_receiver`. pub fn parse_buffer_for_ccsds_space_packets( buf: &mut [u8], - apid_lookup: &dyn ApidLookup, - tc_receiver: &mut dyn ReceivesTc, - next_write_idx: &mut usize, + _apid_lookup: &dyn ApidLookup, + _tc_receiver: &mut dyn ReceivesTc, + _next_write_idx: &mut usize, ) -> Result { - let mut start_index_packet = 0; - let mut start_found = false; - let mut last_byte = false; - let mut packets_found = 0; - for i in 0..buf.len() { + let packets_found = 0; + for _ in 0..buf.len() { todo!(); } // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. - if start_index_packet > 0 && start_found && packets_found > 0 {} Ok(packets_found) } -- 2.43.0 From 7536e107da5306c5bc0c7edb1e99ff95413c5a61 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 15:01:40 +0200 Subject: [PATCH 24/50] clippy fix --- satrs-example/src/tmtc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satrs-example/src/tmtc.rs b/satrs-example/src/tmtc.rs index 188a59d..5d2ea5e 100644 --- a/satrs-example/src/tmtc.rs +++ b/satrs-example/src/tmtc.rs @@ -1,5 +1,5 @@ use log::{info, warn}; -use satrs_core::hal::host::udp_server::{ReceiveResult, UdpTcServer}; +use satrs_core::hal::std::udp_server::{ReceiveResult, UdpTcServer}; use std::net::SocketAddr; use std::sync::mpsc::{Receiver, SendError, Sender, TryRecvError}; use std::thread; -- 2.43.0 From 88a5a390d93c0a3588776839808f60a6f773d4b8 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 15:59:51 +0200 Subject: [PATCH 25/50] new parser module --- satrs-core/src/hal/std/tcp_server.rs | 5 +- .../src/hal/std/tcp_spacepackets_server.rs | 27 ---- .../src/hal/std/tcp_with_cobs_server.rs | 54 +------- satrs-core/src/lib.rs | 1 + satrs-core/src/parsers.rs | 131 ++++++++++++++++++ 5 files changed, 134 insertions(+), 84 deletions(-) create mode 100644 satrs-core/src/parsers.rs diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index c2d4869..f1f5c47 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -12,10 +12,7 @@ use crate::tmtc::{ReceivesTc, TmPacketSource}; use thiserror::Error; // Re-export the TMTC in COBS server. -pub use crate::hal::std::tcp_spacepackets_server::parse_buffer_for_ccsds_space_packets; -pub use crate::hal::std::tcp_with_cobs_server::{ - parse_buffer_for_cobs_encoded_packets, CobsTcParser, CobsTmSender, TcpTmtcInCobsServer, -}; +pub use crate::hal::std::tcp_with_cobs_server::{CobsTcParser, CobsTmSender, TcpTmtcInCobsServer}; /// Configuration struct for the generic TCP TMTC server /// diff --git a/satrs-core/src/hal/std/tcp_spacepackets_server.rs b/satrs-core/src/hal/std/tcp_spacepackets_server.rs index 2509db1..8b13789 100644 --- a/satrs-core/src/hal/std/tcp_spacepackets_server.rs +++ b/satrs-core/src/hal/std/tcp_spacepackets_server.rs @@ -1,28 +1 @@ -use crate::tmtc::ReceivesTc; -pub trait ApidLookup { - fn validate(&self, apid: u16) -> bool; -} -/// This function parses a given buffer for COBS encoded packets. The packet structure is -/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: -/// -/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 -/// -/// This function is also able to deal with broken tail packets at the end. If broken tail -/// packets are detected, they are moved to the front of the buffer, and the write index for -/// future write operations will be written to the `next_write_idx` argument. -/// -/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. -pub fn parse_buffer_for_ccsds_space_packets( - buf: &mut [u8], - _apid_lookup: &dyn ApidLookup, - _tc_receiver: &mut dyn ReceivesTc, - _next_write_idx: &mut usize, -) -> Result { - let packets_found = 0; - for _ in 0..buf.len() { - todo!(); - } - // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. - Ok(packets_found) -} diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 5eabd69..a540a71 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -1,6 +1,5 @@ use alloc::boxed::Box; use alloc::vec; -use cobs::decode_in_place; use cobs::encode; use delegate::delegate; use std::io::Write; @@ -9,6 +8,7 @@ use std::net::TcpListener; use std::net::TcpStream; use std::vec::Vec; +use crate::parsers::parse_buffer_for_cobs_encoded_packets; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; @@ -152,58 +152,6 @@ impl TcpTmtcInCobsServer { } } -/// This function parses a given buffer for COBS encoded packets. The packet structure is -/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: -/// -/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 -/// -/// This function is also able to deal with broken tail packets at the end. If broken tail -/// packets are detected, they are moved to the front of the buffer, and the write index for -/// future write operations will be written to the `next_write_idx` argument. -/// -/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. -pub fn parse_buffer_for_cobs_encoded_packets( - buf: &mut [u8], - tc_receiver: &mut dyn ReceivesTc, - next_write_idx: &mut usize, -) -> Result { - let mut start_index_packet = 0; - let mut start_found = false; - let mut last_byte = false; - let mut packets_found = 0; - for i in 0..buf.len() { - if i == buf.len() - 1 { - last_byte = true; - } - if buf[i] == 0 { - if !start_found && !last_byte && buf[i + 1] == 0 { - // Special case: Consecutive sentinel values or all zeroes. - // Skip. - continue; - } - if start_found { - let decode_result = decode_in_place(&mut buf[start_index_packet..i]); - if let Ok(packet_len) = decode_result { - packets_found += 1; - tc_receiver - .pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?; - } - start_found = false; - } else { - start_index_packet = i + 1; - start_found = true; - } - } - } - // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. - if start_index_packet > 0 && start_found && packets_found > 0 { - let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); - first_seg[..last_seg.len()].copy_from_slice(last_seg); - *next_write_idx = last_seg.len(); - } - Ok(packets_found) -} - #[cfg(test)] mod tests { use core::{ diff --git a/satrs-core/src/lib.rs b/satrs-core/src/lib.rs index 43c8cfb..50e9d42 100644 --- a/satrs-core/src/lib.rs +++ b/satrs-core/src/lib.rs @@ -33,6 +33,7 @@ pub mod hk; pub mod mode; pub mod objects; pub mod params; +pub mod parsers; pub mod pool; pub mod power; pub mod pus; diff --git a/satrs-core/src/parsers.rs b/satrs-core/src/parsers.rs new file mode 100644 index 0000000..f2ee248 --- /dev/null +++ b/satrs-core/src/parsers.rs @@ -0,0 +1,131 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use cobs::decode_in_place; +#[cfg(feature = "alloc")] +use hashbrown::HashSet; + +use crate::tmtc::ReceivesTc; + +/// This function parses a given buffer for COBS encoded packets. The packet structure is +/// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: +/// +/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 +/// +/// This function is also able to deal with broken tail packets at the end. If broken tail +/// packets are detected, they are moved to the front of the buffer, and the write index for +/// future write operations will be written to the `next_write_idx` argument. +/// +/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. +pub fn parse_buffer_for_cobs_encoded_packets( + buf: &mut [u8], + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let mut start_index_packet = 0; + let mut start_found = false; + let mut last_byte = false; + let mut packets_found = 0; + for i in 0..buf.len() { + if i == buf.len() - 1 { + last_byte = true; + } + if buf[i] == 0 { + if !start_found && !last_byte && buf[i + 1] == 0 { + // Special case: Consecutive sentinel values or all zeroes. + // Skip. + continue; + } + if start_found { + let decode_result = decode_in_place(&mut buf[start_index_packet..i]); + if let Ok(packet_len) = decode_result { + packets_found += 1; + tc_receiver + .pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?; + } + start_found = false; + } else { + start_index_packet = i + 1; + start_found = true; + } + } + } + // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. + if start_index_packet > 0 && start_found && packets_found > 0 { + let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); + first_seg[..last_seg.len()].copy_from_slice(last_seg); + *next_write_idx = last_seg.len(); + } + Ok(packets_found) +} + +pub trait PacketIdLookup { + fn validate(&self, apid: u16) -> bool; +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for Vec { + fn validate(&self, apid: u16) -> bool { + self.contains(&apid) + } +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for HashSet { + fn validate(&self, apid: u16) -> bool { + self.contains(&apid) + } +} + +impl PacketIdLookup for &[u16] { + fn validate(&self, apid: u16) -> bool { + if self.binary_search(&apid).is_ok() { + return true; + } + false + } +} + +/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the +/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then +/// uses the length field of the packet to extract CCSDS packets. +/// +/// This function is also able to deal with broken tail packets at the end as long a the parser +/// can read the full 6 bytes which constitue a space packet header. If broken tail packets are +/// detected, they are moved to the front of the buffer, and the write index for future write +/// operations will be written to the `next_write_idx` argument. +/// +/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. +pub fn parse_buffer_for_ccsds_space_packets( + buf: &mut [u8], + packet_id_lookup: &dyn PacketIdLookup, + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let packets_found = 0; + let mut current_idx = 0; + let buf_len = buf.len(); + loop { + if current_idx + 7 >= buf.len() { + break; + } + let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap()); + if packet_id_lookup.validate(packet_id) { + let length_field = + u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap()); + let packet_size = length_field + 7; + if (current_idx + packet_size as usize) < buf_len { + tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?; + } else { + // Move packet to start of buffer if applicable. + if current_idx > 0 { + buf.copy_within(current_idx.., 0); + *next_write_idx = current_idx; + } + } + current_idx += packet_size as usize; + continue; + } + current_idx += 1; + } + Ok(packets_found) +} -- 2.43.0 From 2f08365247a2dcaa5f608b48c45c6bc4a3020d39 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 16:08:09 +0200 Subject: [PATCH 26/50] move some tests --- .../src/hal/std/tcp_with_cobs_server.rs | 179 +--------------- satrs-core/src/parsers.rs | 192 ++++++++++++++++++ 2 files changed, 195 insertions(+), 176 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index a540a71..559c7a2 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -165,14 +165,12 @@ mod tests { thread, }; - use crate::tmtc::{ReceivesTcCore, TmPacketSource}; - use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; + use crate::{tmtc::{ReceivesTcCore, TmPacketSource}, hal::std::tcp_server::ServerConfig, parsers::tests::{SIMPLE_PACKET, INVERTED_PACKET}}; + use alloc::{collections::VecDeque, sync::Arc, vec::Vec, boxed::Box}; use cobs::encode; - use super::{parse_buffer_for_cobs_encoded_packets, ServerConfig, TcpTmtcInCobsServer}; + use super::TcpTmtcInCobsServer; - const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; - const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; #[derive(Default, Clone)] struct SyncTcCacher { @@ -188,20 +186,6 @@ mod tests { } } - #[derive(Default)] - struct TcCacher { - tc_queue: VecDeque>, - } - - impl ReceivesTcCore for TcCacher { - type Error = (); - - fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { - self.tc_queue.push_back(tc_raw.to_vec()); - Ok(()) - } - } - #[derive(Default, Clone)] struct SyncTmSource { tm_queue: Arc>>>, @@ -243,163 +227,6 @@ mod tests { *current_idx += 1; } - #[test] - fn test_parsing_simple_packet() { - let mut test_sender = TcCacher::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.tc_queue.len(), 1); - let packet = &test_sender.tc_queue[0]; - assert_eq!(packet, &SIMPLE_PACKET); - } - - #[test] - fn test_parsing_consecutive_packets() { - let mut test_sender = TcCacher::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - - // Second packet - encoded_buf[current_idx] = 0; - current_idx += 1; - current_idx += encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); - encoded_buf[current_idx] = 0; - current_idx += 1; - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 2); - assert_eq!(test_sender.tc_queue.len(), 2); - let packet0 = &test_sender.tc_queue[0]; - assert_eq!(packet0, &SIMPLE_PACKET); - let packet1 = &test_sender.tc_queue[1]; - assert_eq!(packet1, &INVERTED_PACKET); - } - - #[test] - fn test_split_tail_packet_only() { - let mut test_sender = TcCacher::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - let mut next_read_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx - 1], - &mut test_sender, - &mut next_read_idx, - ) - .unwrap(); - assert_eq!(packets, 0); - assert_eq!(test_sender.tc_queue.len(), 0); - assert_eq!(next_read_idx, 0); - } - - fn generic_test_split_packet(cut_off: usize) { - let mut test_sender = TcCacher::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - assert!(cut_off < INVERTED_PACKET.len() + 1); - let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - // Second packet - encoded_buf[current_idx] = 0; - let packet_start = current_idx; - current_idx += 1; - let encoded_len = encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); - assert_eq!(encoded_len, 6); - current_idx += encoded_len; - // We cut off the sentinel byte, so we expecte the write index to be the length of the - // packet minus the sentinel byte plus the first sentinel byte. - let next_expected_write_idx = 1 + encoded_len - cut_off + 1; - encoded_buf[current_idx] = 0; - current_idx += 1; - let mut next_write_idx = 0; - let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec(); - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx - cut_off], - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.tc_queue.len(), 1); - assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); - assert_eq!(next_write_idx, next_expected_write_idx); - assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); - } - - #[test] - fn test_one_packet_and_split_tail_packet_0() { - generic_test_split_packet(1); - } - - #[test] - fn test_one_packet_and_split_tail_packet_1() { - generic_test_split_packet(2); - } - - #[test] - fn test_one_packet_and_split_tail_packet_2() { - generic_test_split_packet(3); - } - - #[test] - fn test_zero_at_end() { - let mut test_sender = TcCacher::default(); - let mut encoded_buf: [u8; 16] = [0; 16]; - let mut next_write_idx = 0; - let mut current_idx = 0; - encoded_buf[current_idx] = 5; - current_idx += 1; - encode_simple_packet(&mut encoded_buf, &mut current_idx); - encoded_buf[current_idx] = 0; - current_idx += 1; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut encoded_buf[0..current_idx], - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 1); - assert_eq!(test_sender.tc_queue.len(), 1); - assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); - assert_eq!(next_write_idx, 1); - assert_eq!(encoded_buf[0], 0); - } - - #[test] - fn test_all_zeroes() { - let mut test_sender = TcCacher::default(); - let mut all_zeroes: [u8; 5] = [0; 5]; - let mut next_write_idx = 0; - let packets = parse_buffer_for_cobs_encoded_packets( - // Cut off the sentinel byte at the end. - &mut all_zeroes, - &mut test_sender, - &mut next_write_idx, - ) - .unwrap(); - assert_eq!(packets, 0); - assert!(test_sender.tc_queue.is_empty()); - assert_eq!(next_write_idx, 0); - } - fn generic_tmtc_server( addr: &SocketAddr, tc_receiver: SyncTcCacher, diff --git a/satrs-core/src/parsers.rs b/satrs-core/src/parsers.rs index f2ee248..41aeaa1 100644 --- a/satrs-core/src/parsers.rs +++ b/satrs-core/src/parsers.rs @@ -129,3 +129,195 @@ pub fn parse_buffer_for_ccsds_space_packets( } Ok(packets_found) } + +#[cfg(test)] +pub(crate) mod tests { + use alloc::{collections::VecDeque, vec::Vec}; + use cobs::encode; + + use crate::tmtc::ReceivesTcCore; + + use super::parse_buffer_for_cobs_encoded_packets; + + pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; + + #[derive(Default)] + struct TcCacher { + tc_queue: VecDeque>, + } + + impl ReceivesTcCore for TcCacher { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + self.tc_queue.push_back(tc_raw.to_vec()); + Ok(()) + } + } + + pub (crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; + } + + #[test] + fn test_parsing_simple_packet() { + let mut test_sender = TcCacher::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.tc_queue.len(), 1); + let packet = &test_sender.tc_queue[0]; + assert_eq!(packet, &SIMPLE_PACKET); + } + + #[test] + fn test_parsing_consecutive_packets() { + let mut test_sender = TcCacher::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + + // Second packet + encoded_buf[current_idx] = 0; + current_idx += 1; + current_idx += encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 2); + assert_eq!(test_sender.tc_queue.len(), 2); + let packet0 = &test_sender.tc_queue[0]; + assert_eq!(packet0, &SIMPLE_PACKET); + let packet1 = &test_sender.tc_queue[1]; + assert_eq!(packet1, &INVERTED_PACKET); + } + + #[test] + fn test_split_tail_packet_only() { + let mut test_sender = TcCacher::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut next_read_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - 1], + &mut test_sender, + &mut next_read_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert_eq!(test_sender.tc_queue.len(), 0); + assert_eq!(next_read_idx, 0); + } + + fn generic_test_split_packet(cut_off: usize) { + let mut test_sender = TcCacher::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + assert!(cut_off < INVERTED_PACKET.len() + 1); + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + // Second packet + encoded_buf[current_idx] = 0; + let packet_start = current_idx; + current_idx += 1; + let encoded_len = encode(&INVERTED_PACKET, &mut encoded_buf[current_idx..]); + assert_eq!(encoded_len, 6); + current_idx += encoded_len; + // We cut off the sentinel byte, so we expecte the write index to be the length of the + // packet minus the sentinel byte plus the first sentinel byte. + let next_expected_write_idx = 1 + encoded_len - cut_off + 1; + encoded_buf[current_idx] = 0; + current_idx += 1; + let mut next_write_idx = 0; + let expected_at_start = encoded_buf[packet_start..current_idx - cut_off].to_vec(); + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx - cut_off], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.tc_queue.len(), 1); + assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, next_expected_write_idx); + assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start); + } + + #[test] + fn test_one_packet_and_split_tail_packet_0() { + generic_test_split_packet(1); + } + + #[test] + fn test_one_packet_and_split_tail_packet_1() { + generic_test_split_packet(2); + } + + #[test] + fn test_one_packet_and_split_tail_packet_2() { + generic_test_split_packet(3); + } + + #[test] + fn test_zero_at_end() { + let mut test_sender = TcCacher::default(); + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut next_write_idx = 0; + let mut current_idx = 0; + encoded_buf[current_idx] = 5; + current_idx += 1; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + encoded_buf[current_idx] = 0; + current_idx += 1; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut encoded_buf[0..current_idx], + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 1); + assert_eq!(test_sender.tc_queue.len(), 1); + assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET); + assert_eq!(next_write_idx, 1); + assert_eq!(encoded_buf[0], 0); + } + + #[test] + fn test_all_zeroes() { + let mut test_sender = TcCacher::default(); + let mut all_zeroes: [u8; 5] = [0; 5]; + let mut next_write_idx = 0; + let packets = parse_buffer_for_cobs_encoded_packets( + // Cut off the sentinel byte at the end. + &mut all_zeroes, + &mut test_sender, + &mut next_write_idx, + ) + .unwrap(); + assert_eq!(packets, 0); + assert!(test_sender.tc_queue.is_empty()); + assert_eq!(next_write_idx, 0); + } +} -- 2.43.0 From e4d8c0c9a790865103f3113d3316dfd161d6f1fe Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 16:08:35 +0200 Subject: [PATCH 27/50] cargo fmt --- satrs-core/src/hal/std/tcp_with_cobs_server.rs | 9 ++++++--- satrs-core/src/parsers.rs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 559c7a2..31bf4d0 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -165,13 +165,16 @@ mod tests { thread, }; - use crate::{tmtc::{ReceivesTcCore, TmPacketSource}, hal::std::tcp_server::ServerConfig, parsers::tests::{SIMPLE_PACKET, INVERTED_PACKET}}; - use alloc::{collections::VecDeque, sync::Arc, vec::Vec, boxed::Box}; + use crate::{ + hal::std::tcp_server::ServerConfig, + parsers::tests::{INVERTED_PACKET, SIMPLE_PACKET}, + tmtc::{ReceivesTcCore, TmPacketSource}, + }; + use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; use cobs::encode; use super::TcpTmtcInCobsServer; - #[derive(Default, Clone)] struct SyncTcCacher { tc_queue: Arc>>>, diff --git a/satrs-core/src/parsers.rs b/satrs-core/src/parsers.rs index 41aeaa1..cfcd9b8 100644 --- a/satrs-core/src/parsers.rs +++ b/satrs-core/src/parsers.rs @@ -156,7 +156,7 @@ pub(crate) mod tests { } } - pub (crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { encoded_buf[*current_idx] = 0; *current_idx += 1; *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); -- 2.43.0 From 86ec0f50b80ad143cd28e949e6336ba57a7944b1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 16:17:24 +0200 Subject: [PATCH 28/50] proper modularisation --- satrs-core/src/parsers/ccsds.rs | 78 +++++++++++++++ .../src/{parsers.rs => parsers/cobs.rs} | 95 +------------------ satrs-core/src/parsers/mod.rs | 21 ++++ 3 files changed, 104 insertions(+), 90 deletions(-) create mode 100644 satrs-core/src/parsers/ccsds.rs rename satrs-core/src/{parsers.rs => parsers/cobs.rs} (72%) create mode 100644 satrs-core/src/parsers/mod.rs diff --git a/satrs-core/src/parsers/ccsds.rs b/satrs-core/src/parsers/ccsds.rs new file mode 100644 index 0000000..949a1cf --- /dev/null +++ b/satrs-core/src/parsers/ccsds.rs @@ -0,0 +1,78 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use hashbrown::HashSet; + +use crate::tmtc::ReceivesTc; + +pub trait PacketIdLookup { + fn validate(&self, apid: u16) -> bool; +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for Vec { + fn validate(&self, apid: u16) -> bool { + self.contains(&apid) + } +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for HashSet { + fn validate(&self, apid: u16) -> bool { + self.contains(&apid) + } +} + +impl PacketIdLookup for &[u16] { + fn validate(&self, apid: u16) -> bool { + if self.binary_search(&apid).is_ok() { + return true; + } + false + } +} + +/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the +/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then +/// uses the length field of the packet to extract CCSDS packets. +/// +/// This function is also able to deal with broken tail packets at the end as long a the parser +/// can read the full 6 bytes which constitue a space packet header. If broken tail packets are +/// detected, they are moved to the front of the buffer, and the write index for future write +/// operations will be written to the `next_write_idx` argument. +/// +/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. +pub fn parse_buffer_for_ccsds_space_packets( + buf: &mut [u8], + packet_id_lookup: &dyn PacketIdLookup, + tc_receiver: &mut dyn ReceivesTc, + next_write_idx: &mut usize, +) -> Result { + let packets_found = 0; + let mut current_idx = 0; + let buf_len = buf.len(); + loop { + if current_idx + 7 >= buf.len() { + break; + } + let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap()); + if packet_id_lookup.validate(packet_id) { + let length_field = + u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap()); + let packet_size = length_field + 7; + if (current_idx + packet_size as usize) < buf_len { + tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?; + } else { + // Move packet to start of buffer if applicable. + if current_idx > 0 { + buf.copy_within(current_idx.., 0); + *next_write_idx = current_idx; + } + } + current_idx += packet_size as usize; + continue; + } + current_idx += 1; + } + Ok(packets_found) +} diff --git a/satrs-core/src/parsers.rs b/satrs-core/src/parsers/cobs.rs similarity index 72% rename from satrs-core/src/parsers.rs rename to satrs-core/src/parsers/cobs.rs index cfcd9b8..bf99ce6 100644 --- a/satrs-core/src/parsers.rs +++ b/satrs-core/src/parsers/cobs.rs @@ -1,10 +1,5 @@ -#[cfg(feature = "alloc")] -use alloc::vec::Vec; -use cobs::decode_in_place; -#[cfg(feature = "alloc")] -use hashbrown::HashSet; - use crate::tmtc::ReceivesTc; +use cobs::decode_in_place; /// This function parses a given buffer for COBS encoded packets. The packet structure is /// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: @@ -58,90 +53,18 @@ pub fn parse_buffer_for_cobs_encoded_packets( Ok(packets_found) } -pub trait PacketIdLookup { - fn validate(&self, apid: u16) -> bool; -} - -#[cfg(feature = "alloc")] -impl PacketIdLookup for Vec { - fn validate(&self, apid: u16) -> bool { - self.contains(&apid) - } -} - -#[cfg(feature = "alloc")] -impl PacketIdLookup for HashSet { - fn validate(&self, apid: u16) -> bool { - self.contains(&apid) - } -} - -impl PacketIdLookup for &[u16] { - fn validate(&self, apid: u16) -> bool { - if self.binary_search(&apid).is_ok() { - return true; - } - false - } -} - -/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the -/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then -/// uses the length field of the packet to extract CCSDS packets. -/// -/// This function is also able to deal with broken tail packets at the end as long a the parser -/// can read the full 6 bytes which constitue a space packet header. If broken tail packets are -/// detected, they are moved to the front of the buffer, and the write index for future write -/// operations will be written to the `next_write_idx` argument. -/// -/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. -pub fn parse_buffer_for_ccsds_space_packets( - buf: &mut [u8], - packet_id_lookup: &dyn PacketIdLookup, - tc_receiver: &mut dyn ReceivesTc, - next_write_idx: &mut usize, -) -> Result { - let packets_found = 0; - let mut current_idx = 0; - let buf_len = buf.len(); - loop { - if current_idx + 7 >= buf.len() { - break; - } - let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap()); - if packet_id_lookup.validate(packet_id) { - let length_field = - u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap()); - let packet_size = length_field + 7; - if (current_idx + packet_size as usize) < buf_len { - tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?; - } else { - // Move packet to start of buffer if applicable. - if current_idx > 0 { - buf.copy_within(current_idx.., 0); - *next_write_idx = current_idx; - } - } - current_idx += packet_size as usize; - continue; - } - current_idx += 1; - } - Ok(packets_found) -} - #[cfg(test)] pub(crate) mod tests { use alloc::{collections::VecDeque, vec::Vec}; use cobs::encode; - use crate::tmtc::ReceivesTcCore; + use crate::{ + parsers::tests::{encode_simple_packet, INVERTED_PACKET, SIMPLE_PACKET}, + tmtc::ReceivesTcCore, + }; use super::parse_buffer_for_cobs_encoded_packets; - pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; - pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; - #[derive(Default)] struct TcCacher { tc_queue: VecDeque>, @@ -156,14 +79,6 @@ pub(crate) mod tests { } } - pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encoded_buf[*current_idx] = 0; - *current_idx += 1; - *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); - encoded_buf[*current_idx] = 0; - *current_idx += 1; - } - #[test] fn test_parsing_simple_packet() { let mut test_sender = TcCacher::default(); diff --git a/satrs-core/src/parsers/mod.rs b/satrs-core/src/parsers/mod.rs new file mode 100644 index 0000000..c25060a --- /dev/null +++ b/satrs-core/src/parsers/mod.rs @@ -0,0 +1,21 @@ +pub mod ccsds; +pub mod cobs; + +pub use crate::parsers::ccsds::parse_buffer_for_ccsds_space_packets; +pub use crate::parsers::cobs::parse_buffer_for_cobs_encoded_packets; + +#[cfg(test)] +pub(crate) mod tests { + use cobs::encode; + + pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; + + pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; + } +} -- 2.43.0 From d0e6ccdaa30818de3144596db898bed4189b7bb1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 16:18:14 +0200 Subject: [PATCH 29/50] this is better --- satrs-core/Cargo.toml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 9ee7cc1..a26cf03 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -90,23 +90,23 @@ version = "1" [features] default = ["std"] std = [ - "downcast-rs/std", - "alloc", - "bus", - "postcard/use-std", - "crossbeam-channel/std", - "serde/std", - "spacepackets/std", - "num_enum/std", - "thiserror", - "socket2" + "downcast-rs/std", + "alloc", + "bus", + "postcard/use-std", + "crossbeam-channel/std", + "serde/std", + "spacepackets/std", + "num_enum/std", + "thiserror", + "socket2" ] alloc = [ - "serde/alloc", - "spacepackets/alloc", - "hashbrown", - "dyn-clone", - "downcast-rs" + "serde/alloc", + "spacepackets/alloc", + "hashbrown", + "dyn-clone", + "downcast-rs" ] serde = ["dep:serde", "spacepackets/serde"] crossbeam = ["crossbeam-channel"] -- 2.43.0 From d5722b7f39e68237e3562edb88516cc131bb2957 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:27:08 +0200 Subject: [PATCH 30/50] appears to work now --- satrs-core/Cargo.toml | 4 ++-- satrs-core/src/parsers/ccsds.rs | 37 ++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index a26cf03..543039e 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -68,8 +68,8 @@ optional = true [dependencies.spacepackets] version = "0.7.0-beta.1" # path = "../../spacepackets" -# git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" -# rev = "" +git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" +rev = "79d26e1a6" # branch = "" default-features = false diff --git a/satrs-core/src/parsers/ccsds.rs b/satrs-core/src/parsers/ccsds.rs index 949a1cf..1a90a7b 100644 --- a/satrs-core/src/parsers/ccsds.rs +++ b/satrs-core/src/parsers/ccsds.rs @@ -2,36 +2,59 @@ use alloc::vec::Vec; #[cfg(feature = "alloc")] use hashbrown::HashSet; +use spacepackets::PacketId; use crate::tmtc::ReceivesTc; pub trait PacketIdLookup { - fn validate(&self, apid: u16) -> bool; + fn validate(&self, packet_id: u16) -> bool; } #[cfg(feature = "alloc")] impl PacketIdLookup for Vec { - fn validate(&self, apid: u16) -> bool { - self.contains(&apid) + fn validate(&self, packet_id: u16) -> bool { + self.contains(&packet_id) + } +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for Vec { + fn validate(&self, packet_id: u16) -> bool { + self.contains(&PacketId::from(packet_id)) } } #[cfg(feature = "alloc")] impl PacketIdLookup for HashSet { - fn validate(&self, apid: u16) -> bool { - self.contains(&apid) + fn validate(&self, packet_id: u16) -> bool { + self.contains(&packet_id) + } +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for HashSet { + fn validate(&self, packet_id: u16) -> bool { + self.contains(&PacketId::from(packet_id)) } } impl PacketIdLookup for &[u16] { - fn validate(&self, apid: u16) -> bool { - if self.binary_search(&apid).is_ok() { + fn validate(&self, packet_id: u16) -> bool { + if self.binary_search(&packet_id).is_ok() { return true; } false } } +impl PacketIdLookup for &[PacketId] { + fn validate(&self, packet_id: u16) -> bool { + if self.binary_search(&PacketId::from(packet_id)).is_ok() { + return true; + } + false + } +} /// This function parses a given buffer for tightly packed CCSDS space packets. It uses the /// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then /// uses the length field of the packet to extract CCSDS packets. -- 2.43.0 From aa556ad746f603581b90b4fe9c5fab9d797ce999 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:35:24 +0200 Subject: [PATCH 31/50] maybe like this? --- satrs-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 543039e..5eb0421 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -66,7 +66,7 @@ features = ["all"] optional = true [dependencies.spacepackets] -version = "0.7.0-beta.1" +# version = "0.7.0-beta.1" # path = "../../spacepackets" git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" rev = "79d26e1a6" -- 2.43.0 From 4dd85f294cfe97247ed0020825661e8ed34554b4 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:36:10 +0200 Subject: [PATCH 32/50] this in confusing --- satrs-mib/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml index f6dd23a..db9b85e 100644 --- a/satrs-mib/Cargo.toml +++ b/satrs-mib/Cargo.toml @@ -23,8 +23,8 @@ version = "1" optional = true [dependencies.satrs-core] -version = "0.1.0-alpha.0" -# path = "../satrs-core" +# version = "0.1.0-alpha.0" +path = "../satrs-core" [dependencies.satrs-mib-codegen] path = "codegen" -- 2.43.0 From 35e1f7a983f6535c5571186e361fe101d4306b89 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:40:50 +0200 Subject: [PATCH 33/50] jenkinsfile improvements --- automation/Jenkinsfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index 44946f7..0770614 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -8,6 +8,11 @@ pipeline { } stages { + stage('Rust Toolchain Info') { + steps { + sh 'rustc --version' + } + } stage('Clippy') { steps { sh 'cargo clippy' @@ -15,7 +20,9 @@ pipeline { } stage('Docs') { steps { - sh 'cargo +nightly doc --all-features' + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh 'cargo +nightly doc --all-features' + } } } stage('Rustfmt') { -- 2.43.0 From b62d60f5790bb7aa9fa7bbe0dfa7d1496bdb5c08 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:48:56 +0200 Subject: [PATCH 34/50] lets try this --- satrs-mib/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml index db9b85e..5f304cd 100644 --- a/satrs-mib/Cargo.toml +++ b/satrs-mib/Cargo.toml @@ -24,7 +24,8 @@ optional = true [dependencies.satrs-core] # version = "0.1.0-alpha.0" -path = "../satrs-core" +git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" +rev = "35e1f7a983f6535c5571186e361fe101d4306b89" [dependencies.satrs-mib-codegen] path = "codegen" -- 2.43.0 From e1998a8bcc4e2863f38a083b3e4527012389b335 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:51:39 +0200 Subject: [PATCH 35/50] wrong repo --- satrs-mib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml index 5f304cd..cdfa4f5 100644 --- a/satrs-mib/Cargo.toml +++ b/satrs-mib/Cargo.toml @@ -24,7 +24,7 @@ optional = true [dependencies.satrs-core] # version = "0.1.0-alpha.0" -git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" +git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git" rev = "35e1f7a983f6535c5571186e361fe101d4306b89" [dependencies.satrs-mib-codegen] -- 2.43.0 From 9ccb6bb0005853ab340ac9a5bfd5949c00a915c9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 18 Sep 2023 18:55:30 +0200 Subject: [PATCH 36/50] stupid circ deps --- satrs-mib/codegen/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/satrs-mib/codegen/Cargo.toml b/satrs-mib/codegen/Cargo.toml index ccc4d1a..db6a671 100644 --- a/satrs-mib/codegen/Cargo.toml +++ b/satrs-mib/codegen/Cargo.toml @@ -20,8 +20,9 @@ quote = "1" proc-macro2 = "1" [dependencies.satrs-core] -version = "0.1.0-alpha.0" -# path = "../../satrs-core" +# version = "0.1.0-alpha.0" +git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git" +rev = "35e1f7a983f6535c5571186e361fe101d4306b89" [dev-dependencies] trybuild = { version = "1", features = ["diff"] } -- 2.43.0 From 22254e4bbe468701cbc53a453d8f56c8fb4c105b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 19 Sep 2023 00:13:55 +0200 Subject: [PATCH 37/50] this works, just not sure whether its the best solution.. --- satrs-core/src/hal/std/tcp_server.rs | 12 ++-- .../src/hal/std/tcp_with_cobs_server.rs | 14 ++--- satrs-core/src/parsers/ccsds.rs | 4 +- satrs-core/src/parsers/cobs.rs | 4 +- satrs-core/src/tmtc/mod.rs | 58 ++++++++++++++++++- 5 files changed, 72 insertions(+), 20 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_server.rs b/satrs-core/src/hal/std/tcp_server.rs index f1f5c47..dcccf66 100644 --- a/satrs-core/src/hal/std/tcp_server.rs +++ b/satrs-core/src/hal/std/tcp_server.rs @@ -162,8 +162,8 @@ impl< cfg: ServerConfig, tc_parser: TcParser, tm_sender: TmSender, - tm_source: Box + Send>, - tc_receiver: Box + Send>, + tm_source: Box>, + tc_receiver: Box>, ) -> Result, std::io::Error> { Ok(Self { base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?, @@ -280,17 +280,17 @@ impl< pub(crate) struct TcpTmtcServerBase { pub(crate) listener: TcpListener, pub(crate) inner_loop_delay: Duration, - pub(crate) tm_source: Box + Send>, + pub(crate) tm_source: Box>, pub(crate) tm_buffer: Vec, - pub(crate) tc_receiver: Box + Send>, + pub(crate) tc_receiver: Box>, pub(crate) tc_buffer: Vec, } impl TcpTmtcServerBase { pub(crate) fn new( cfg: ServerConfig, - tm_source: Box + Send>, - tc_receiver: Box + Send>, + tm_source: Box>, + tc_receiver: Box>, ) -> Result { // Create a TCP listener bound to two addresses. let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 31bf4d0..52f277b 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -20,7 +20,7 @@ use crate::hal::std::tcp_server::{ #[derive(Default)] pub struct CobsTcParser {} -impl TcpTcParser for CobsTcParser { +impl TcpTcParser for CobsTcParser { fn handle_tc_parsing( &mut self, tc_buffer: &mut [u8], @@ -32,7 +32,7 @@ impl TcpTcParser for CobsTcParser { // Reader vec full, need to parse for packets. conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets( &mut tc_buffer[..current_write_idx], - tc_receiver, + tc_receiver.upcast_mut(), next_write_idx, ) .map_err(|e| TcpTmtcError::TcError(e))?; @@ -105,7 +105,7 @@ impl TcpTmSender for CobsTmSender { /// packets even from a data stream which is split up. The server wil use the /// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a /// generic TC receiver. -pub struct TcpTmtcInCobsServer { +pub struct TcpTmtcInCobsServer { generic_server: TcpTmtcGenericServer, } @@ -122,8 +122,8 @@ impl TcpTmtcInCobsServer { /// forwarded to this TC receiver. pub fn new( cfg: ServerConfig, - tm_source: Box + Send>, - tc_receiver: Box + Send>, + tm_source: Box>, + tc_receiver: Box>, ) -> Result> { Ok(Self { generic_server: TcpTmtcGenericServer::new( @@ -168,7 +168,7 @@ mod tests { use crate::{ hal::std::tcp_server::ServerConfig, parsers::tests::{INVERTED_PACKET, SIMPLE_PACKET}, - tmtc::{ReceivesTcCore, TmPacketSource}, + tmtc::{ReceivesTcCore, TmPacketSourceCore}, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; use cobs::encode; @@ -201,7 +201,7 @@ mod tests { } } - impl TmPacketSource for SyncTmSource { + impl TmPacketSourceCore for SyncTmSource { type Error = (); fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { diff --git a/satrs-core/src/parsers/ccsds.rs b/satrs-core/src/parsers/ccsds.rs index 1a90a7b..71e6aec 100644 --- a/satrs-core/src/parsers/ccsds.rs +++ b/satrs-core/src/parsers/ccsds.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use hashbrown::HashSet; use spacepackets::PacketId; -use crate::tmtc::ReceivesTc; +use crate::tmtc::ReceivesTcCore; pub trait PacketIdLookup { fn validate(&self, packet_id: u16) -> bool; @@ -68,7 +68,7 @@ impl PacketIdLookup for &[PacketId] { pub fn parse_buffer_for_ccsds_space_packets( buf: &mut [u8], packet_id_lookup: &dyn PacketIdLookup, - tc_receiver: &mut dyn ReceivesTc, + tc_receiver: &mut dyn ReceivesTcCore, next_write_idx: &mut usize, ) -> Result { let packets_found = 0; diff --git a/satrs-core/src/parsers/cobs.rs b/satrs-core/src/parsers/cobs.rs index bf99ce6..e57a719 100644 --- a/satrs-core/src/parsers/cobs.rs +++ b/satrs-core/src/parsers/cobs.rs @@ -1,4 +1,4 @@ -use crate::tmtc::ReceivesTc; +use crate::tmtc::ReceivesTcCore; use cobs::decode_in_place; /// This function parses a given buffer for COBS encoded packets. The packet structure is @@ -13,7 +13,7 @@ use cobs::decode_in_place; /// The parser will write all packets which were decoded successfully to the given `tc_receiver`. pub fn parse_buffer_for_cobs_encoded_packets( buf: &mut [u8], - tc_receiver: &mut dyn ReceivesTc, + tc_receiver: &mut dyn ReceivesTcCore, next_write_idx: &mut usize, ) -> Result { let mut start_index_packet = 0; diff --git a/satrs-core/src/tmtc/mod.rs b/satrs-core/src/tmtc/mod.rs index 2f8bdf3..04f4299 100644 --- a/satrs-core/src/tmtc/mod.rs +++ b/satrs-core/src/tmtc/mod.rs @@ -72,12 +72,33 @@ pub trait ReceivesTcCore { /// Extension trait of [ReceivesTcCore] which allows downcasting by implementing [Downcast] and /// is also sendable. #[cfg(feature = "alloc")] -pub trait ReceivesTc: ReceivesTcCore + Downcast + Send {} +pub trait ReceivesTc: ReceivesTcCore + Downcast + Send { + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn ReceivesTcCore; + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn ReceivesTcCore; +} /// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature /// is enabled. #[cfg(feature = "alloc")] -impl ReceivesTc for T where T: ReceivesTcCore + Send + 'static {} +impl ReceivesTc for T +where + T: ReceivesTcCore + Send + 'static, +{ + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn ReceivesTcCore { + self + } + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn ReceivesTcCore { + self + } +} #[cfg(feature = "alloc")] impl_downcast!(ReceivesTc assoc Error); @@ -95,7 +116,38 @@ pub trait ReceivesCcsdsTc { /// Generic trait for a TM packet source, with no restrictions on the type of TM. /// Implementors write the telemetry into the provided buffer and return the size of the telemetry. -pub trait TmPacketSource { +pub trait TmPacketSourceCore { type Error; fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result; } + +/// Extension trait of [TmPacketSourceCore] which allows downcasting by implementing [Downcast] and +/// is also sendable. +#[cfg(feature = "alloc")] +pub trait TmPacketSource: TmPacketSourceCore + Downcast + Send { + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn TmPacketSourceCore; + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn TmPacketSourceCore; +} + +/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature +/// is enabled. +#[cfg(feature = "alloc")] +impl TmPacketSource for T +where + T: TmPacketSourceCore + Send + 'static, +{ + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn TmPacketSourceCore { + self + } + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn TmPacketSourceCore { + self + } +} -- 2.43.0 From 3aba6b42761b49efd02848a9bcbcba1a07e72732 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 11:45:27 +0200 Subject: [PATCH 38/50] stupid doctests --- .../src/hal/std/tcp_with_cobs_server.rs | 150 +++++++++++++++++- satrs-core/src/parsers/ccsds.rs | 3 + 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 52f277b..de15884 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -105,6 +105,153 @@ impl TcpTmSender for CobsTmSender { /// packets even from a data stream which is split up. The server wil use the /// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a /// generic TC receiver. +/// +/// ## Example +/// +/// ``` +/// use core::{ +/// sync::atomic::{AtomicBool, Ordering}, +/// time::Duration, +/// }; +/// use std::{ +/// io::{Read, Write}, +/// net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, +/// sync::Mutex, +/// thread, +/// }; +/// +/// use satrs_core::{ +/// hal::std::tcp_server::{ServerConfig, TcpTmtcInCobsServer}, +/// tmtc::{ReceivesTcCore, TmPacketSourceCore}, +/// }; +/// use std::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; +/// use cobs::encode; +/// +/// #[derive(Default, Clone)] +/// struct SyncTcCacher { +/// tc_queue: Arc>>>, +/// } +/// impl ReceivesTcCore for SyncTcCacher { +/// type Error = (); +/// +/// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { +/// let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed"); +/// tc_queue.push_back(tc_raw.to_vec()); +/// Ok(()) +/// } +/// } +/// +/// #[derive(Default, Clone)] +/// struct SyncTmSource { +/// tm_queue: Arc>>>, +/// } +/// +/// impl SyncTmSource { +/// pub(crate) fn add_tm(&mut self, tm: &[u8]) { +/// let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec"); +/// tm_queue.push_back(tm.to_vec()); +/// } +/// } +/// +/// impl TmPacketSourceCore for SyncTmSource { +/// type Error = (); +/// +/// fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { +/// let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); +/// if !tm_queue.is_empty() { +/// let next_vec = tm_queue.front().unwrap(); +/// if buffer.len() < next_vec.len() { +/// panic!( +/// "provided buffer too small, must be at least {} bytes", +/// next_vec.len() +/// ); +/// } +/// let next_vec = tm_queue.pop_front().unwrap(); +/// buffer[0..next_vec.len()].copy_from_slice(&next_vec); +/// return Ok(next_vec.len()); +/// } +/// Ok(0) +/// } +/// } +/// +/// fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { +/// encoded_buf[*current_idx] = 0; +/// *current_idx += 1; +/// *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); +/// encoded_buf[*current_idx] = 0; +/// *current_idx += 1; +/// } +/// +/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; +/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 4, 1]; +/// +/// let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); +/// let tc_receiver = SyncTcCacher::default(); +/// let tm_source = SyncTmSource::default(); +/// let mut tcp_server = TcpTmtcInCobsServer::new( +/// ServerConfig::new(auto_port_addr, Duration::from_millis(2), 1024, 1024), +/// Box::new(tm_source), +/// Box::new(tc_receiver.clone()), +/// ).expect("TCP server generation failed"); +/// let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); +/// let conn_handled: Arc = Default::default(); +/// let set_if_done = conn_handled.clone(); +/// +/// // Call the connection handler in separate thread, does block. +/// thread::spawn(move || { +/// let result = tcp_server.handle_next_connection(); +/// if result.is_err() { +/// panic!("handling connection failed: {:?}", result.unwrap_err()); +/// } +/// let conn_result = result.unwrap(); +/// assert_eq!(conn_result.num_received_tcs, 1); +/// assert_eq!(conn_result.num_sent_tms, 1); +/// set_if_done.store(true, Ordering::Relaxed); +/// }); +/// +/// // Send TC to server now. +/// let mut encoded_buf: [u8; 16] = [0; 16]; +/// let mut current_idx = 0; +/// encode_simple_packet(&mut encoded_buf, &mut current_idx); +/// let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); +/// stream +/// .write_all(&encoded_buf[..current_idx]) +/// .expect("writing to TCP server failed"); +/// // Done with writing. +/// stream +/// .shutdown(std::net::Shutdown::Write) +/// .expect("shutting down write failed"); +/// let mut read_buf: [u8; 16] = [0; 16]; +/// let read_len = stream.read(&mut read_buf).expect("read failed"); +/// drop(stream); +/// +/// // 1 byte encoding overhead, 2 sentinel bytes. +/// assert_eq!(read_len, 8); +/// assert_eq!(read_buf[0], 0); +/// assert_eq!(read_buf[read_len - 1], 0); +/// let decoded_len = +/// cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); +/// assert_eq!(decoded_len, 5); +/// // Skip first sentinel byte. +/// assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); +/// // A certain amount of time is allowed for the transaction to complete. +/// for _ in 0..3 { +/// if !conn_handled.load(Ordering::Relaxed) { +/// thread::sleep(Duration::from_millis(5)); +/// } +/// } +/// if !conn_handled.load(Ordering::Relaxed) { +/// panic!("connection was not handled properly"); +/// } +/// // Check that the packet was received and decoded successfully. +/// let mut tc_queue = tc_receiver +/// .tc_queue +/// .lock() +/// .expect("locking tc queue failed"); +/// assert_eq!(tc_queue.len(), 1); +/// assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); +/// drop(tc_queue); +/// ``` pub struct TcpTmtcInCobsServer { generic_server: TcpTmtcGenericServer, } @@ -335,6 +482,8 @@ mod tests { .expect("shutting down write failed"); let mut read_buf: [u8; 16] = [0; 16]; let read_len = stream.read(&mut read_buf).expect("read failed"); + drop(stream); + // 1 byte encoding overhead, 2 sentinel bytes. assert_eq!(read_len, 8); assert_eq!(read_buf[0], 0); @@ -345,7 +494,6 @@ mod tests { // Skip first sentinel byte. assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); - drop(stream); // A certain amount of time is allowed for the transaction to complete. for _ in 0..3 { if !conn_handled.load(Ordering::Relaxed) { diff --git a/satrs-core/src/parsers/ccsds.rs b/satrs-core/src/parsers/ccsds.rs index 71e6aec..fe84f3f 100644 --- a/satrs-core/src/parsers/ccsds.rs +++ b/satrs-core/src/parsers/ccsds.rs @@ -99,3 +99,6 @@ pub fn parse_buffer_for_ccsds_space_packets( } Ok(packets_found) } + +#[cfg(test)] +mod tests {} -- 2.43.0 From 567a0a1cf5863961cc8fe78499029d24206c7a2d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 14:05:25 +0200 Subject: [PATCH 39/50] doc example works --- .../src/hal/std/tcp_with_cobs_server.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index de15884..3888c53 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -108,6 +108,18 @@ impl TcpTmSender for CobsTmSender { /// /// ## Example /// +/// This is a more elaborate example. It showcases all major features of the TCP server by +/// performing following steps: +/// +/// 1. It defines both a TC receiver and a TM source which are [Sync]. +/// 2. A telemetry packet is inserted into the TM source. The packet will be handled by the +/// TCP server after handling all TCs. +/// 3. It instantiates the TCP server on localhost with automatic port assignment and assigns +/// the TC receiver and TM source created previously. +/// 4. It moves the TCP server to a different thread and calls the +/// [TcpTmtcInCobsServer::handle_next_connection] call inside that thread +/// 5. The main threads connects to the server, sends a test telecommand and then reads back +/// the test telemetry insertd in to the TM source previously. /// ``` /// use core::{ /// sync::atomic::{AtomicBool, Ordering}, @@ -174,6 +186,7 @@ impl TcpTmSender for CobsTmSender { /// } /// } /// +/// // Simple COBS encoder which also inserts the sentinel bytes. /// fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { /// encoded_buf[*current_idx] = 0; /// *current_idx += 1; @@ -187,7 +200,9 @@ impl TcpTmSender for CobsTmSender { /// /// let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); /// let tc_receiver = SyncTcCacher::default(); -/// let tm_source = SyncTmSource::default(); +/// let mut tm_source = SyncTmSource::default(); +/// // Insert a telemetry packet which will be read back by the client at a later stage. +/// tm_source.add_tm(&INVERTED_PACKET); /// let mut tcp_server = TcpTmtcInCobsServer::new( /// ServerConfig::new(auto_port_addr, Duration::from_millis(2), 1024, 1024), /// Box::new(tm_source), @@ -204,8 +219,9 @@ impl TcpTmSender for CobsTmSender { /// panic!("handling connection failed: {:?}", result.unwrap_err()); /// } /// let conn_result = result.unwrap(); -/// assert_eq!(conn_result.num_received_tcs, 1); -/// assert_eq!(conn_result.num_sent_tms, 1); +/// assert_eq!(conn_result.num_received_tcs, 1, "No TC received"); +/// assert_eq!(conn_result.num_sent_tms, 1, "No TM received"); +/// // Signal the main thread we are done. /// set_if_done.store(true, Ordering::Relaxed); /// }); /// -- 2.43.0 From afd7999d5cb51f37b39b9971cbd6623285860f71 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 14:57:11 +0200 Subject: [PATCH 40/50] added missing tests --- .../src/hal/std/tcp_with_cobs_server.rs | 89 ++++++++++++++----- satrs-core/src/parsers/cobs.rs | 7 +- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 3888c53..494ebad 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -148,6 +148,7 @@ impl TcpTmSender for CobsTmSender { /// /// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { /// let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed"); +/// println!("Received TC: {:x?}", tc_raw); /// tc_queue.push_back(tc_raw.to_vec()); /// Ok(()) /// } @@ -178,6 +179,7 @@ impl TcpTmSender for CobsTmSender { /// next_vec.len() /// ); /// } +/// println!("Sending and encoding TM: {:x?}", next_vec); /// let next_vec = tm_queue.pop_front().unwrap(); /// buffer[0..next_vec.len()].copy_from_slice(&next_vec); /// return Ok(next_vec.len()); @@ -386,9 +388,17 @@ mod tests { } fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encode_packet(&SIMPLE_PACKET, encoded_buf, current_idx) + } + + fn encode_inverted_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encode_packet(&INVERTED_PACKET, encoded_buf, current_idx) + } + + fn encode_packet(packet: &[u8], encoded_buf: &mut [u8], current_idx: &mut usize) { encoded_buf[*current_idx] = 0; *current_idx += 1; - *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + *current_idx += encode(packet, &mut encoded_buf[*current_idx..]); encoded_buf[*current_idx] = 0; *current_idx += 1; } @@ -401,7 +411,7 @@ mod tests { TcpTmtcInCobsServer::new( ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024), Box::new(tm_source), - Box::new(tc_receiver.clone()), + Box::new(tc_receiver), ) .expect("TCP server generation failed") } @@ -411,8 +421,7 @@ mod tests { let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let tm_source = SyncTmSource::default(); - let mut tcp_server = - generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); + let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source); let dest_addr = tcp_server .local_addr() .expect("retrieving dest addr failed"); @@ -458,14 +467,12 @@ mod tests { } #[test] - fn test_server_basic_no_tm_multi_tc() {} - - #[test] - fn test_server_basic_with_tm() { + fn test_server_basic_multi_tm_multi_tc() { let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let tc_receiver = SyncTcCacher::default(); let mut tm_source = SyncTmSource::default(); tm_source.add_tm(&INVERTED_PACKET); + tm_source.add_tm(&SIMPLE_PACKET); let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone()); let dest_addr = tcp_server @@ -480,15 +487,19 @@ mod tests { panic!("handling connection failed: {:?}", result.unwrap_err()); } let conn_result = result.unwrap(); - assert_eq!(conn_result.num_received_tcs, 1); - assert_eq!(conn_result.num_sent_tms, 1); + assert_eq!(conn_result.num_received_tcs, 2, "Not enough TCs received"); + assert_eq!(conn_result.num_sent_tms, 2, "Not enough TMs received"); set_if_done.store(true, Ordering::Relaxed); }); // Send TC to server now. - let mut encoded_buf: [u8; 16] = [0; 16]; + let mut encoded_buf: [u8; 32] = [0; 32]; let mut current_idx = 0; encode_simple_packet(&mut encoded_buf, &mut current_idx); + encode_inverted_packet(&mut encoded_buf, &mut current_idx); let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); + stream + .set_read_timeout(Some(Duration::from_millis(10))) + .expect("setting reas timeout failed"); stream .write_all(&encoded_buf[..current_idx]) .expect("writing to TCP server failed"); @@ -497,18 +508,49 @@ mod tests { .shutdown(std::net::Shutdown::Write) .expect("shutting down write failed"); let mut read_buf: [u8; 16] = [0; 16]; - let read_len = stream.read(&mut read_buf).expect("read failed"); - drop(stream); + let mut read_len_total = 0; + // Timeout ensures this does not block forever. + while read_len_total < 16 { + let read_len = stream.read(&mut read_buf).expect("read failed"); + read_len_total += read_len; + // Read until full expected size is available. + if read_len == 16 { + // Read first TM packet. + current_idx = 0; + assert_eq!(read_len, 16); + assert_eq!(read_buf[0], 0); + current_idx += 1; + let mut dec_report = cobs::decode_in_place_report(&mut read_buf[current_idx..]) + .expect("COBS decoding failed"); + assert_eq!(dec_report.dst_used, 5); + // Skip first sentinel byte. + assert_eq!( + &read_buf[current_idx..current_idx + INVERTED_PACKET.len()], + &INVERTED_PACKET + ); + current_idx += dec_report.src_used; + // End sentinel. + assert_eq!(read_buf[current_idx], 0, "invalid sentinel end byte"); + current_idx += 1; - // 1 byte encoding overhead, 2 sentinel bytes. - assert_eq!(read_len, 8); - assert_eq!(read_buf[0], 0); - assert_eq!(read_buf[read_len - 1], 0); - let decoded_len = - cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); - assert_eq!(decoded_len, 5); - // Skip first sentinel byte. - assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); + // Read second TM packet. + assert_eq!(read_buf[current_idx], 0); + current_idx += 1; + dec_report = cobs::decode_in_place_report(&mut read_buf[current_idx..]) + .expect("COBS decoding failed"); + assert_eq!(dec_report.dst_used, 5); + // Skip first sentinel byte. + assert_eq!( + &read_buf[current_idx..current_idx + SIMPLE_PACKET.len()], + &SIMPLE_PACKET + ); + current_idx += dec_report.src_used; + // End sentinel. + assert_eq!(read_buf[current_idx], 0); + break; + } + } + drop(stream); // A certain amount of time is allowed for the transaction to complete. for _ in 0..3 { @@ -524,8 +566,9 @@ mod tests { .tc_queue .lock() .expect("locking tc queue failed"); - assert_eq!(tc_queue.len(), 1); + assert_eq!(tc_queue.len(), 2); assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); + assert_eq!(tc_queue.pop_front().unwrap(), &INVERTED_PACKET); drop(tc_queue); } } diff --git a/satrs-core/src/parsers/cobs.rs b/satrs-core/src/parsers/cobs.rs index e57a719..5d906a8 100644 --- a/satrs-core/src/parsers/cobs.rs +++ b/satrs-core/src/parsers/cobs.rs @@ -44,11 +44,10 @@ pub fn parse_buffer_for_cobs_encoded_packets( } } } - // Split frame at the end for a multi-packet frame. Move it to the front of the buffer. + // Move split frame at the end to the front of the buffer. if start_index_packet > 0 && start_found && packets_found > 0 { - let (first_seg, last_seg) = buf.split_at_mut(start_index_packet - 1); - first_seg[..last_seg.len()].copy_from_slice(last_seg); - *next_write_idx = last_seg.len(); + buf.copy_within(start_index_packet - 1.., 0); + *next_write_idx = buf.len() - start_index_packet + 1; } Ok(packets_found) } -- 2.43.0 From f314e69ed84706aea412b7de5a343d133c12826f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 15:04:46 +0200 Subject: [PATCH 41/50] lets see --- .../src/hal/std/tcp_with_cobs_server.rs | 164 +---------------- satrs-core/tests/tcp_server_cobs.rs | 165 ++++++++++++++++++ 2 files changed, 167 insertions(+), 162 deletions(-) create mode 100644 satrs-core/tests/tcp_server_cobs.rs diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 494ebad..ec080e2 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -108,168 +108,8 @@ impl TcpTmSender for CobsTmSender { /// /// ## Example /// -/// This is a more elaborate example. It showcases all major features of the TCP server by -/// performing following steps: -/// -/// 1. It defines both a TC receiver and a TM source which are [Sync]. -/// 2. A telemetry packet is inserted into the TM source. The packet will be handled by the -/// TCP server after handling all TCs. -/// 3. It instantiates the TCP server on localhost with automatic port assignment and assigns -/// the TC receiver and TM source created previously. -/// 4. It moves the TCP server to a different thread and calls the -/// [TcpTmtcInCobsServer::handle_next_connection] call inside that thread -/// 5. The main threads connects to the server, sends a test telecommand and then reads back -/// the test telemetry insertd in to the TM source previously. -/// ``` -/// use core::{ -/// sync::atomic::{AtomicBool, Ordering}, -/// time::Duration, -/// }; -/// use std::{ -/// io::{Read, Write}, -/// net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, -/// sync::Mutex, -/// thread, -/// }; -/// -/// use satrs_core::{ -/// hal::std::tcp_server::{ServerConfig, TcpTmtcInCobsServer}, -/// tmtc::{ReceivesTcCore, TmPacketSourceCore}, -/// }; -/// use std::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; -/// use cobs::encode; -/// -/// #[derive(Default, Clone)] -/// struct SyncTcCacher { -/// tc_queue: Arc>>>, -/// } -/// impl ReceivesTcCore for SyncTcCacher { -/// type Error = (); -/// -/// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { -/// let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed"); -/// println!("Received TC: {:x?}", tc_raw); -/// tc_queue.push_back(tc_raw.to_vec()); -/// Ok(()) -/// } -/// } -/// -/// #[derive(Default, Clone)] -/// struct SyncTmSource { -/// tm_queue: Arc>>>, -/// } -/// -/// impl SyncTmSource { -/// pub(crate) fn add_tm(&mut self, tm: &[u8]) { -/// let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec"); -/// tm_queue.push_back(tm.to_vec()); -/// } -/// } -/// -/// impl TmPacketSourceCore for SyncTmSource { -/// type Error = (); -/// -/// fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { -/// let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); -/// if !tm_queue.is_empty() { -/// let next_vec = tm_queue.front().unwrap(); -/// if buffer.len() < next_vec.len() { -/// panic!( -/// "provided buffer too small, must be at least {} bytes", -/// next_vec.len() -/// ); -/// } -/// println!("Sending and encoding TM: {:x?}", next_vec); -/// let next_vec = tm_queue.pop_front().unwrap(); -/// buffer[0..next_vec.len()].copy_from_slice(&next_vec); -/// return Ok(next_vec.len()); -/// } -/// Ok(0) -/// } -/// } -/// -/// // Simple COBS encoder which also inserts the sentinel bytes. -/// fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { -/// encoded_buf[*current_idx] = 0; -/// *current_idx += 1; -/// *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); -/// encoded_buf[*current_idx] = 0; -/// *current_idx += 1; -/// } -/// -/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; -/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 4, 1]; -/// -/// let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); -/// let tc_receiver = SyncTcCacher::default(); -/// let mut tm_source = SyncTmSource::default(); -/// // Insert a telemetry packet which will be read back by the client at a later stage. -/// tm_source.add_tm(&INVERTED_PACKET); -/// let mut tcp_server = TcpTmtcInCobsServer::new( -/// ServerConfig::new(auto_port_addr, Duration::from_millis(2), 1024, 1024), -/// Box::new(tm_source), -/// Box::new(tc_receiver.clone()), -/// ).expect("TCP server generation failed"); -/// let dest_addr = tcp_server.local_addr().expect("retrieving dest addr failed"); -/// let conn_handled: Arc = Default::default(); -/// let set_if_done = conn_handled.clone(); -/// -/// // Call the connection handler in separate thread, does block. -/// thread::spawn(move || { -/// let result = tcp_server.handle_next_connection(); -/// if result.is_err() { -/// panic!("handling connection failed: {:?}", result.unwrap_err()); -/// } -/// let conn_result = result.unwrap(); -/// assert_eq!(conn_result.num_received_tcs, 1, "No TC received"); -/// assert_eq!(conn_result.num_sent_tms, 1, "No TM received"); -/// // Signal the main thread we are done. -/// set_if_done.store(true, Ordering::Relaxed); -/// }); -/// -/// // Send TC to server now. -/// let mut encoded_buf: [u8; 16] = [0; 16]; -/// let mut current_idx = 0; -/// encode_simple_packet(&mut encoded_buf, &mut current_idx); -/// let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); -/// stream -/// .write_all(&encoded_buf[..current_idx]) -/// .expect("writing to TCP server failed"); -/// // Done with writing. -/// stream -/// .shutdown(std::net::Shutdown::Write) -/// .expect("shutting down write failed"); -/// let mut read_buf: [u8; 16] = [0; 16]; -/// let read_len = stream.read(&mut read_buf).expect("read failed"); -/// drop(stream); -/// -/// // 1 byte encoding overhead, 2 sentinel bytes. -/// assert_eq!(read_len, 8); -/// assert_eq!(read_buf[0], 0); -/// assert_eq!(read_buf[read_len - 1], 0); -/// let decoded_len = -/// cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); -/// assert_eq!(decoded_len, 5); -/// // Skip first sentinel byte. -/// assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); -/// // A certain amount of time is allowed for the transaction to complete. -/// for _ in 0..3 { -/// if !conn_handled.load(Ordering::Relaxed) { -/// thread::sleep(Duration::from_millis(5)); -/// } -/// } -/// if !conn_handled.load(Ordering::Relaxed) { -/// panic!("connection was not handled properly"); -/// } -/// // Check that the packet was received and decoded successfully. -/// let mut tc_queue = tc_receiver -/// .tc_queue -/// .lock() -/// .expect("locking tc queue failed"); -/// assert_eq!(tc_queue.len(), 1); -/// assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); -/// drop(tc_queue); -/// ``` +/// The [TCP COBS integration](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_server_cobs.rs) +/// test also serves as the example application for this module. pub struct TcpTmtcInCobsServer { generic_server: TcpTmtcGenericServer, } diff --git a/satrs-core/tests/tcp_server_cobs.rs b/satrs-core/tests/tcp_server_cobs.rs new file mode 100644 index 0000000..bac9437 --- /dev/null +++ b/satrs-core/tests/tcp_server_cobs.rs @@ -0,0 +1,165 @@ +//! This serves as both an integration test and an example application showcasing all major +//! features of the TCP COBS server by performing following steps: +//! +//! 1. It defines both a TC receiver and a TM source which are [Sync]. +//! 2. A telemetry packet is inserted into the TM source. The packet will be handled by the +//! TCP server after handling all TCs. +//! 3. It instantiates the TCP server on localhost with automatic port assignment and assigns +//! the TC receiver and TM source created previously. +//! 4. It moves the TCP server to a different thread and calls the +//! [TcpTmtcInCobsServer::handle_next_connection] call inside that thread +//! 5. The main threads connects to the server, sends a test telecommand and then reads back +//! the test telemetry insertd in to the TM source previously. +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; +use std::{ + io::{Read, Write}, + net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, + sync::Mutex, + thread, +}; + +use cobs::encode; +use satrs_core::{ + hal::std::tcp_server::{ServerConfig, TcpTmtcInCobsServer}, + tmtc::{ReceivesTcCore, TmPacketSourceCore}, +}; +use std::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; + +#[derive(Default, Clone)] +struct SyncTcCacher { + tc_queue: Arc>>>, +} +impl ReceivesTcCore for SyncTcCacher { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed"); + println!("Received TC: {:x?}", tc_raw); + tc_queue.push_back(tc_raw.to_vec()); + Ok(()) + } +} + +#[derive(Default, Clone)] +struct SyncTmSource { + tm_queue: Arc>>>, +} + +impl SyncTmSource { + pub(crate) fn add_tm(&mut self, tm: &[u8]) { + let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec"); + tm_queue.push_back(tm.to_vec()); + } +} + +impl TmPacketSourceCore for SyncTmSource { + type Error = (); + + fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result { + let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed"); + if !tm_queue.is_empty() { + let next_vec = tm_queue.front().unwrap(); + if buffer.len() < next_vec.len() { + panic!( + "provided buffer too small, must be at least {} bytes", + next_vec.len() + ); + } + println!("Sending and encoding TM: {:x?}", next_vec); + let next_vec = tm_queue.pop_front().unwrap(); + buffer[0..next_vec.len()].copy_from_slice(&next_vec); + return Ok(next_vec.len()); + } + Ok(0) + } +} + +// Simple COBS encoder which also inserts the sentinel bytes. +fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; +} + +const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; +const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 4, 1]; + +fn main() { + let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + let tc_receiver = SyncTcCacher::default(); + let mut tm_source = SyncTmSource::default(); + // Insert a telemetry packet which will be read back by the client at a later stage. + tm_source.add_tm(&INVERTED_PACKET); + let mut tcp_server = TcpTmtcInCobsServer::new( + ServerConfig::new(auto_port_addr, Duration::from_millis(2), 1024, 1024), + Box::new(tm_source), + Box::new(tc_receiver.clone()), + ) + .expect("TCP server generation failed"); + let dest_addr = tcp_server + .local_addr() + .expect("retrieving dest addr failed"); + let conn_handled: Arc = Default::default(); + let set_if_done = conn_handled.clone(); + + // Call the connection handler in separate thread, does block. + thread::spawn(move || { + let result = tcp_server.handle_next_connection(); + if result.is_err() { + panic!("handling connection failed: {:?}", result.unwrap_err()); + } + let conn_result = result.unwrap(); + assert_eq!(conn_result.num_received_tcs, 1, "No TC received"); + assert_eq!(conn_result.num_sent_tms, 1, "No TM received"); + // Signal the main thread we are done. + set_if_done.store(true, Ordering::Relaxed); + }); + + // Send TC to server now. + let mut encoded_buf: [u8; 16] = [0; 16]; + let mut current_idx = 0; + encode_simple_packet(&mut encoded_buf, &mut current_idx); + let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); + stream + .write_all(&encoded_buf[..current_idx]) + .expect("writing to TCP server failed"); + // Done with writing. + stream + .shutdown(std::net::Shutdown::Write) + .expect("shutting down write failed"); + let mut read_buf: [u8; 16] = [0; 16]; + let read_len = stream.read(&mut read_buf).expect("read failed"); + drop(stream); + + // 1 byte encoding overhead, 2 sentinel bytes. + assert_eq!(read_len, 8); + assert_eq!(read_buf[0], 0); + assert_eq!(read_buf[read_len - 1], 0); + let decoded_len = + cobs::decode_in_place(&mut read_buf[1..read_len]).expect("COBS decoding failed"); + assert_eq!(decoded_len, 5); + // Skip first sentinel byte. + assert_eq!(&read_buf[1..1 + INVERTED_PACKET.len()], &INVERTED_PACKET); + // A certain amount of time is allowed for the transaction to complete. + for _ in 0..3 { + if !conn_handled.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(5)); + } + } + if !conn_handled.load(Ordering::Relaxed) { + panic!("connection was not handled properly"); + } + // Check that the packet was received and decoded successfully. + let mut tc_queue = tc_receiver + .tc_queue + .lock() + .expect("locking tc queue failed"); + assert_eq!(tc_queue.len(), 1); + assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET); + drop(tc_queue); +} -- 2.43.0 From 4017b5afc24cc807f92cd1e81c61df591b713cee Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 15:18:20 +0200 Subject: [PATCH 42/50] better module name --- satrs-core/src/{parsers => encoding}/ccsds.rs | 0 satrs-core/src/{parsers => encoding}/cobs.rs | 16 ++++++++++++-- satrs-core/src/encoding/mod.rs | 22 +++++++++++++++++++ .../src/hal/std/tcp_with_cobs_server.rs | 4 ++-- satrs-core/src/lib.rs | 2 +- satrs-core/src/parsers/mod.rs | 21 ------------------ 6 files changed, 39 insertions(+), 26 deletions(-) rename satrs-core/src/{parsers => encoding}/ccsds.rs (100%) rename satrs-core/src/{parsers => encoding}/cobs.rs (92%) create mode 100644 satrs-core/src/encoding/mod.rs delete mode 100644 satrs-core/src/parsers/mod.rs diff --git a/satrs-core/src/parsers/ccsds.rs b/satrs-core/src/encoding/ccsds.rs similarity index 100% rename from satrs-core/src/parsers/ccsds.rs rename to satrs-core/src/encoding/ccsds.rs diff --git a/satrs-core/src/parsers/cobs.rs b/satrs-core/src/encoding/cobs.rs similarity index 92% rename from satrs-core/src/parsers/cobs.rs rename to satrs-core/src/encoding/cobs.rs index 5d906a8..0b0e04a 100644 --- a/satrs-core/src/parsers/cobs.rs +++ b/satrs-core/src/encoding/cobs.rs @@ -1,5 +1,17 @@ use crate::tmtc::ReceivesTcCore; -use cobs::decode_in_place; +use cobs::{decode_in_place, encode}; + +/// This function encodes the given packet with COBS and also wraps the encoded packet with +/// the sentinel value 0. It can be used repeatedly on the same encoded buffer by expecting +/// and incrementing the mutable reference of the current packet index. This is also used +/// to retrieve the total encoded size. +pub fn encode_packet_with_cobs(packet: &[u8], encoded_buf: &mut [u8], current_idx: &mut usize) { + encoded_buf[*current_idx] = 0; + *current_idx += 1; + *current_idx += encode(packet, &mut encoded_buf[*current_idx..]); + encoded_buf[*current_idx] = 0; + *current_idx += 1; +} /// This function parses a given buffer for COBS encoded packets. The packet structure is /// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: @@ -58,7 +70,7 @@ pub(crate) mod tests { use cobs::encode; use crate::{ - parsers::tests::{encode_simple_packet, INVERTED_PACKET, SIMPLE_PACKET}, + encoding::tests::{encode_simple_packet, INVERTED_PACKET, SIMPLE_PACKET}, tmtc::ReceivesTcCore, }; diff --git a/satrs-core/src/encoding/mod.rs b/satrs-core/src/encoding/mod.rs new file mode 100644 index 0000000..33d2727 --- /dev/null +++ b/satrs-core/src/encoding/mod.rs @@ -0,0 +1,22 @@ +pub mod ccsds; +pub mod cobs; + +pub use crate::encoding::ccsds::parse_buffer_for_ccsds_space_packets; +pub use crate::encoding::cobs::parse_buffer_for_cobs_encoded_packets; + +#[cfg(test)] +pub(crate) mod tests { + use super::cobs::encode_packet_with_cobs; + + pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; + pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; + + pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encode_packet_with_cobs(&SIMPLE_PACKET, encoded_buf, current_idx) + } + + #[allow(dead_code)] + pub(crate) fn encode_inverted_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { + encode_packet_with_cobs(&INVERTED_PACKET, encoded_buf, current_idx) + } +} diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index ec080e2..357b07d 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -8,7 +8,7 @@ use std::net::TcpListener; use std::net::TcpStream; use std::vec::Vec; -use crate::parsers::parse_buffer_for_cobs_encoded_packets; +use crate::encoding::parse_buffer_for_cobs_encoded_packets; use crate::tmtc::ReceivesTc; use crate::tmtc::TmPacketSource; @@ -171,8 +171,8 @@ mod tests { }; use crate::{ + encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET}, hal::std::tcp_server::ServerConfig, - parsers::tests::{INVERTED_PACKET, SIMPLE_PACKET}, tmtc::{ReceivesTcCore, TmPacketSourceCore}, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; diff --git a/satrs-core/src/lib.rs b/satrs-core/src/lib.rs index 50e9d42..8ae56e5 100644 --- a/satrs-core/src/lib.rs +++ b/satrs-core/src/lib.rs @@ -20,6 +20,7 @@ extern crate downcast_rs; #[cfg(any(feature = "std", test))] extern crate std; +pub mod encoding; pub mod error; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] @@ -33,7 +34,6 @@ pub mod hk; pub mod mode; pub mod objects; pub mod params; -pub mod parsers; pub mod pool; pub mod power; pub mod pus; diff --git a/satrs-core/src/parsers/mod.rs b/satrs-core/src/parsers/mod.rs deleted file mode 100644 index c25060a..0000000 --- a/satrs-core/src/parsers/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod ccsds; -pub mod cobs; - -pub use crate::parsers::ccsds::parse_buffer_for_ccsds_space_packets; -pub use crate::parsers::cobs::parse_buffer_for_cobs_encoded_packets; - -#[cfg(test)] -pub(crate) mod tests { - use cobs::encode; - - pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; - pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; - - pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encoded_buf[*current_idx] = 0; - *current_idx += 1; - *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); - encoded_buf[*current_idx] = 0; - *current_idx += 1; - } -} -- 2.43.0 From 1851b7427972723c8b0d9ce68d5b8b9b9c59c738 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 15:20:14 +0200 Subject: [PATCH 43/50] use new public function --- satrs-core/src/encoding/mod.rs | 2 +- satrs-core/tests/tcp_server_cobs.rs | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/satrs-core/src/encoding/mod.rs b/satrs-core/src/encoding/mod.rs index 33d2727..20973f2 100644 --- a/satrs-core/src/encoding/mod.rs +++ b/satrs-core/src/encoding/mod.rs @@ -2,7 +2,7 @@ pub mod ccsds; pub mod cobs; pub use crate::encoding::ccsds::parse_buffer_for_ccsds_space_packets; -pub use crate::encoding::cobs::parse_buffer_for_cobs_encoded_packets; +pub use crate::encoding::cobs::{encode_packet_with_cobs, parse_buffer_for_cobs_encoded_packets}; #[cfg(test)] pub(crate) mod tests { diff --git a/satrs-core/tests/tcp_server_cobs.rs b/satrs-core/tests/tcp_server_cobs.rs index bac9437..5956fb3 100644 --- a/satrs-core/tests/tcp_server_cobs.rs +++ b/satrs-core/tests/tcp_server_cobs.rs @@ -21,8 +21,8 @@ use std::{ thread, }; -use cobs::encode; use satrs_core::{ + encoding::cobs::encode_packet_with_cobs, hal::std::tcp_server::{ServerConfig, TcpTmtcInCobsServer}, tmtc::{ReceivesTcCore, TmPacketSourceCore}, }; @@ -77,15 +77,6 @@ impl TmPacketSourceCore for SyncTmSource { } } -// Simple COBS encoder which also inserts the sentinel bytes. -fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encoded_buf[*current_idx] = 0; - *current_idx += 1; - *current_idx += encode(&SIMPLE_PACKET, &mut encoded_buf[*current_idx..]); - encoded_buf[*current_idx] = 0; - *current_idx += 1; -} - const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 4, 1]; @@ -123,7 +114,7 @@ fn main() { // Send TC to server now. let mut encoded_buf: [u8; 16] = [0; 16]; let mut current_idx = 0; - encode_simple_packet(&mut encoded_buf, &mut current_idx); + encode_packet_with_cobs(&SIMPLE_PACKET, &mut encoded_buf, &mut current_idx); let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed"); stream .write_all(&encoded_buf[..current_idx]) -- 2.43.0 From 1517811d1352c6b7278631a589fe866608d9fc3b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 15:22:37 +0200 Subject: [PATCH 44/50] better docs --- satrs-core/src/hal/std/tcp_with_cobs_server.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/satrs-core/src/hal/std/tcp_with_cobs_server.rs b/satrs-core/src/hal/std/tcp_with_cobs_server.rs index 357b07d..fc44ebf 100644 --- a/satrs-core/src/hal/std/tcp_with_cobs_server.rs +++ b/satrs-core/src/hal/std/tcp_with_cobs_server.rs @@ -104,7 +104,8 @@ impl TcpTmSender for CobsTmSender { /// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full /// packets even from a data stream which is split up. The server wil use the /// [parse_buffer_for_cobs_encoded_packets] function to parse for packets and pass them to a -/// generic TC receiver. +/// generic TC receiver. The user can use [crate::encoding::encode_packet_with_cobs] to encode +/// telecommands sent to the server. /// /// ## Example /// -- 2.43.0 From 3f73b73ded52c392b21ea646070023b71f96b6c3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 15:45:06 +0200 Subject: [PATCH 45/50] add doctest for encoder function --- satrs-core/src/encoding/cobs.rs | 36 +++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/satrs-core/src/encoding/cobs.rs b/satrs-core/src/encoding/cobs.rs index 0b0e04a..6fa4901 100644 --- a/satrs-core/src/encoding/cobs.rs +++ b/satrs-core/src/encoding/cobs.rs @@ -1,16 +1,48 @@ use crate::tmtc::ReceivesTcCore; -use cobs::{decode_in_place, encode}; +use cobs::{decode_in_place, encode, max_encoding_length}; /// This function encodes the given packet with COBS and also wraps the encoded packet with /// the sentinel value 0. It can be used repeatedly on the same encoded buffer by expecting /// and incrementing the mutable reference of the current packet index. This is also used /// to retrieve the total encoded size. -pub fn encode_packet_with_cobs(packet: &[u8], encoded_buf: &mut [u8], current_idx: &mut usize) { +/// +/// This function will return [false] if the given encoding buffer is not large enough to hold +/// the encoded buffer and the two sentinel bytes and [true] if the encoding was successfull. +/// +/// ## Example +/// +/// ``` +/// use cobs::decode_in_place_report; +/// use satrs_core::encoding::{encode_packet_with_cobs}; +// +/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; +/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; +/// +/// let mut encoding_buf: [u8; 32] = [0; 32]; +/// let mut current_idx = 0; +/// assert!(encode_packet_with_cobs(&SIMPLE_PACKET, &mut encoding_buf, &mut current_idx)); +/// assert!(encode_packet_with_cobs(&INVERTED_PACKET, &mut encoding_buf, &mut current_idx)); +/// assert_eq!(encoding_buf[0], 0); +/// let dec_report = decode_in_place_report(&mut encoding_buf[1..]).expect("decoding failed"); +/// assert_eq!(encoding_buf[1 + dec_report.src_used], 0); +/// assert_eq!(dec_report.dst_used, 5); +/// assert_eq!(current_idx, 16); +/// ``` +pub fn encode_packet_with_cobs( + packet: &[u8], + encoded_buf: &mut [u8], + current_idx: &mut usize, +) -> bool { + let max_encoding_len = max_encoding_length(packet.len()); + if *current_idx + max_encoding_len + 2 > encoded_buf.len() { + return false; + } encoded_buf[*current_idx] = 0; *current_idx += 1; *current_idx += encode(packet, &mut encoded_buf[*current_idx..]); encoded_buf[*current_idx] = 0; *current_idx += 1; + true } /// This function parses a given buffer for COBS encoded packets. The packet structure is -- 2.43.0 From e717999cb0c5b4db4e4f98ab7e9968c4938f0758 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 20 Sep 2023 17:14:07 +0200 Subject: [PATCH 46/50] cobs --- satrs-core/src/encoding/cobs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satrs-core/src/encoding/cobs.rs b/satrs-core/src/encoding/cobs.rs index 6fa4901..28ed3a1 100644 --- a/satrs-core/src/encoding/cobs.rs +++ b/satrs-core/src/encoding/cobs.rs @@ -48,7 +48,7 @@ pub fn encode_packet_with_cobs( /// This function parses a given buffer for COBS encoded packets. The packet structure is /// expected to be like this, assuming a sentinel value of 0 as the packet delimiter: /// -/// 0 | ... Packet Data ... | 0 | 0 | ... Packet Data ... | 0 +/// 0 | ... Encoded Packet Data ... | 0 | 0 | ... Encoded Packet Data ... | 0 /// /// This function is also able to deal with broken tail packets at the end. If broken tail /// packets are detected, they are moved to the front of the buffer, and the write index for -- 2.43.0 From c3bce2774757212afb7db629d1be92b936947fd0 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 21 Sep 2023 15:11:00 +0200 Subject: [PATCH 47/50] push some progress --- satrs-core/src/encoding/ccsds.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/satrs-core/src/encoding/ccsds.rs b/satrs-core/src/encoding/ccsds.rs index fe84f3f..141e81a 100644 --- a/satrs-core/src/encoding/ccsds.rs +++ b/satrs-core/src/encoding/ccsds.rs @@ -55,6 +55,7 @@ impl PacketIdLookup for &[PacketId] { false } } + /// This function parses a given buffer for tightly packed CCSDS space packets. It uses the /// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then /// uses the length field of the packet to extract CCSDS packets. @@ -101,4 +102,12 @@ pub fn parse_buffer_for_ccsds_space_packets( } #[cfg(test)] -mod tests {} +mod tests { + use spacepackets::{ecss::tc::PusTcCreator, SpHeader}; + + #[test] + fn test_basic() { + let sph = SpHeader::tc_unseg(0x02, 0, 0); + let ping_tc = PusTcCreator::new_simple(sph, service, subservice, app_data, set_ccsds_len) + } +} -- 2.43.0 From 39621cf855af75d8de251d404aa571f27c5895fc Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 21 Sep 2023 16:34:18 +0200 Subject: [PATCH 48/50] added more tests --- Cargo.toml | 3 +- satrs-core/src/encoding/ccsds.rs | 117 +++++++++++++++++++++++-------- satrs-core/src/encoding/cobs.rs | 20 +----- satrs-core/src/encoding/mod.rs | 22 +++++- 4 files changed, 112 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c9d35a8..eaeb356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] - +resolver = "2" members = [ "satrs-core", "satrs-mib", @@ -9,3 +9,4 @@ members = [ exclude = [ "satrs-example-stm32f3-disco", ] + diff --git a/satrs-core/src/encoding/ccsds.rs b/satrs-core/src/encoding/ccsds.rs index 141e81a..b6ccef6 100644 --- a/satrs-core/src/encoding/ccsds.rs +++ b/satrs-core/src/encoding/ccsds.rs @@ -17,13 +17,6 @@ impl PacketIdLookup for Vec { } } -#[cfg(feature = "alloc")] -impl PacketIdLookup for Vec { - fn validate(&self, packet_id: u16) -> bool { - self.contains(&PacketId::from(packet_id)) - } -} - #[cfg(feature = "alloc")] impl PacketIdLookup for HashSet { fn validate(&self, packet_id: u16) -> bool { @@ -31,6 +24,18 @@ impl PacketIdLookup for HashSet { } } +impl PacketIdLookup for [u16] { + fn validate(&self, packet_id: u16) -> bool { + self.binary_search(&packet_id).is_ok() + } +} + +#[cfg(feature = "alloc")] +impl PacketIdLookup for Vec { + fn validate(&self, packet_id: u16) -> bool { + self.contains(&PacketId::from(packet_id)) + } +} #[cfg(feature = "alloc")] impl PacketIdLookup for HashSet { fn validate(&self, packet_id: u16) -> bool { @@ -38,21 +43,9 @@ impl PacketIdLookup for HashSet { } } -impl PacketIdLookup for &[u16] { +impl PacketIdLookup for [PacketId] { fn validate(&self, packet_id: u16) -> bool { - if self.binary_search(&packet_id).is_ok() { - return true; - } - false - } -} - -impl PacketIdLookup for &[PacketId] { - fn validate(&self, packet_id: u16) -> bool { - if self.binary_search(&PacketId::from(packet_id)).is_ok() { - return true; - } - false + self.binary_search(&PacketId::from(packet_id)).is_ok() } } @@ -65,14 +58,16 @@ impl PacketIdLookup for &[PacketId] { /// detected, they are moved to the front of the buffer, and the write index for future write /// operations will be written to the `next_write_idx` argument. /// -/// The parser will write all packets which were decoded successfully to the given `tc_receiver`. +/// The parser will write all packets which were decoded successfully to the given `tc_receiver` +/// and return the number of packets found. If the [ReceivesTcCore::pass_tc] calls fails, the +/// error will be returned. pub fn parse_buffer_for_ccsds_space_packets( buf: &mut [u8], - packet_id_lookup: &dyn PacketIdLookup, - tc_receiver: &mut dyn ReceivesTcCore, + packet_id_lookup: &(impl PacketIdLookup + ?Sized), + tc_receiver: &mut impl ReceivesTcCore, next_write_idx: &mut usize, ) -> Result { - let packets_found = 0; + let mut packets_found = 0; let mut current_idx = 0; let buf_len = buf.len(); loop { @@ -86,6 +81,7 @@ pub fn parse_buffer_for_ccsds_space_packets( let packet_size = length_field + 7; if (current_idx + packet_size as usize) < buf_len { tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?; + packets_found += 1; } else { // Move packet to start of buffer if applicable. if current_idx > 0 { @@ -103,11 +99,76 @@ pub fn parse_buffer_for_ccsds_space_packets( #[cfg(test)] mod tests { - use spacepackets::{ecss::tc::PusTcCreator, SpHeader}; + use spacepackets::{ + ecss::{tc::PusTcCreator, SerializablePusPacket}, + PacketId, SpHeader, + }; + + use crate::encoding::tests::TcCacher; + + use super::parse_buffer_for_ccsds_space_packets; + + const TEST_APID: u16 = 0x02; #[test] fn test_basic() { - let sph = SpHeader::tc_unseg(0x02, 0, 0); - let ping_tc = PusTcCreator::new_simple(sph, service, subservice, app_data, set_ccsds_len) + let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); + let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); + let mut buffer: [u8; 32] = [0; 32]; + let packet_len = ping_tc + .write_to_bytes(&mut buffer) + .expect("writing packet failed"); + let valid_packet_ids = [PacketId::const_tc(true, TEST_APID)]; + let mut tc_cacher = TcCacher::default(); + let mut next_write_idx = 0; + let parse_result = parse_buffer_for_ccsds_space_packets( + &mut buffer, + valid_packet_ids.as_slice(), + &mut tc_cacher, + &mut next_write_idx, + ); + assert!(parse_result.is_ok()); + let parsed_packets = parse_result.unwrap(); + assert_eq!(parsed_packets, 1); + assert_eq!(tc_cacher.tc_queue.len(), 1); + assert_eq!( + tc_cacher.tc_queue.pop_front().unwrap(), + buffer[..packet_len] + ); + } + + #[test] + fn test_multi_pakcet() { + let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); + let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); + let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true); + let mut buffer: [u8; 32] = [0; 32]; + let packet_len_ping = ping_tc + .write_to_bytes(&mut buffer) + .expect("writing packet failed"); + let _packet_len_action = action_tc + .write_to_bytes(&mut buffer[packet_len_ping..]) + .expect("writing packet failed"); + let valid_packet_ids = [PacketId::const_tc(true, TEST_APID)]; + let mut tc_cacher = TcCacher::default(); + let mut next_write_idx = 0; + let parse_result = parse_buffer_for_ccsds_space_packets( + &mut buffer, + valid_packet_ids.as_slice(), + &mut tc_cacher, + &mut next_write_idx, + ); + assert!(parse_result.is_ok()); + let parsed_packets = parse_result.unwrap(); + assert_eq!(parsed_packets, 1); + assert_eq!(tc_cacher.tc_queue.len(), 2); + assert_eq!( + tc_cacher.tc_queue.pop_front().unwrap(), + buffer[..packet_len_ping] + ); + assert_eq!( + tc_cacher.tc_queue.pop_front().unwrap(), + buffer[packet_len_ping..] + ); } } diff --git a/satrs-core/src/encoding/cobs.rs b/satrs-core/src/encoding/cobs.rs index 28ed3a1..2645745 100644 --- a/satrs-core/src/encoding/cobs.rs +++ b/satrs-core/src/encoding/cobs.rs @@ -98,30 +98,12 @@ pub fn parse_buffer_for_cobs_encoded_packets( #[cfg(test)] pub(crate) mod tests { - use alloc::{collections::VecDeque, vec::Vec}; use cobs::encode; - use crate::{ - encoding::tests::{encode_simple_packet, INVERTED_PACKET, SIMPLE_PACKET}, - tmtc::ReceivesTcCore, - }; + use crate::encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET}; use super::parse_buffer_for_cobs_encoded_packets; - #[derive(Default)] - struct TcCacher { - tc_queue: VecDeque>, - } - - impl ReceivesTcCore for TcCacher { - type Error = (); - - fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { - self.tc_queue.push_back(tc_raw.to_vec()); - Ok(()) - } - } - #[test] fn test_parsing_simple_packet() { let mut test_sender = TcCacher::default(); diff --git a/satrs-core/src/encoding/mod.rs b/satrs-core/src/encoding/mod.rs index 20973f2..94e3dee 100644 --- a/satrs-core/src/encoding/mod.rs +++ b/satrs-core/src/encoding/mod.rs @@ -6,17 +6,35 @@ pub use crate::encoding::cobs::{encode_packet_with_cobs, parse_buffer_for_cobs_e #[cfg(test)] pub(crate) mod tests { + use alloc::{collections::VecDeque, vec::Vec}; + + use crate::tmtc::ReceivesTcCore; + use super::cobs::encode_packet_with_cobs; pub(crate) const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5]; pub(crate) const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1]; + #[derive(Default)] + pub(crate) struct TcCacher { + pub(crate) tc_queue: VecDeque>, + } + + impl ReceivesTcCore for TcCacher { + type Error = (); + + fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> { + self.tc_queue.push_back(tc_raw.to_vec()); + Ok(()) + } + } + pub(crate) fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encode_packet_with_cobs(&SIMPLE_PACKET, encoded_buf, current_idx) + encode_packet_with_cobs(&SIMPLE_PACKET, encoded_buf, current_idx); } #[allow(dead_code)] pub(crate) fn encode_inverted_packet(encoded_buf: &mut [u8], current_idx: &mut usize) { - encode_packet_with_cobs(&INVERTED_PACKET, encoded_buf, current_idx) + encode_packet_with_cobs(&INVERTED_PACKET, encoded_buf, current_idx); } } -- 2.43.0 From 0d49dbcc2a556127ecc13b15170b13ade28cf5ad Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 21 Sep 2023 16:44:52 +0200 Subject: [PATCH 49/50] Another test --- satrs-core/src/encoding/ccsds.rs | 61 +++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/satrs-core/src/encoding/ccsds.rs b/satrs-core/src/encoding/ccsds.rs index b6ccef6..7012e1f 100644 --- a/satrs-core/src/encoding/ccsds.rs +++ b/satrs-core/src/encoding/ccsds.rs @@ -108,17 +108,20 @@ mod tests { use super::parse_buffer_for_ccsds_space_packets; - const TEST_APID: u16 = 0x02; + const TEST_APID_0: u16 = 0x02; + const TEST_APID_1: u16 = 0x10; + const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0); + const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1); #[test] fn test_basic() { - let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); + let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap(); let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); let mut buffer: [u8; 32] = [0; 32]; let packet_len = ping_tc .write_to_bytes(&mut buffer) .expect("writing packet failed"); - let valid_packet_ids = [PacketId::const_tc(true, TEST_APID)]; + let valid_packet_ids = [TEST_PACKET_ID_0]; let mut tc_cacher = TcCacher::default(); let mut next_write_idx = 0; let parse_result = parse_buffer_for_ccsds_space_packets( @@ -138,18 +141,18 @@ mod tests { } #[test] - fn test_multi_pakcet() { - let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); + fn test_multi_packet() { + let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap(); let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true); let mut buffer: [u8; 32] = [0; 32]; let packet_len_ping = ping_tc .write_to_bytes(&mut buffer) .expect("writing packet failed"); - let _packet_len_action = action_tc + let packet_len_action = action_tc .write_to_bytes(&mut buffer[packet_len_ping..]) .expect("writing packet failed"); - let valid_packet_ids = [PacketId::const_tc(true, TEST_APID)]; + let valid_packet_ids = [TEST_PACKET_ID_0]; let mut tc_cacher = TcCacher::default(); let mut next_write_idx = 0; let parse_result = parse_buffer_for_ccsds_space_packets( @@ -160,7 +163,7 @@ mod tests { ); assert!(parse_result.is_ok()); let parsed_packets = parse_result.unwrap(); - assert_eq!(parsed_packets, 1); + assert_eq!(parsed_packets, 2); assert_eq!(tc_cacher.tc_queue.len(), 2); assert_eq!( tc_cacher.tc_queue.pop_front().unwrap(), @@ -168,7 +171,47 @@ mod tests { ); assert_eq!( tc_cacher.tc_queue.pop_front().unwrap(), - buffer[packet_len_ping..] + buffer[packet_len_ping..packet_len_ping + packet_len_action] ); } + + #[test] + fn test_multi_apid() { + let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap(); + let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); + sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap(); + let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true); + let mut buffer: [u8; 32] = [0; 32]; + let packet_len_ping = ping_tc + .write_to_bytes(&mut buffer) + .expect("writing packet failed"); + let packet_len_action = action_tc + .write_to_bytes(&mut buffer[packet_len_ping..]) + .expect("writing packet failed"); + let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1]; + let mut tc_cacher = TcCacher::default(); + let mut next_write_idx = 0; + let parse_result = parse_buffer_for_ccsds_space_packets( + &mut buffer, + valid_packet_ids.as_slice(), + &mut tc_cacher, + &mut next_write_idx, + ); + assert!(parse_result.is_ok()); + let parsed_packets = parse_result.unwrap(); + assert_eq!(parsed_packets, 2); + assert_eq!(tc_cacher.tc_queue.len(), 2); + assert_eq!( + tc_cacher.tc_queue.pop_front().unwrap(), + buffer[..packet_len_ping] + ); + assert_eq!( + tc_cacher.tc_queue.pop_front().unwrap(), + buffer[packet_len_ping..packet_len_ping + packet_len_action] + ); + } + + #[test] + fn test_split_packet() { + } } -- 2.43.0 From 216874d32913d18b0635408c393be62916104760 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 21 Sep 2023 18:08:40 +0200 Subject: [PATCH 50/50] CCSDS parser working well --- satrs-core/src/encoding/ccsds.rs | 62 +++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/satrs-core/src/encoding/ccsds.rs b/satrs-core/src/encoding/ccsds.rs index 7012e1f..f8f775f 100644 --- a/satrs-core/src/encoding/ccsds.rs +++ b/satrs-core/src/encoding/ccsds.rs @@ -54,9 +54,9 @@ impl PacketIdLookup for [PacketId] { /// uses the length field of the packet to extract CCSDS packets. /// /// This function is also able to deal with broken tail packets at the end as long a the parser -/// can read the full 6 bytes which constitue a space packet header. If broken tail packets are -/// detected, they are moved to the front of the buffer, and the write index for future write -/// operations will be written to the `next_write_idx` argument. +/// can read the full 7 bytes which constitue a space packet header plus one byte minimal size. +/// If broken tail packets are detected, they are moved to the front of the buffer, and the write +/// index for future write operations will be written to the `next_write_idx` argument. /// /// The parser will write all packets which were decoded successfully to the given `tc_receiver` /// and return the number of packets found. If the [ReceivesTcCore::pass_tc] calls fails, the @@ -67,6 +67,7 @@ pub fn parse_buffer_for_ccsds_space_packets( tc_receiver: &mut impl ReceivesTcCore, next_write_idx: &mut usize, ) -> Result { + *next_write_idx = 0; let mut packets_found = 0; let mut current_idx = 0; let buf_len = buf.len(); @@ -86,7 +87,7 @@ pub fn parse_buffer_for_ccsds_space_packets( // Move packet to start of buffer if applicable. if current_idx > 0 { buf.copy_within(current_idx.., 0); - *next_write_idx = current_idx; + *next_write_idx = buf.len() - current_idx; } } current_idx += packet_size as usize; @@ -212,6 +213,57 @@ mod tests { } #[test] - fn test_split_packet() { + fn test_split_packet_multi() { + let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap(); + let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); + sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap(); + let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true); + let mut buffer: [u8; 32] = [0; 32]; + let packet_len_ping = ping_tc + .write_to_bytes(&mut buffer) + .expect("writing packet failed"); + let packet_len_action = action_tc + .write_to_bytes(&mut buffer[packet_len_ping..]) + .expect("writing packet failed"); + let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1]; + let mut tc_cacher = TcCacher::default(); + let mut next_write_idx = 0; + let parse_result = parse_buffer_for_ccsds_space_packets( + &mut buffer[..packet_len_ping + packet_len_action - 4], + valid_packet_ids.as_slice(), + &mut tc_cacher, + &mut next_write_idx, + ); + assert!(parse_result.is_ok()); + let parsed_packets = parse_result.unwrap(); + assert_eq!(parsed_packets, 1); + assert_eq!(tc_cacher.tc_queue.len(), 1); + // The broken packet was moved to the start, so the next write index should be after the + // last segment missing 4 bytes. + assert_eq!(next_write_idx, packet_len_action - 4); + } + + #[test] + fn test_one_split_packet() { + let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap(); + let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true); + let mut buffer: [u8; 32] = [0; 32]; + let packet_len_ping = ping_tc + .write_to_bytes(&mut buffer) + .expect("writing packet failed"); + let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1]; + let mut tc_cacher = TcCacher::default(); + let mut next_write_idx = 0; + let parse_result = parse_buffer_for_ccsds_space_packets( + &mut buffer[..packet_len_ping - 4], + valid_packet_ids.as_slice(), + &mut tc_cacher, + &mut next_write_idx, + ); + assert_eq!(next_write_idx, 0); + assert!(parse_result.is_ok()); + let parsed_packets = parse_result.unwrap(); + assert_eq!(parsed_packets, 0); + assert_eq!(tc_cacher.tc_queue.len(), 0); } } -- 2.43.0