minitel_ws/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use minitel_stum::Minitel;

use std::{collections::VecDeque, io::Error, net::TcpStream};

use minitel_stum::{MinitelRead, MinitelWrite};
use std::io::{ErrorKind, Result};
use tungstenite::{Utf8Bytes, WebSocket};

/// Minitel terminal used over a websocket connection
pub type WSMinitel = Minitel<WSPort<TcpStream>>;

/// Build a Minitel terminal over a websocket connection
pub fn ws_minitel(socket: WebSocket<TcpStream>) -> WSMinitel {
    WSMinitel::new(WSPort::new(socket))
}

pub struct WSPort<Stream: std::io::Read + std::io::Write> {
    ws: WebSocket<Stream>,
    buffer: VecDeque<u8>,
}

impl<Stream: std::io::Read + std::io::Write> WSPort<Stream> {
    pub fn new(ws: WebSocket<Stream>) -> Self {
        Self {
            ws,
            buffer: VecDeque::new(),
        }
    }
}

impl<Stream: std::io::Read + std::io::Write> From<WebSocket<Stream>> for WSPort<Stream> {
    fn from(ws: WebSocket<Stream>) -> Self {
        Self::new(ws)
    }
}

impl<Stream: std::io::Read + std::io::Write> MinitelRead for WSPort<Stream> {
    fn read(&mut self, data: &mut [u8]) -> Result<()> {
        // The websocket provides data without control of the size
        // store them in a buffer, and deliver as much as requested
        while self.buffer.len() < data.len() {
            let message = self.ws.read().map_err(map_err)?;
            if let tungstenite::Message::Text(data) = message {
                self.buffer.extend(data.as_bytes());
            }
        }
        for byte in data.iter_mut() {
            *byte = self.buffer.pop_front().unwrap();
        }
        Ok(())
    }
}

impl<Stream: std::io::Read + std::io::Write> MinitelWrite for WSPort<Stream> {
    fn send(&mut self, data: &[u8]) -> Result<()> {
        // the minitel websocket only accepts text messages? can be invalid utf8?
        let text = Utf8Bytes::try_from(data.to_vec())
            .map_err(|_| std::io::Error::new(ErrorKind::InvalidData, "Invalid UTF-8 data"))?;
        self.ws
            .send(tungstenite::Message::text(text))
            .map_err(map_err)
    }

    fn flush(&mut self) -> Result<()> {
        self.ws.flush().map_err(map_err)?;
        self.buffer.clear();
        Ok(())
    }
}

fn map_err(e: tungstenite::Error) -> std::io::Error {
    match e {
        tungstenite::Error::ConnectionClosed => ErrorKind::ConnectionReset.into(),
        tungstenite::Error::AlreadyClosed => ErrorKind::NotConnected.into(),
        tungstenite::Error::Io(error) => error,
        tungstenite::Error::Tls(tls_error) => Error::new(ErrorKind::Other, tls_error),
        tungstenite::Error::Capacity(capacity_error) => {
            Error::new(ErrorKind::InvalidData, capacity_error)
        }
        tungstenite::Error::Protocol(protocol_error) => {
            Error::new(ErrorKind::InvalidData, protocol_error)
        }
        tungstenite::Error::WriteBufferFull(_) => Error::new(ErrorKind::Other, "Write buffer full"),
        tungstenite::Error::Utf8 => Error::new(ErrorKind::InvalidData, "Invalid UTF-8 data"),
        tungstenite::Error::AttackAttempt => Error::new(ErrorKind::Other, "Attack attempt"),
        tungstenite::Error::Url(url_error) => Error::new(ErrorKind::InvalidData, url_error),
        tungstenite::Error::Http(_) => Error::new(ErrorKind::InvalidData, "HTTP error"),
        tungstenite::Error::HttpFormat(error) => Error::new(ErrorKind::InvalidData, error),
    }
}