summaryrefslogtreecommitdiff
path: root/src/telnet.rs
blob: c18fe5334c055dc89b8398e0c56c50c66a64dc23 (plain)
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
use std::io::{ErrorKind, Read, Result, Write};
use std::net::TcpStream;
use std::thread;
use std::time::Duration;

use crate::renderer::Renderer;
use crate::screen::Screen;

/// Parses a 16bit buffer by parsing both u8
fn parse_16bit_buffer(buffer: &[u8]) -> u16 {
    let high = buffer.first().unwrap_or(&0);
    let low = buffer.get(1).unwrap_or(&0);
    (u16::from(*high) << 8) | u16::from(*low)
}

// Telnet protocol constants
const NAWS_REQUEST: &[u8] = &[0xFF, 0xFD, 0x1F]; // IAC DO NAWS
const NAWS_RESPONSE_PREFIX: &[u8] = &[0xFF, 0xFB, 0x1F, 0xFF, 0xFA, 0x1F]; // IAC WILL NAWS IAC SB NAWS
const ESCAPE_SEQUENCE: &[u8] = &[0xFF, 0xF4, 0xFF, 0xFD, 0x06]; // IAC IP IAC DO TIMING_MARK

/// Selects a random screen and renderer, waits for the NAWS handshake, and
/// starts the animation.
pub fn handle_client(mut stream: TcpStream, frequency: u64, modulation: u8) -> Result<()> {
    let mut buffer = [0; 1024];

    let screen = Screen::random();
    let renderer = Renderer::random();

    let mut width: Option<u16> = None;
    let mut height: Option<u16> = None;
    let mut current_modulation: u8 = 0;

    // Send NAWS request to get terminal size
    stream.write_all(NAWS_REQUEST)?;
    stream.flush()?;

    loop {
        stream.set_read_timeout(Some(Duration::from_millis(10)))?;
        match stream.read(&mut buffer) {
            Ok(0) => break, // Disconnect
            Ok(n) => {
                if n >= 10 && buffer[0..6] == *NAWS_RESPONSE_PREFIX {
                    width = Some(parse_16bit_buffer(&buffer[6..8]));
                    height = Some(parse_16bit_buffer(&buffer[8..10]));

                    // Clear screen once we have dimensions
                    stream.write_all(b"\x1B[2J")?; // Clear screen
                    stream.flush()?;
                }

                // Check for telnet escape sequence (Ctrl+C)
                if n >= 5 && buffer[0..5] == *ESCAPE_SEQUENCE {
                    break;
                }
            }
            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {}
            Err(_) => break,
        }

        if let (Some(w), Some(h)) = (width, height) {
            let payload = screen.render(current_modulation, w, h, &renderer);
            let message = format!("\x1B[1;1H{payload}"); // Reset cursor + payload
            stream.write_all(message.as_bytes())?;
            stream.flush()?;

            current_modulation = current_modulation.wrapping_add(modulation);
        }

        thread::sleep(Duration::from_millis(frequency));
    }
    Ok(())
}