commit 172796420ae9329b509389fce75ca8dfce1cb567 Author: James Martin Date: Thu Jul 23 23:32:46 2020 -0700 The server successfully responds to status pings. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e2139a6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_size = 2 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..a16207e --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,19 @@ +name: Rust + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build + run: cargo build --locked diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3618ad2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,405 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", + "yaml-rust", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9" + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tmd" +version = "0.1.0" +dependencies = [ + "clap", + "serde", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "tokio" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" +dependencies = [ + "bytes", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-uds", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "yaml-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..24701e6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tmd" +version = "0.1.0" +authors = ["James Martin "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "2.33.1", features = ["yaml"] } +serde = { version = "1.0.114", features = ["derive"] } +serde_json = "1.0.56" +tokio = { version = "0.2.22", features = ["io-util", "macros", "net", "tcp", "rt-threaded"] } +uuid = { version = "0.8", features = ["serde"] } diff --git a/src/cli.yml b/src/cli.yml new file mode 100644 index 0000000..ee6fd0b --- /dev/null +++ b/src/cli.yml @@ -0,0 +1,9 @@ +name: TMD +version: "1.0" +author: James Martin +about: A Minecraft protocol-compatible server written in Rust. +args: + - host: + help: The IP address the server will use to listen for connections. Defaults to any address (`::`). + - port: + help: The port the server will accept connections from. Defaults to 25565. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3deb488 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,106 @@ +mod net; + +use crate::net::chat::Chat; +use crate::net::source; +use crate::net::source::{PacketError, PacketSource}; +use tokio::io::BufWriter; +use tokio::net::{TcpListener, TcpStream}; +use tokio::net::tcp::WriteHalf; +use std::io; +use std::net::IpAddr; + +use PacketError::*; + +#[tokio::main] +async fn main() -> io::Result<()> { + let yaml = clap::load_yaml!("cli.yml"); + let args = clap::App::from_yaml(yaml).get_matches(); + let host: IpAddr = args.value_of("host").unwrap_or("::").parse() + .expect("Invalid host IP address."); + let port: u16 = args.value_of("port").unwrap_or("25565").parse() + .expect("Port must be an integer between 1 an 65535."); + + let listener = TcpListener::bind((host, port)).await + .expect(&format!("Failed to bind to {}:{}.", host, port)); + + listen(listener).await; + + Ok(()) +} + +async fn listen(mut listener: TcpListener) { + loop { + let (socket, _) = match listener.accept().await { + Ok(x) => x, + Err(e) => { + eprintln!("Failed to accept client: {:?}", e); + continue; + } + }; + + tokio::spawn(accept_connection(socket)); + } +} + +async fn accept_connection(mut socket: TcpStream) { + let (mut read, write) = socket.split(); + let mut source = PacketSource::new(&mut read); + let mut dest = BufWriter::new(write); + + eprintln!("Client connected."); + match interact_handshake(&mut source, &mut dest).await { + Err(err) => { eprintln!("Client disconnected with error: {:?}", err); }, + Ok(_) => { eprintln!("Client disconnected without error."); } + } +} + +async fn interact_handshake(source: &mut PacketSource<'_>, dest: &mut BufWriter>) -> source::Result<()> { + use crate::net::packet::handshake::*; + use PacketHandshakeServerbound::*; + + match read_packet_handshake(source).await? { + Handshake(pkt) => { + if pkt.next_state == HandshakeNextState::Status { + interact_status(source, dest).await + } else { + Err(PktError("We do not support client log-in yet.".to_string())) + } + } + } +} + +async fn interact_status(source: &mut PacketSource<'_>, dest: &mut BufWriter>) -> source::Result<()> { + use crate::net::packet::status::*; + use PacketStatusClientbound::*; + use PacketStatusServerbound::*; + + loop { + match read_packet_status(source).await? { + Request => { + match write_packet_status(dest, Response(PacketResponse { + version: PacketResponseVersion { + name: "1.16.1".to_string(), + protocol: 736, + }, + players: PacketResponsePlayers { + max: 255, + online: 0, + sample: Vec::new(), + }, + description: Chat { text: "Hello, world!".to_string() }, + favicon: None, + })).await { + Ok(_) => {}, + Err(err) => return Err(IoError(err)) + } + }, + Ping(payload) => { + match write_packet_status(dest, Pong(payload)).await { + Ok(_) => {}, + Err(err) => return Err(IoError(err)) + } + return Ok(()); + } + } + } +} diff --git a/src/net/chat.rs b/src/net/chat.rs new file mode 100644 index 0000000..63310d5 --- /dev/null +++ b/src/net/chat.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +// TODO: Support more features. +#[derive(Serialize, Deserialize)] +pub struct Chat { + pub text: String, +} diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..484bfc2 --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,3 @@ +pub mod chat; +pub mod packet; +pub mod source; diff --git a/src/net/packet.rs b/src/net/packet.rs new file mode 100644 index 0000000..45704fc --- /dev/null +++ b/src/net/packet.rs @@ -0,0 +1,2 @@ +pub mod handshake; +pub mod status; \ No newline at end of file diff --git a/src/net/packet/handshake.rs b/src/net/packet/handshake.rs new file mode 100644 index 0000000..b4953ef --- /dev/null +++ b/src/net/packet/handshake.rs @@ -0,0 +1,47 @@ +use crate::net::source::{PacketError, PacketSource, Result}; +use PacketError::PktError; + +#[derive(Debug, PartialEq, Eq)] +pub enum HandshakeNextState { + Status, + Login, +} + +#[derive(Debug)] +pub struct PacketHandshake { + pub protocol_version: i32, + pub server_address: String, + pub server_port: u16, + pub next_state: HandshakeNextState, +} + +#[derive(Debug)] +pub enum PacketHandshakeServerbound { + Handshake(PacketHandshake), +} + +pub async fn read_packet_handshake(source: &mut PacketSource<'_>) -> Result { + use PacketHandshakeServerbound::*; + + let _length = source.read_varint().await?; + let id = source.read_varint().await?; + match id { + 0x00 => { + let protocol_version = source.read_varint().await?; + let server_address = source.read_string().await?; + let server_port = source.read_u16().await?; + let next_state = match source.read_varint().await? { + 1 => HandshakeNextState::Status, + 2 => HandshakeNextState::Login, + n => return Err(PktError(format!("Invalid next protocol state in handshake: {}", n))) + }; + Ok(Handshake(PacketHandshake { + protocol_version: protocol_version, + server_address: server_address, + server_port: server_port, + next_state: next_state, + })) + }, + id => Err(PktError(format!("Invalid handshake packet id: {}", id))) + } +} diff --git a/src/net/packet/status.rs b/src/net/packet/status.rs new file mode 100644 index 0000000..33928fb --- /dev/null +++ b/src/net/packet/status.rs @@ -0,0 +1,117 @@ +use crate::net::chat::Chat; +use crate::net::source::{PacketError, PacketSource, Result}; +use serde::Serialize; +use std::convert::TryInto; +use tokio::io::AsyncWriteExt; +use tokio::io::BufWriter; +use tokio::net::tcp::WriteHalf; +use uuid::Uuid; + +use PacketError::PktError; + +#[derive(Serialize)] +pub struct PacketResponseVersion { + pub name: String, + pub protocol: u32, +} + +#[derive(Serialize)] +pub struct PacketResponsePlayersSample { + pub name: String, + pub id: Uuid, +} + +#[derive(Serialize)] +pub struct PacketResponsePlayers { + pub max: u32, + pub online: u32, + pub sample: Vec +} + +#[derive(Serialize)] +pub struct PacketResponse { + pub version: PacketResponseVersion, + pub players: PacketResponsePlayers, + pub description: Chat, + #[serde(skip_serializing_if = "Option::is_none")] + pub favicon: Option, +} + +pub enum PacketStatusClientbound { + Response(PacketResponse), + Pong([u8; 8]), +} + +#[derive(Debug)] +pub enum PacketStatusServerbound { + Request, + Ping([u8; 8]), +} + +pub async fn read_packet_status(source: &mut PacketSource<'_>) -> Result { + use PacketStatusServerbound::*; + + let _length = source.read_varint().await?; + let id = source.read_varint().await?; + + match id { + 0 => Ok(Request), + 1 => { + let mut buf = [0; 8]; + source.read_exact(&mut buf).await?; + Ok(Ping(buf.try_into().unwrap())) + } + id => Err(PktError(format!("Invalid status packet id: {}", id))) + } +} + +fn write_varint(dest: &mut Vec, mut value: i32) { + loop { + let mut temp = (value & 0b01111111) as u8; + value = value >> 7; + if value != 0 { + temp |= 0b10000000; + } + dest.push(temp); + + if value == 0 { + break; + } + } +} + +fn write_slice(dest: &mut Vec, bytes: &[u8]) { + write_varint(dest, bytes.len() as i32); + dest.extend_from_slice(bytes); +} + +fn write_string(dest: &mut Vec, value: &str) { + write_slice(dest, value.as_bytes()); +} + +pub async fn write_packet_status(dest: &mut BufWriter>, + packet: PacketStatusClientbound) -> std::io::Result<()> { + use PacketStatusClientbound::*; + + let mut data = Vec::new(); + + match packet { + Response(response) => { + write_varint(&mut data, 0x00); + write_slice(&mut data, serde_json::to_vec(&response).unwrap().as_slice()); + }, + Pong(payload) => { + write_varint(&mut data, 0x01); + data.extend_from_slice(&payload); + } + } + + let mut packet_length_buf = Vec::new(); + write_varint(&mut packet_length_buf, data.len() as i32); + + dest.write_all(packet_length_buf.as_slice()).await?; + dest.write_all(data.as_slice()).await?; + dest.flush().await?; + + Ok(()) +} diff --git a/src/net/source.rs b/src/net/source.rs new file mode 100644 index 0000000..077abd7 --- /dev/null +++ b/src/net/source.rs @@ -0,0 +1,132 @@ +use tokio::io::AsyncReadExt; +use tokio::net::tcp::ReadHalf; + +const MAX_CLIENT_PACKET_SIZE: usize = 32767; + +pub struct PacketSource<'a> { + source: &'a mut ReadHalf<'a>, + buf: [u8; MAX_CLIENT_PACKET_SIZE], + index: usize, + used: usize, +} + +#[derive(Debug)] +pub enum PacketError { + IoError(std::io::Error), + PktError(String), +} + +use PacketError::*; + +pub type Result = std::result::Result; + +impl PacketSource<'_> { + pub fn new<'a>(source: &'a mut ReadHalf<'a>) -> PacketSource<'a> { + PacketSource { + source: source, + buf: [0; MAX_CLIENT_PACKET_SIZE], + index: 0, + used: 0, + } + } + + // TODO: Come up with a more efficient way of buffering. + pub async fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + let mut index = 0; + while buf.len() > index { + let bytes_needed = buf.len() - index; + // If we've already read as many bytes as we need, return. + if bytes_needed == 0 { + break; + } + + let bytes_remaining = self.used - self.index; + // If we're out of bytes to read, read some more. + if bytes_remaining == 0 { + // If we've already used up the entire buffer, restart from the beginning. + let space_remaining = self.buf.len() - self.used; + if space_remaining < 1 { + self.used = 0; + self.index = 0; + } + + let len = self.buf.len(); + let read = match self.source.read(&mut self.buf[self.index..len]).await { + Ok(read) => read, + Err(err) => return Err(IoError(err)) + }; + + self.used += read; + continue; + } + + let bytes_to_copy = bytes_remaining.min(bytes_needed); + + buf[index..index + bytes_to_copy] + .copy_from_slice(&self.buf[self.index..self.index + bytes_to_copy]); + index += bytes_to_copy; + self.index += bytes_to_copy; + } + + Ok(()) + } + + pub async fn read_u8(&mut self) -> Result { + let mut buf = [0; 1]; + self.read_exact(&mut buf).await?; + Ok(buf[0]) + } + + pub async fn read_u16(&mut self) -> Result { + let mut buf = [0; 2]; + self.read_exact(&mut buf).await?; + Ok(u16::from_le_bytes(buf)) + } + + pub async fn read_i64(&mut self) -> Result { + let mut buf = [0; 8]; + self.read_exact(&mut buf).await?; + Ok(i64::from_le_bytes(buf)) + } + + pub async fn read_varint(&mut self) -> Result { + let mut length = 1; + let mut acc = 0; + // VarInts must not be longer than 5 bytes. + while length <= 5 { + // If the highest bit is set, there are further bytes to be read from this VarInt; + // the rest of the bits are the actual data in the VarInt. + let read = self.read_u8().await?; + acc |= (read & 0b01111111) as i32; + + // There are no mo + if (read & 0b10000000) == 0 { + return Ok(acc); + } + + // Make space for the rest of the bits. + acc = acc << 7; + length += 1; + } + + // The VarInt was too long! + Err(PktError("VarInt was more than 5 bytes.".to_string())) + } + + pub async fn read_string(&mut self) -> Result { + let length = self.read_varint().await?; + if length < 0 { + return Err(PktError("String length cannot be negative.".to_string())); + } + + let length = length as usize; + if length > MAX_CLIENT_PACKET_SIZE { + return Err(PktError("String was too long.".to_string())); + } + + let mut buf = Vec::new(); + buf.resize(length, 0); + self.read_exact(buf.as_mut_slice()).await?; + String::from_utf8(buf).map_err(|_| PktError("String was invalid UTF-8.".to_string())) + } +} \ No newline at end of file