|
| 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