minitel_ws/
lib.rs

1use minitel_stum::Minitel;
2
3use std::{collections::VecDeque, io::Error, net::TcpStream};
4
5use minitel_stum::{MinitelRead, MinitelWrite};
6use std::io::{ErrorKind, Result};
7use tungstenite::{Utf8Bytes, WebSocket};
8
9/// Minitel terminal used over a websocket connection
10pub type WSMinitel = Minitel<WSPort<TcpStream>>;
11
12/// Build a Minitel terminal over a websocket connection
13pub fn ws_minitel(socket: WebSocket<TcpStream>) -> WSMinitel {
14    WSMinitel::new(WSPort::new(socket))
15}
16
17pub struct WSPort<Stream: std::io::Read + std::io::Write> {
18    ws: WebSocket<Stream>,
19    buffer: VecDeque<u8>,
20}
21
22impl<Stream: std::io::Read + std::io::Write> WSPort<Stream> {
23    pub fn new(ws: WebSocket<Stream>) -> Self {
24        Self {
25            ws,
26            buffer: VecDeque::new(),
27        }
28    }
29}
30
31impl<Stream: std::io::Read + std::io::Write> From<WebSocket<Stream>> for WSPort<Stream> {
32    fn from(ws: WebSocket<Stream>) -> Self {
33        Self::new(ws)
34    }
35}
36
37impl<Stream: std::io::Read + std::io::Write> MinitelRead for WSPort<Stream> {
38    fn read(&mut self, data: &mut [u8]) -> Result<()> {
39        // The websocket provides data without control of the size
40        // store them in a buffer, and deliver as much as requested
41        while self.buffer.len() < data.len() {
42            let message = self.ws.read().map_err(map_err)?;
43            if let tungstenite::Message::Text(data) = message {
44                self.buffer.extend(data.as_bytes());
45            }
46        }
47        for byte in data.iter_mut() {
48            *byte = self.buffer.pop_front().unwrap();
49        }
50        Ok(())
51    }
52}
53
54impl<Stream: std::io::Read + std::io::Write> MinitelWrite for WSPort<Stream> {
55    fn send(&mut self, data: &[u8]) -> Result<()> {
56        // the minitel websocket only accepts text messages? can be invalid utf8?
57        let text = Utf8Bytes::try_from(data.to_vec())
58            .map_err(|_| std::io::Error::new(ErrorKind::InvalidData, "Invalid UTF-8 data"))?;
59        self.ws
60            .send(tungstenite::Message::text(text))
61            .map_err(map_err)
62    }
63
64    fn flush(&mut self) -> Result<()> {
65        self.ws.flush().map_err(map_err)?;
66        self.buffer.clear();
67        Ok(())
68    }
69}
70
71fn map_err(e: tungstenite::Error) -> std::io::Error {
72    match e {
73        tungstenite::Error::ConnectionClosed => ErrorKind::ConnectionReset.into(),
74        tungstenite::Error::AlreadyClosed => ErrorKind::NotConnected.into(),
75        tungstenite::Error::Io(error) => error,
76        tungstenite::Error::Tls(tls_error) => Error::new(ErrorKind::Other, tls_error),
77        tungstenite::Error::Capacity(capacity_error) => {
78            Error::new(ErrorKind::InvalidData, capacity_error)
79        }
80        tungstenite::Error::Protocol(protocol_error) => {
81            Error::new(ErrorKind::InvalidData, protocol_error)
82        }
83        tungstenite::Error::WriteBufferFull(_) => Error::new(ErrorKind::Other, "Write buffer full"),
84        tungstenite::Error::Utf8 => Error::new(ErrorKind::InvalidData, "Invalid UTF-8 data"),
85        tungstenite::Error::AttackAttempt => Error::new(ErrorKind::Other, "Attack attempt"),
86        tungstenite::Error::Url(url_error) => Error::new(ErrorKind::InvalidData, url_error),
87        tungstenite::Error::Http(_) => Error::new(ErrorKind::InvalidData, "HTTP error"),
88        tungstenite::Error::HttpFormat(error) => Error::new(ErrorKind::InvalidData, error),
89    }
90}