Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / server / src / dedicated / transport.rs

use std::io::{self, BufReader, BufWriter, Read, Write};
use std::net::{Shutdown, SocketAddr, TcpStream};
use std::sync::mpsc::{self, SyncSender};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};

use common::protocol::{Bridge, Connection, PROTOCOL_VERSION, Packet};

use crate::disconnect;
use crate::save::valid_name;

/// A player who completed the offline handshake.
pub(super) struct Login {
    pub name: String,
    pub address: SocketAddr,
    pub connection: Connection,
}

/// `NetLoginHandler` gives up after 600 ticks however slowly the bytes arrive.
const LOGIN_DEADLINE: Duration = Duration::from_secs(30);

/// Reads with a total deadline while logging in, then with the ordinary socket timeout.
struct Socket {
    stream: TcpStream,
    deadline: Option<Instant>,
}

impl Read for Socket {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if let Some(deadline) = self.deadline {
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.is_zero() {
                return Err(io::Error::new(
                    io::ErrorKind::TimedOut,
                    "Took too long to log in",
                ));
            }
            self.stream.set_read_timeout(Some(remaining))?;
        }
        self.stream.read(buf)
    }
}

pub(super) struct Peer {
    stream: TcpStream,
    thread: JoinHandle<()>,
}

impl Peer {
    pub fn start(stream: TcpStream, incoming: SyncSender<Login>) -> io::Result<Self> {
        stream.set_nodelay(true)?;
        stream.set_read_timeout(Some(Duration::from_secs(30)))?;
        stream.set_write_timeout(Some(Duration::from_secs(5)))?;
        let socket = stream.try_clone()?;
        let thread = std::thread::Builder::new()
            .name("client-connection".into())
            .spawn(move || {
                if let Err(error) = serve(socket, incoming) {
                    tracing::debug!("connection closed: {error}");
                }
            })?;
        Ok(Self { stream, thread })
    }

    pub fn finished(&self) -> bool {
        self.thread.is_finished()
    }
}

impl Drop for Peer {
    fn drop(&mut self) {
        let _ = self.stream.shutdown(Shutdown::Both);
    }
}

fn serve(stream: TcpStream, incoming: SyncSender<Login>) -> io::Result<()> {
    let address = stream.peer_addr()?;
    let mut output = BufWriter::new(stream.try_clone()?);
    let mut input = BufReader::new(Socket {
        stream,
        deadline: Some(Instant::now() + LOGIN_DEADLINE),
    });
    let name = match login(&mut input, &mut output) {
        Ok(name) => name,
        Err(error) => {
            let _ = disconnect(error.to_string()).write(&mut output);
            let _ = output.flush();
            return Err(error);
        }
    };
    let (bridge, server) = common::protocol::bridged_connection();
    input.get_mut().deadline = None;
    input
        .get_ref()
        .stream
        .set_read_timeout(Some(Duration::from_secs(30)))?;
    let sender = bridge.incoming.clone();
    let reader = std::thread::Builder::new()
        .name("client-packets".into())
        .spawn(move || {
            loop {
                let packet = Packet::read_from_client(&mut input)
                    .unwrap_or_else(|error| disconnect(error.to_string()));
                let disconnected = matches!(packet, Packet::Disconnect { .. });
                if sender.send(packet).is_err() || disconnected {
                    break;
                }
            }
        })?;
    let login = Login {
        name,
        address,
        connection: server,
    };
    // The player joins only once its packets can be read
    let result = if incoming.try_send(login).is_err() {
        Err(io::Error::other("server login queue is full"))
    } else {
        forward(&bridge, &reader, &mut output)
    };
    let _ = output.get_ref().shutdown(Shutdown::Both);
    let _ = reader.join();
    result
}

/// Writes the server's packets to the client until either side disconnects.
fn forward(bridge: &Bridge, reader: &JoinHandle<()>, output: &mut impl Write) -> io::Result<()> {
    while !reader.is_finished() {
        let packet = match bridge.outgoing.recv_timeout(Duration::from_millis(50)) {
            Ok(packet) => packet,
            Err(mpsc::RecvTimeoutError::Timeout) => continue,
            Err(mpsc::RecvTimeoutError::Disconnected) => break,
        };
        let mut disconnected = matches!(packet, Packet::Disconnect { .. });
        packet.write(output).map_err(io::Error::other)?;
        bridge.written(&packet);
        for packet in bridge.outgoing.try_iter() {
            disconnected |= matches!(packet, Packet::Disconnect { .. });
            packet.write(output).map_err(io::Error::other)?;
            bridge.written(&packet);
            if disconnected {
                break;
            }
        }
        output.flush()?;
        if disconnected {
            break;
        }
    }
    Ok(())
}

pub(super) fn login(input: &mut impl Read, output: &mut impl Write) -> io::Result<String> {
    let Packet::Handshake { .. } = Packet::read_from_client(input).map_err(io::Error::other)?
    else {
        return Err(io::Error::other("Protocol error"));
    };
    Packet::Handshake {
        username: "-".into(),
    }
    .write(output)
    .map_err(io::Error::other)?;
    output.flush()?;
    let Packet::Login {
        version_or_entity,
        username,
        ..
    } = Packet::read_from_client(input).map_err(io::Error::other)?
    else {
        return Err(io::Error::other("Protocol error"));
    };
    if version_or_entity != PROTOCOL_VERSION {
        return Err(io::Error::other(if version_or_entity > PROTOCOL_VERSION {
            "Outdated server!"
        } else {
            "Outdated client!"
        }));
    }
    if !valid_name(&username) {
        return Err(io::Error::other("Invalid username"));
    }
    Ok(username)
}