Skip to content

Commit

Permalink
feat: Add target.rs module for sending data blocks related functions
Browse files Browse the repository at this point in the history
  • Loading branch information
IshanGrover2004 committed Aug 14, 2024
1 parent 975ddc7 commit 967edb6
Showing 1 changed file with 293 additions and 0 deletions.
293 changes: 293 additions & 0 deletions src/rust/lib_ccxr/src/net/target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
use super::{
block::{Block, BlockStream, Command, DEFAULT_TCP_PORT, NO_RESPONSE_INTERVAL, PING_INTERVAL},
NetError,
};
use crate::time::units::Timestamp;
use crate::util::log::{fatal, info, ExitCause};

use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;

/// A struct of configuration parameters to construct [`SendTarget`].
#[derive(Copy, Clone, Debug)]
pub struct SendTargetConfig<'a> {
/// Target's IP address or hostname.
pub target_addr: &'a str,

/// Target's port number.
///
/// If no port number is provided then [`DEFAULT_TCP_PORT`] will be used instead.
pub port: Option<u16>,

/// Password to be sent after establishing connection.
pub password: Option<&'a str>,

/// Description to sent after establishing connection.
pub description: Option<&'a str>,
}

/// A struct used for sending subtitle data across the network.
///
/// Even though it exposes methods from [`BlockStream`], it is recommended to not use them.
/// Instead use the methods provided directly by [`SendTarget`] like [`SendTarget::send_header`],
/// [`SendTarget::send_cc`], etc.
///
/// To create a [`SendTarget`], one must first construct a [`SendTargetConfig`].
///
/// ```no_run
/// # use lib_ccxr::net::{SendTarget, SendTargetConfig};
/// let config = SendTargetConfig {
/// target_addr: "192.168.60.133",
/// port: None,
/// password: Some("12345678"),
/// description: None,
/// };
/// let mut send_target = SendTarget::new(config);
///
/// // Once send_target is constructed, we can use it to send different kinds of data.
/// # let header = &[0u8; 1];
/// send_target.send_header(header);
/// # let cc = &[0u8; 1];
/// send_target.send_cc(cc);
/// ```
pub struct SendTarget<'a> {
stream: Option<TcpStream>,
config: SendTargetConfig<'a>,
header_data: Option<Vec<u8>>,
last_ping: Timestamp,
last_send_ping: Timestamp,
}

impl BlockStream for SendTarget<'_> {
fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.as_mut().unwrap().write(buf)
}

fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.as_mut().unwrap().read(buf)
}
}

impl<'a> SendTarget<'a> {
/// Create a new [`SendTarget`] from the configuration parameters of [`SendTargetConfig`].
///
/// Note: This method attempts to connect to a server. It does not return a [`Result`]. When
/// it is unable to connect to a server, it crashes instantly by calling [`fatal!`].
pub fn new(config: SendTargetConfig<'a>) -> SendTarget<'a> {
if config.target_addr.is_empty() {
info!("Server address is not set\n");
fatal!(
cause = ExitCause::Failure;
"Unable to connect, address passed is null\n"
);
}

let tcp_stream = TcpStream::connect((
config.target_addr,
config.port.unwrap_or(DEFAULT_TCP_PORT),
))
.unwrap_or_else(
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (tcp connection error).\n"),
);

tcp_stream.set_nonblocking(true).unwrap_or_else(
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (set nonblocking).\n"),
);

let mut send_target = SendTarget {
stream: Some(tcp_stream),
config,
header_data: None,
last_ping: Timestamp::from_millis(0),
last_send_ping: Timestamp::from_millis(0),
};

send_target.send_password().unwrap_or_else(
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending password).\n"),
);

send_target.send_description().unwrap_or_else(
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending cc_desc).\n"),
);

info!(
"Connected to {}:{}\n",
send_target.config.target_addr,
send_target.config.port.unwrap_or_else(|| DEFAULT_TCP_PORT)
);

send_target
}

/// Consumes the [`SendTarget`] only returning its internal stream.
///
/// Note: Crashes if `self.stream` is not set.
fn into_stream(self) -> TcpStream {
self.stream.expect("TcpStream must be set")
}

/// Send a [`BinHeader`](Command::BinHeader) [`Block`] returning if the operation was successful.
pub fn send_header(&mut self, data: &[u8]) -> bool {
#[cfg(feature = "debug_out")]
{
eprintln!("Sending header (len = {}): ", data.len());
eprintln!(
"File created by {:02X} version {:02X}{:02X}",
data[3], data[4], data[5]
);
eprintln!("File format revision: {:02X}{:02X}", data[6], data[7]);
}

if let Ok(true) = self.send_block(&Block::bin_header(data)) {
} else {
println!("Can't send BIN header");
return false;
}

if self.header_data.is_none() {
self.header_data = Some(data.into())
}

true
}

/// Send a [`BinData`](Command::BinData) [`Block`] returning if the operation was successful.
pub fn send_cc(&mut self, data: &[u8]) -> bool {
#[cfg(feature = "debug_out")]
{
eprintln!("[C] Sending {} bytes", data.len());
}

if let Ok(true) = self.send_block(&Block::bin_data(data)) {
} else {
println!("Can't send BIN data");
return false;
}

true
}

/// Send a [`EpgData`](Command::EpgData) [`Block`] returning if the operation was successful.
pub fn send_epg_data(
&mut self,
start: &str,
stop: &str,
title: Option<&str>,
desc: Option<&str>,
lang: Option<&str>,
category: Option<&str>,
) -> bool {
let block = Block::epg_data(start, stop, title, desc, lang, category);

#[cfg(feature = "debug_out")]
{
eprintln!("[C] Sending EPG: {} bytes", block.data().len())
}

if let Ok(true) = self.send_block(&block) {
} else {
eprintln!("Can't send EPG data");
return false;
}

true
}

/// Send a [`Ping`](Command::Ping) [`Block`].
///
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
/// that indicates the status of this connection. It will be `false` if the connection shutdown
/// correctly.
fn send_ping(&mut self) -> Result<bool, NetError> {
self.send_block(&Block::ping())
}

/// Send a [`Password`](Command::Password) [`Block`].
///
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
/// that indicates the status of this connection. It will be `false` if the connection shutdown
/// correctly.
fn send_password(&mut self) -> Result<bool, NetError> {
let password = self.config.password.unwrap_or("");
self.send_block(&Block::password(password))
}

/// Send a [`CcDesc`](Command::CcDesc) [`Block`].
///
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
/// that indicates the status of this connection. It will be `false` if the connection shutdown
/// correctly.
fn send_description(&mut self) -> Result<bool, NetError> {
let description = self.config.description.unwrap_or("");
self.send_block(&Block::cc_desc(description))
}

/// Check the connection health and reset connection if necessary.
///
/// This method determines the connection health by comparing the time since last [`Ping`]
/// [`Block`] was received with [`NO_RESPONSE_INTERVAL`]. If it exceeds the
/// [`NO_RESPONSE_INTERVAL`], the connection is reset.
///
/// This method also sends timely [`Ping`] [`Block`]s back to the server based on the
/// [`PING_INTERVAL`]. This method will crash instantly with [`fatal!`] if it is unable to send
/// data.
///
/// [`Ping`]: Command::Ping
pub fn check_connection(&mut self) {
let now = Timestamp::now();

if self.last_ping.millis() == 0 {
self.last_ping = now;
}

loop {
if self
.recv_block()
.ok()
.flatten()
.map(|x| x.command() == Command::Ping)
.unwrap_or(false)
{
#[cfg(feature = "debug_out")]
{
eprintln!("[S] Received PING");
}
self.last_ping = now;
} else {
break;
}
}

if now - self.last_ping > NO_RESPONSE_INTERVAL {
eprintln!(
"[S] No PING received from the server in {} sec, reconnecting",
NO_RESPONSE_INTERVAL.seconds()
);

self.stream
.take()
.unwrap()
.shutdown(std::net::Shutdown::Both)
.expect("Unable to shutdown the TCP Server");

self.stream = Some(SendTarget::new(self.config).into_stream());

// `self.header_data` is only temporarily taken, since it will be refilled inside
// `send_header` function.
if let Some(header_data) = self.header_data.take() {
self.send_header(header_data.as_slice());
}

self.last_ping = now;
}

if now - self.last_send_ping >= PING_INTERVAL {
if self.send_ping().is_err() {
fatal!(cause = ExitCause::Failure; "Unable to send data\n");
}

self.last_send_ping = now;
}
}
}

0 comments on commit 967edb6

Please sign in to comment.