Skip to content

Commit 967edb6

Browse files
feat: Add target.rs module for sending data blocks related functions
1 parent 975ddc7 commit 967edb6

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

src/rust/lib_ccxr/src/net/target.rs

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
use super::{
2+
block::{Block, BlockStream, Command, DEFAULT_TCP_PORT, NO_RESPONSE_INTERVAL, PING_INTERVAL},
3+
NetError,
4+
};
5+
use crate::time::units::Timestamp;
6+
use crate::util::log::{fatal, info, ExitCause};
7+
8+
use std::io;
9+
use std::io::{Read, Write};
10+
use std::net::TcpStream;
11+
12+
/// A struct of configuration parameters to construct [`SendTarget`].
13+
#[derive(Copy, Clone, Debug)]
14+
pub struct SendTargetConfig<'a> {
15+
/// Target's IP address or hostname.
16+
pub target_addr: &'a str,
17+
18+
/// Target's port number.
19+
///
20+
/// If no port number is provided then [`DEFAULT_TCP_PORT`] will be used instead.
21+
pub port: Option<u16>,
22+
23+
/// Password to be sent after establishing connection.
24+
pub password: Option<&'a str>,
25+
26+
/// Description to sent after establishing connection.
27+
pub description: Option<&'a str>,
28+
}
29+
30+
/// A struct used for sending subtitle data across the network.
31+
///
32+
/// Even though it exposes methods from [`BlockStream`], it is recommended to not use them.
33+
/// Instead use the methods provided directly by [`SendTarget`] like [`SendTarget::send_header`],
34+
/// [`SendTarget::send_cc`], etc.
35+
///
36+
/// To create a [`SendTarget`], one must first construct a [`SendTargetConfig`].
37+
///
38+
/// ```no_run
39+
/// # use lib_ccxr::net::{SendTarget, SendTargetConfig};
40+
/// let config = SendTargetConfig {
41+
/// target_addr: "192.168.60.133",
42+
/// port: None,
43+
/// password: Some("12345678"),
44+
/// description: None,
45+
/// };
46+
/// let mut send_target = SendTarget::new(config);
47+
///
48+
/// // Once send_target is constructed, we can use it to send different kinds of data.
49+
/// # let header = &[0u8; 1];
50+
/// send_target.send_header(header);
51+
/// # let cc = &[0u8; 1];
52+
/// send_target.send_cc(cc);
53+
/// ```
54+
pub struct SendTarget<'a> {
55+
stream: Option<TcpStream>,
56+
config: SendTargetConfig<'a>,
57+
header_data: Option<Vec<u8>>,
58+
last_ping: Timestamp,
59+
last_send_ping: Timestamp,
60+
}
61+
62+
impl BlockStream for SendTarget<'_> {
63+
fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
64+
self.stream.as_mut().unwrap().write(buf)
65+
}
66+
67+
fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
68+
self.stream.as_mut().unwrap().read(buf)
69+
}
70+
}
71+
72+
impl<'a> SendTarget<'a> {
73+
/// Create a new [`SendTarget`] from the configuration parameters of [`SendTargetConfig`].
74+
///
75+
/// Note: This method attempts to connect to a server. It does not return a [`Result`]. When
76+
/// it is unable to connect to a server, it crashes instantly by calling [`fatal!`].
77+
pub fn new(config: SendTargetConfig<'a>) -> SendTarget<'a> {
78+
if config.target_addr.is_empty() {
79+
info!("Server address is not set\n");
80+
fatal!(
81+
cause = ExitCause::Failure;
82+
"Unable to connect, address passed is null\n"
83+
);
84+
}
85+
86+
let tcp_stream = TcpStream::connect((
87+
config.target_addr,
88+
config.port.unwrap_or(DEFAULT_TCP_PORT),
89+
))
90+
.unwrap_or_else(
91+
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (tcp connection error).\n"),
92+
);
93+
94+
tcp_stream.set_nonblocking(true).unwrap_or_else(
95+
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (set nonblocking).\n"),
96+
);
97+
98+
let mut send_target = SendTarget {
99+
stream: Some(tcp_stream),
100+
config,
101+
header_data: None,
102+
last_ping: Timestamp::from_millis(0),
103+
last_send_ping: Timestamp::from_millis(0),
104+
};
105+
106+
send_target.send_password().unwrap_or_else(
107+
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending password).\n"),
108+
);
109+
110+
send_target.send_description().unwrap_or_else(
111+
|_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending cc_desc).\n"),
112+
);
113+
114+
info!(
115+
"Connected to {}:{}\n",
116+
send_target.config.target_addr,
117+
send_target.config.port.unwrap_or_else(|| DEFAULT_TCP_PORT)
118+
);
119+
120+
send_target
121+
}
122+
123+
/// Consumes the [`SendTarget`] only returning its internal stream.
124+
///
125+
/// Note: Crashes if `self.stream` is not set.
126+
fn into_stream(self) -> TcpStream {
127+
self.stream.expect("TcpStream must be set")
128+
}
129+
130+
/// Send a [`BinHeader`](Command::BinHeader) [`Block`] returning if the operation was successful.
131+
pub fn send_header(&mut self, data: &[u8]) -> bool {
132+
#[cfg(feature = "debug_out")]
133+
{
134+
eprintln!("Sending header (len = {}): ", data.len());
135+
eprintln!(
136+
"File created by {:02X} version {:02X}{:02X}",
137+
data[3], data[4], data[5]
138+
);
139+
eprintln!("File format revision: {:02X}{:02X}", data[6], data[7]);
140+
}
141+
142+
if let Ok(true) = self.send_block(&Block::bin_header(data)) {
143+
} else {
144+
println!("Can't send BIN header");
145+
return false;
146+
}
147+
148+
if self.header_data.is_none() {
149+
self.header_data = Some(data.into())
150+
}
151+
152+
true
153+
}
154+
155+
/// Send a [`BinData`](Command::BinData) [`Block`] returning if the operation was successful.
156+
pub fn send_cc(&mut self, data: &[u8]) -> bool {
157+
#[cfg(feature = "debug_out")]
158+
{
159+
eprintln!("[C] Sending {} bytes", data.len());
160+
}
161+
162+
if let Ok(true) = self.send_block(&Block::bin_data(data)) {
163+
} else {
164+
println!("Can't send BIN data");
165+
return false;
166+
}
167+
168+
true
169+
}
170+
171+
/// Send a [`EpgData`](Command::EpgData) [`Block`] returning if the operation was successful.
172+
pub fn send_epg_data(
173+
&mut self,
174+
start: &str,
175+
stop: &str,
176+
title: Option<&str>,
177+
desc: Option<&str>,
178+
lang: Option<&str>,
179+
category: Option<&str>,
180+
) -> bool {
181+
let block = Block::epg_data(start, stop, title, desc, lang, category);
182+
183+
#[cfg(feature = "debug_out")]
184+
{
185+
eprintln!("[C] Sending EPG: {} bytes", block.data().len())
186+
}
187+
188+
if let Ok(true) = self.send_block(&block) {
189+
} else {
190+
eprintln!("Can't send EPG data");
191+
return false;
192+
}
193+
194+
true
195+
}
196+
197+
/// Send a [`Ping`](Command::Ping) [`Block`].
198+
///
199+
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
200+
/// that indicates the status of this connection. It will be `false` if the connection shutdown
201+
/// correctly.
202+
fn send_ping(&mut self) -> Result<bool, NetError> {
203+
self.send_block(&Block::ping())
204+
}
205+
206+
/// Send a [`Password`](Command::Password) [`Block`].
207+
///
208+
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
209+
/// that indicates the status of this connection. It will be `false` if the connection shutdown
210+
/// correctly.
211+
fn send_password(&mut self) -> Result<bool, NetError> {
212+
let password = self.config.password.unwrap_or("");
213+
self.send_block(&Block::password(password))
214+
}
215+
216+
/// Send a [`CcDesc`](Command::CcDesc) [`Block`].
217+
///
218+
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
219+
/// that indicates the status of this connection. It will be `false` if the connection shutdown
220+
/// correctly.
221+
fn send_description(&mut self) -> Result<bool, NetError> {
222+
let description = self.config.description.unwrap_or("");
223+
self.send_block(&Block::cc_desc(description))
224+
}
225+
226+
/// Check the connection health and reset connection if necessary.
227+
///
228+
/// This method determines the connection health by comparing the time since last [`Ping`]
229+
/// [`Block`] was received with [`NO_RESPONSE_INTERVAL`]. If it exceeds the
230+
/// [`NO_RESPONSE_INTERVAL`], the connection is reset.
231+
///
232+
/// This method also sends timely [`Ping`] [`Block`]s back to the server based on the
233+
/// [`PING_INTERVAL`]. This method will crash instantly with [`fatal!`] if it is unable to send
234+
/// data.
235+
///
236+
/// [`Ping`]: Command::Ping
237+
pub fn check_connection(&mut self) {
238+
let now = Timestamp::now();
239+
240+
if self.last_ping.millis() == 0 {
241+
self.last_ping = now;
242+
}
243+
244+
loop {
245+
if self
246+
.recv_block()
247+
.ok()
248+
.flatten()
249+
.map(|x| x.command() == Command::Ping)
250+
.unwrap_or(false)
251+
{
252+
#[cfg(feature = "debug_out")]
253+
{
254+
eprintln!("[S] Received PING");
255+
}
256+
self.last_ping = now;
257+
} else {
258+
break;
259+
}
260+
}
261+
262+
if now - self.last_ping > NO_RESPONSE_INTERVAL {
263+
eprintln!(
264+
"[S] No PING received from the server in {} sec, reconnecting",
265+
NO_RESPONSE_INTERVAL.seconds()
266+
);
267+
268+
self.stream
269+
.take()
270+
.unwrap()
271+
.shutdown(std::net::Shutdown::Both)
272+
.expect("Unable to shutdown the TCP Server");
273+
274+
self.stream = Some(SendTarget::new(self.config).into_stream());
275+
276+
// `self.header_data` is only temporarily taken, since it will be refilled inside
277+
// `send_header` function.
278+
if let Some(header_data) = self.header_data.take() {
279+
self.send_header(header_data.as_slice());
280+
}
281+
282+
self.last_ping = now;
283+
}
284+
285+
if now - self.last_send_ping >= PING_INTERVAL {
286+
if self.send_ping().is_err() {
287+
fatal!(cause = ExitCause::Failure; "Unable to send data\n");
288+
}
289+
290+
self.last_send_ping = now;
291+
}
292+
}
293+
}

0 commit comments

Comments
 (0)