192 lines
6.2 KiB
Rust
192 lines
6.2 KiB
Rust
use std::io::{self, Read, Write};
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
use std::sync::mpsc;
|
|
|
|
use mio::net::TcpStream;
|
|
use mio::{Events, Interest, Poll, Token};
|
|
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
|
use ops_sat_rs::config::{SPP_CLIENT_WIRETAPPING_RX, SPP_CLIENT_WIRETAPPING_TX};
|
|
use satrs::encoding::ccsds::parse_buffer_for_ccsds_space_packets;
|
|
use satrs::queue::GenericSendError;
|
|
use satrs::spacepackets::PacketId;
|
|
use satrs::tmtc::PacketAsVec;
|
|
use satrs::ComponentId;
|
|
use thiserror::Error;
|
|
|
|
use super::{SimpleSpValidator, TcpComponent};
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ClientError {
|
|
#[error("send error: {0}")]
|
|
Send(#[from] GenericSendError),
|
|
#[error("io error: {0}")]
|
|
Io(#[from] io::Error),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum ConnectionStatus {
|
|
Unknown,
|
|
Connected,
|
|
LostConnection,
|
|
TryingReconnect,
|
|
}
|
|
|
|
pub struct TcpSppClient {
|
|
id: ComponentId,
|
|
poll: Poll,
|
|
events: Events,
|
|
// Optional to allow periodic reconnection attempts on the TCP server.
|
|
client: Option<TcpStream>,
|
|
read_buf: [u8; 4096],
|
|
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
|
server_addr: SocketAddr,
|
|
connection: ConnectionStatus,
|
|
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
|
validator: SimpleSpValidator,
|
|
}
|
|
|
|
impl TcpSppClient {
|
|
pub fn new(
|
|
id: ComponentId,
|
|
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
|
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
|
valid_ids: &'static [PacketId],
|
|
port: u16,
|
|
) -> io::Result<Self> {
|
|
let poll = Poll::new()?;
|
|
let events = Events::with_capacity(128);
|
|
let mut client = Self {
|
|
id,
|
|
poll,
|
|
events,
|
|
client: None,
|
|
connection: ConnectionStatus::Unknown,
|
|
read_buf: [0; 4096],
|
|
server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
|
|
tm_tcp_client_rx,
|
|
tc_source_tx,
|
|
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
|
};
|
|
client.connect()?;
|
|
Ok(client)
|
|
}
|
|
|
|
pub fn connect(&mut self) -> io::Result<()> {
|
|
let mut client = TcpStream::connect(self.server_addr)?;
|
|
|
|
self.poll.registry().register(
|
|
&mut client,
|
|
Token(0),
|
|
Interest::READABLE | Interest::WRITABLE,
|
|
)?;
|
|
self.client = Some(client);
|
|
self.connection = ConnectionStatus::TryingReconnect;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn operation(&mut self) -> Result<(), ClientError> {
|
|
match self.connection {
|
|
ConnectionStatus::TryingReconnect | ConnectionStatus::Unknown => {
|
|
self.check_conn_status()?
|
|
}
|
|
ConnectionStatus::Connected => {
|
|
self.check_conn_status()?;
|
|
self.poll
|
|
.poll(&mut self.events, Some(STOP_CHECK_FREQUENCY))?;
|
|
let events: Vec<mio::event::Event> = self.events.iter().cloned().collect();
|
|
for event in events {
|
|
if event.token() == Token(0) {
|
|
if event.is_readable() {
|
|
log::warn!("TCP client is readable");
|
|
self.read_from_server()?;
|
|
}
|
|
if event.is_writable() {
|
|
log::warn!("TCP client is writable");
|
|
self.write_to_server()?;
|
|
}
|
|
}
|
|
}
|
|
return Ok(());
|
|
}
|
|
ConnectionStatus::LostConnection => self.connect()?,
|
|
};
|
|
std::thread::sleep(STOP_CHECK_FREQUENCY);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_from_server(&mut self) -> Result<(), ClientError> {
|
|
match self.client.as_mut().unwrap().read(&mut self.read_buf) {
|
|
Ok(0) => (),
|
|
Ok(read_bytes) => self.handle_read_bytstream(read_bytes)?,
|
|
Err(e) => return Err(e.into()),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_to_server(&mut self) -> io::Result<()> {
|
|
loop {
|
|
match self.tm_tcp_client_rx.try_recv() {
|
|
Ok(tm) => {
|
|
if SPP_CLIENT_WIRETAPPING_TX {
|
|
log::debug!(
|
|
"SPP TCP TX {}: {:x?}",
|
|
tm.packet.len(),
|
|
tm.packet.as_slice()
|
|
);
|
|
}
|
|
self.client.as_mut().unwrap().write_all(&tm.packet)?;
|
|
}
|
|
Err(e) => match e {
|
|
mpsc::TryRecvError::Empty => break,
|
|
mpsc::TryRecvError::Disconnected => {
|
|
log::error!("TM sender to TCP client has disconnected");
|
|
break;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn check_conn_status(&mut self) -> io::Result<()> {
|
|
match self.client.as_mut().unwrap().peer_addr() {
|
|
Ok(_) => {
|
|
if self.connection == ConnectionStatus::Unknown
|
|
|| self.connection == ConnectionStatus::TryingReconnect
|
|
{
|
|
self.connection = ConnectionStatus::Connected;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => {
|
|
if e.kind() == io::ErrorKind::NotConnected {
|
|
log::warn!("lost connection, or do not have one");
|
|
self.connection = ConnectionStatus::LostConnection;
|
|
return Ok(());
|
|
}
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn handle_read_bytstream(&mut self, read_bytes: usize) -> Result<(), ClientError> {
|
|
let mut dummy = 0;
|
|
if SPP_CLIENT_WIRETAPPING_RX {
|
|
log::debug!(
|
|
"SPP TCP RX {} bytes: {:x?}",
|
|
read_bytes,
|
|
&self.read_buf[..read_bytes]
|
|
);
|
|
}
|
|
// This parser is able to deal with broken tail packets, but we ignore those for now..
|
|
parse_buffer_for_ccsds_space_packets(
|
|
&mut self.read_buf[..read_bytes],
|
|
&self.validator,
|
|
self.id,
|
|
&self.tc_source_tx,
|
|
&mut dummy,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|