Skip to content

Commit 975ddc7

Browse files
feat: Add block related functionality in block.rs
1 parent c4ed3de commit 975ddc7

File tree

1 file changed

+360
-0
lines changed

1 file changed

+360
-0
lines changed

src/rust/lib_ccxr/src/net/block.rs

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
use super::NetError;
2+
use crate::time::units::Timestamp;
3+
4+
use num_enum::{IntoPrimitive, TryFromPrimitive};
5+
6+
use std::borrow::Cow;
7+
use std::fmt::{self, Display, Formatter};
8+
use std::io::{self, Write};
9+
10+
/// Default port to be used when port number is not specified for TCP.
11+
pub const DEFAULT_TCP_PORT: u16 = 2048;
12+
13+
/// The amount of time to wait for a response before reseting the connection.
14+
pub const NO_RESPONSE_INTERVAL: Timestamp = Timestamp::from_millis(20_000);
15+
16+
/// The time interval between sending ping messages.
17+
pub const PING_INTERVAL: Timestamp = Timestamp::from_millis(3_000);
18+
19+
/// The size of the `length` section of the [`Block`]'s byte format.
20+
///
21+
/// See [`BlockStream`] for more information.
22+
pub const LEN_SIZE: usize = 10;
23+
24+
/// The sequence of bytes used to denote the end of a [`Block`] in its byte format.
25+
///
26+
/// See [`BlockStream`] for more information.
27+
pub const END_MARKER: &str = "\r\n";
28+
29+
/// Represents the different kinds of commands that could be sent or received in a block.
30+
#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
31+
#[repr(u8)]
32+
pub enum Command {
33+
Ok = 1,
34+
35+
/// Used to send password just after making a TCP connection.
36+
Password = 2,
37+
BinMode = 3,
38+
39+
/// Used to send description just after making a TCP connection.
40+
CcDesc = 4,
41+
BinHeader = 5,
42+
BinData = 6,
43+
44+
/// Used to send EPG metadata across network.
45+
EpgData = 7,
46+
Error = 51,
47+
UnknownCommand = 52,
48+
WrongPassword = 53,
49+
ConnLimit = 54,
50+
51+
/// Used to send ping messages to check network connectivity.
52+
Ping = 55,
53+
}
54+
55+
/// Represents the smallest unit of data that could be sent or received from network.
56+
///
57+
/// A [`Block`] consists of a [`Command`] and some binary data associated with it. The kind of
58+
/// block is denoted by its [`Command`]. The binary data has different formats or information based
59+
/// on the kind of [`Command`].
60+
///
61+
/// Any subtitle data, metadata, ping, etc, that needs to be sent by network goes through in the
62+
/// form of a [`Block`]. See [`BlockStream`] for more information on how a [`Block`] is sent or
63+
/// received.
64+
pub struct Block<'a> {
65+
command: Command,
66+
data: Cow<'a, [u8]>,
67+
}
68+
69+
impl<'a> Block<'a> {
70+
fn new(command: Command, data: &'a [u8]) -> Block<'a> {
71+
Block {
72+
command,
73+
data: Cow::from(data),
74+
}
75+
}
76+
77+
fn new_owned(command: Command, data: Vec<u8>) -> Block<'a> {
78+
Block {
79+
command,
80+
data: Cow::from(data),
81+
}
82+
}
83+
84+
/// Returns the kind of [`Block`] denoted by its [`Command`].
85+
pub fn command(&self) -> Command {
86+
self.command
87+
}
88+
89+
/// Returns the associated data of [`Block`].
90+
pub fn data(&self) -> &[u8] {
91+
&self.data
92+
}
93+
94+
/// Create a new [`Ping`](Command::Ping) Block.
95+
pub fn ping() -> Block<'a> {
96+
Block::new_owned(Command::Ping, vec![])
97+
}
98+
99+
/// Create a new [`BinHeader`](Command::BinHeader) Block along with `header` data.
100+
pub fn bin_header(header: &'a [u8]) -> Block<'a> {
101+
Block::new(Command::BinHeader, header)
102+
}
103+
104+
/// Create a new [`BinData`](Command::BinData) Block along with `data`.
105+
pub fn bin_data(data: &'a [u8]) -> Block<'a> {
106+
Block::new(Command::BinData, data)
107+
}
108+
109+
/// Create a new [`Password`](Command::Password) Block along with `password` data.
110+
///
111+
/// The data of the returned [`Block`] will consist of `password` encoded as UTF-8 bytes which
112+
/// is not nul-terminated.
113+
///
114+
/// # Examples
115+
/// ```
116+
/// # use lib_ccxr::net::Block;
117+
/// let b = Block::password("A");
118+
/// assert_eq!(b.data(), &[b'A']);
119+
/// ```
120+
pub fn password(password: &'a str) -> Block<'a> {
121+
Block::new(Command::Password, password.as_bytes())
122+
}
123+
124+
/// Create a new [`CcDesc`](Command::CcDesc) Block along with `desc` data.
125+
///
126+
/// The data of the returned [`Block`] will consist of `desc` encoded as UTF-8 bytes which is
127+
/// not nul-terminated.
128+
///
129+
/// # Examples
130+
/// ```
131+
/// # use lib_ccxr::net::Block;
132+
/// let b = Block::cc_desc("Teletext");
133+
/// assert_eq!(b.data(), &[b'T', b'e', b'l', b'e', b't', b'e', b'x', b't']);
134+
/// ```
135+
pub fn cc_desc(desc: &'a str) -> Block<'a> {
136+
Block::new(Command::CcDesc, desc.as_bytes())
137+
}
138+
139+
/// Create a new [`EpgData`](Command::EpgData) Block along with the related metadata used in
140+
/// EPG.
141+
///
142+
/// All the parameters are encoded as UTF-8 bytes which are nul-terminated. If a parameter is
143+
/// [`None`], then it is considered to be equivalent to an empty String. All these
144+
/// nul-terminated UTF-8 bytes are placed one after the other in the order of `start`, `stop`,
145+
/// `title`, `desc`, `lang`, `category` with nul character acting as the seperator between
146+
/// these sections.
147+
///
148+
/// # Examples
149+
/// ```
150+
/// # use lib_ccxr::net::Block;
151+
/// let b = Block::epg_data("A", "B", Some("C"), None, Some("D"), None);
152+
/// assert_eq!(b.data(), &[b'A', b'\0', b'B', b'\0', b'C', b'\0', b'\0', b'D', b'\0', b'\0']);
153+
/// ```
154+
pub fn epg_data(
155+
start: &str,
156+
stop: &str,
157+
title: Option<&str>,
158+
desc: Option<&str>,
159+
lang: Option<&str>,
160+
category: Option<&str>,
161+
) -> Block<'a> {
162+
let title = title.unwrap_or("");
163+
let desc = desc.unwrap_or("");
164+
let lang = lang.unwrap_or("");
165+
let category = category.unwrap_or("");
166+
167+
// Plus 1 to accomodate space for the nul character
168+
let start_len = start.len() + 1;
169+
let stop_len = stop.len() + 1;
170+
let title_len = title.len() + 1;
171+
let desc_len = desc.len() + 1;
172+
let lang_len = lang.len() + 1;
173+
let category_len = category.len() + 1;
174+
175+
let total_len = start_len + stop_len + title_len + desc_len + lang_len + category_len;
176+
let mut data = Vec::with_capacity(total_len);
177+
178+
data.extend_from_slice(start.as_bytes());
179+
data.extend_from_slice("\0".as_bytes());
180+
data.extend_from_slice(stop.as_bytes());
181+
data.extend_from_slice("\0".as_bytes());
182+
data.extend_from_slice(title.as_bytes());
183+
data.extend_from_slice("\0".as_bytes());
184+
data.extend_from_slice(desc.as_bytes());
185+
data.extend_from_slice("\0".as_bytes());
186+
data.extend_from_slice(lang.as_bytes());
187+
data.extend_from_slice("\0".as_bytes());
188+
data.extend_from_slice(category.as_bytes());
189+
data.extend_from_slice("\0".as_bytes());
190+
191+
Block::new_owned(Command::EpgData, data)
192+
}
193+
}
194+
195+
/// The [`BlockStream`] trait allows for sending and receiving [`Block`]s across the network.
196+
///
197+
/// The only two implementers of [`BlockStream`] are [`SendTarget`] and [`RecvSource`] which are
198+
/// used for sending and receiving blocks respectively.
199+
///
200+
/// This trait provides an abstraction over the different interfaces of [`TcpStream`] and
201+
/// [`UdpSocket`]. The implementers only need to implement the functionality to send and receive
202+
/// bytes through network by [`BlockStream::send`] and [`BlockStream::recv`]. The functionality to
203+
/// send and receive [`Block`] will be automatically made available by [`BlockStream::send_block`]
204+
/// and [`BlockStream::recv_block`].
205+
///
206+
/// A [`Block`] is sent or received across the network using a byte format. Since a [`Block`]
207+
/// consists of `command` and variable sized `data`, it is encoded in the following way.
208+
///
209+
/// | Section | Length | Description |
210+
/// |------------|--------------|---------------------------------------------------------------------------|
211+
/// | command | 1 | The `command` enconded as its corresponding byte value. |
212+
/// | length | [`LEN_SIZE`] | The length of `data` encoded as nul-terminated string form of the number. |
213+
/// | data | length | The associated `data` bytes whose meaning is dependent on `command`. |
214+
/// | end_marker | 2 | This value is always [`END_MARKER`], signifying end of current block. |
215+
///
216+
/// The only exception to the above format is a [`Ping`] [`Block`] which is encoded only with its 1-byte
217+
/// command section. It does not have length, data or end_marker sections.
218+
///
219+
/// [`SendTarget`]: super::SendTarget
220+
/// [`RecvSource`]: super::RecvSource
221+
/// [`TcpStream`]: std::net::TcpStream
222+
/// [`UdpSocket`]: std::net::UdpSocket
223+
/// [`Ping`]: Command::Ping
224+
pub trait BlockStream {
225+
/// Send the bytes in `buf` across the network.
226+
fn send(&mut self, buf: &[u8]) -> io::Result<usize>;
227+
228+
/// Receive the bytes from network and place them in `buf`.
229+
fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize>;
230+
231+
/// Send a [`Block`] across the network.
232+
///
233+
/// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool
234+
/// that indicates the status of this connection. It will be `false` if the connection shutdown
235+
/// correctly.
236+
fn send_block(&mut self, block: &Block<'_>) -> Result<bool, NetError> {
237+
let Block { command, data } = block;
238+
239+
if self.send(&[(*command).into()])? != 1 {
240+
return Ok(false);
241+
}
242+
243+
if *command == Command::Ping {
244+
return Ok(true);
245+
}
246+
247+
let mut length_part = [0; LEN_SIZE];
248+
let _ = write!(length_part.as_mut_slice(), "{}", data.len());
249+
if self.send(&length_part)? != LEN_SIZE {
250+
return Ok(false);
251+
}
252+
253+
if data.len() > 0 {
254+
if self.send(data)? != data.len() {
255+
return Ok(false);
256+
}
257+
}
258+
259+
if self.send(END_MARKER.as_bytes())? != END_MARKER.len() {
260+
return Ok(false);
261+
}
262+
263+
#[cfg(feature = "debug_out")]
264+
eprintln!("block = {}", block);
265+
266+
Ok(true)
267+
}
268+
269+
/// Receive a [`Block`] from the network.
270+
///
271+
/// It returns a [`NetError`] if some transmission failure ocurred or byte format is violated.
272+
/// It will return a [`None`] if the connection has shutdown down correctly.
273+
fn recv_block<'a>(&mut self) -> Result<Option<Block<'a>>, NetError> {
274+
let mut command_byte = [0_u8; 1];
275+
if self.recv(&mut command_byte)? != 1 {
276+
return Ok(None);
277+
}
278+
279+
let command: Command = command_byte[0]
280+
.try_into()
281+
.map_err(|_| NetError::InvalidBytes {
282+
location: "command",
283+
})?;
284+
285+
if command == Command::Ping {
286+
return Ok(Some(Block::ping()));
287+
}
288+
289+
let mut length_bytes = [0u8; LEN_SIZE];
290+
if self.recv(&mut length_bytes)? != LEN_SIZE {
291+
return Ok(None);
292+
}
293+
let end = length_bytes
294+
.iter()
295+
.position(|&x| x == b'\0')
296+
.unwrap_or(LEN_SIZE);
297+
let length: usize = String::from_utf8_lossy(&length_bytes[0..end])
298+
.parse()
299+
.map_err(|_| NetError::InvalidBytes { location: "length" })?;
300+
301+
let mut data = vec![0u8; length];
302+
if self.recv(&mut data)? != length {
303+
return Ok(None);
304+
}
305+
306+
let mut end_marker = [0u8; END_MARKER.len()];
307+
if self.recv(&mut end_marker)? != END_MARKER.len() {
308+
return Ok(None);
309+
}
310+
if end_marker != END_MARKER.as_bytes() {
311+
return Err(NetError::InvalidBytes {
312+
location: "end_marker",
313+
});
314+
}
315+
316+
let block = Block::new_owned(command, data);
317+
318+
#[cfg(feature = "debug_out")]
319+
eprintln!("{}", block);
320+
321+
Ok(Some(block))
322+
}
323+
}
324+
325+
impl Display for Block<'_> {
326+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
327+
let _ = write!(f, "[Rust] {} {} ", self.command, self.data.len());
328+
329+
if self.command != Command::BinHeader && self.command != Command::BinData {
330+
let _ = write!(f, "{} ", &*String::from_utf8_lossy(&self.data));
331+
}
332+
333+
let _ = write!(f, "\\r\\n");
334+
335+
Ok(())
336+
}
337+
}
338+
339+
impl Display for Command {
340+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
341+
let message = match self {
342+
Command::Ok => "OK",
343+
Command::Password => "PASSWORD",
344+
Command::BinMode => "BIN_MODE",
345+
Command::CcDesc => "CC_DESC",
346+
Command::BinHeader => "BIN_HEADER",
347+
Command::BinData => "BIN_DATA",
348+
Command::EpgData => "EPG_DATA",
349+
Command::Error => "ERROR",
350+
Command::UnknownCommand => "UNKNOWN_COMMAND",
351+
Command::WrongPassword => "WRONG_PASSWORD",
352+
Command::ConnLimit => "CONN_LIMIT",
353+
Command::Ping => "PING",
354+
};
355+
356+
let _ = write!(f, "{}", message);
357+
358+
Ok(())
359+
}
360+
}

0 commit comments

Comments
 (0)