From 1756fb7f066decaf3a54e10d06f53a5e821620d3 Mon Sep 17 00:00:00 2001 From: Andrew Brower Date: Sat, 23 Aug 2025 15:29:20 -0400 Subject: [PATCH] refactor(proto): move protocols to separate crates --- Cargo.lock | 41 ++- Cargo.toml | 2 +- crawlspace-macro/src/lib.rs | 19 ++ crawlspace-proto-1_8/Cargo.toml | 8 + crawlspace-proto-1_8/src/lib.rs | 52 ++++ crawlspace-proto/Cargo.toml | 11 + crawlspace-proto/src/datatypes/impls.rs | 245 +++++++++++++++++ .../src}/datatypes/position.rs | 24 +- .../src}/datatypes/slot.rs | 30 +- crawlspace-proto/src/datatypes/string.rs | 114 ++++++++ .../src}/datatypes/text_component.rs | 0 .../src}/datatypes/variable.rs | 43 ++- crawlspace-proto/src/lib.rs | 73 +++++ crawlspace/src/protocol/datatypes/impls.rs | 258 ------------------ crawlspace/src/protocol/datatypes/string.rs | 165 ----------- crawlspace/src/protocol/mod.rs | 16 -- .../protocol/packets/login/registry/tags.rs | 19 ++ 17 files changed, 612 insertions(+), 508 deletions(-) create mode 100644 crawlspace-proto-1_8/Cargo.toml create mode 100644 crawlspace-proto-1_8/src/lib.rs create mode 100644 crawlspace-proto/Cargo.toml create mode 100644 crawlspace-proto/src/datatypes/impls.rs rename {crawlspace/src/protocol => crawlspace-proto/src}/datatypes/position.rs (81%) rename {crawlspace/src/protocol => crawlspace-proto/src}/datatypes/slot.rs (76%) create mode 100644 crawlspace-proto/src/datatypes/string.rs rename {crawlspace/src/protocol => crawlspace-proto/src}/datatypes/text_component.rs (100%) rename {crawlspace/src/protocol => crawlspace-proto/src}/datatypes/variable.rs (91%) create mode 100644 crawlspace-proto/src/lib.rs delete mode 100644 crawlspace/src/protocol/datatypes/impls.rs delete mode 100644 crawlspace/src/protocol/datatypes/string.rs diff --git a/Cargo.lock b/Cargo.lock index 89e2585..bf240fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "bitfield-struct" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "bitflags" version = "2.9.2" @@ -306,7 +317,7 @@ version = "0.1.0" dependencies = [ "aes", "bit-vec", - "bitfield-struct", + "bitfield-struct 0.9.5", "byteorder", "bytes", "cfb8", @@ -339,12 +350,22 @@ dependencies = [ ] [[package]] -name = "crawlspace-slime" +name = "crawlspace-proto" version = "0.1.0" dependencies = [ + "bitfield-struct 0.11.0", "byteorder", - "thiserror 2.0.15", - "zstd", + "serde", + "thiserror 2.0.16", + "uuid", +] + +[[package]] +name = "crawlspace-proto-1_8" +version = "0.1.0" +dependencies = [ + "bytes", + "crawlspace-proto", ] [[package]] @@ -1109,7 +1130,7 @@ dependencies = [ "byteorder", "fastnbt", "serde", - "thiserror 2.0.15", + "thiserror 2.0.16", "tracing", "zstd", ] @@ -1185,11 +1206,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.15", + "thiserror-impl 2.0.16", ] [[package]] @@ -1205,9 +1226,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 51b632a..afd552d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ "crawlspace-macro","crawlspace", "crawlspace-slime"] +members = [ "crawlspace-macro","crawlspace" , "crawlspace-proto", "crawlspace-proto-1_8"] [profile.release-stripped] inherits = "release" diff --git a/crawlspace-macro/src/lib.rs b/crawlspace-macro/src/lib.rs index fbf261c..26f1575 100644 --- a/crawlspace-macro/src/lib.rs +++ b/crawlspace-macro/src/lib.rs @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + use proc_macro::{Span, TokenStream}; use quote::quote; use syn::{parse_macro_input, parse_quote, DeriveInput, Fields, Ident, Index, Lit, Path}; diff --git a/crawlspace-proto-1_8/Cargo.toml b/crawlspace-proto-1_8/Cargo.toml new file mode 100644 index 0000000..e675830 --- /dev/null +++ b/crawlspace-proto-1_8/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crawlspace-proto-1_8" +version = "0.1.0" +edition = "2024" + +[dependencies] +bytes = "1.10.1" +crawlspace-proto = { path = "../crawlspace-proto" } diff --git a/crawlspace-proto-1_8/src/lib.rs b/crawlspace-proto-1_8/src/lib.rs new file mode 100644 index 0000000..11cd5c7 --- /dev/null +++ b/crawlspace-proto-1_8/src/lib.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use bytes::BytesMut; +use crawlspace_proto::{ + Packet, Read, ServerboundPacket, + datatypes::{VarInt, VariableNumber}, +}; + +/// Minecraft versions 1.8-1.8.9 +/// Protocol version 47 +pub struct Protocol47 { + reader: R, + writer: W, + bytebuf: BytesMut, +} + +impl Protocol47 { + pub fn new(reader: R, writer: W) -> Self { + Self { + reader, + writer, + bytebuf: BytesMut::new(), + } + } + + fn read_packet(&mut self) -> Result, crawlspace_proto::ErrorKind> { + let len = VarInt::read(&mut self.reader)?; + + todo!(); + } +} + +impl crawlspace_proto::Protocol for Protocol47 { + fn handshake_player(&mut self) {} +} diff --git a/crawlspace-proto/Cargo.toml b/crawlspace-proto/Cargo.toml new file mode 100644 index 0000000..57edaac --- /dev/null +++ b/crawlspace-proto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "crawlspace-proto" +version = "0.1.0" +edition = "2024" + +[dependencies] +bitfield-struct = "0.11.0" +byteorder = "1.5.0" +serde = { version = "1.0.219", features = ["derive"] } +thiserror = "2.0.16" +uuid = "1.18.0" diff --git a/crawlspace-proto/src/datatypes/impls.rs b/crawlspace-proto/src/datatypes/impls.rs new file mode 100644 index 0000000..8e865fc --- /dev/null +++ b/crawlspace-proto/src/datatypes/impls.rs @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use uuid::Uuid; + +use crate::{ + ErrorKind::{self, InvalidData}, + Read, Write, +}; + +impl Read<'_> for bool { + fn read(r: &mut impl std::io::Read) -> Result { + Ok(match r.read_u8()? { + 0x01 => true, + 0x00 => false, + v => { + return Err(InvalidData(format!( + "Expected 0x01 or 0x00 for bool, got {v}" + ))); + } + }) + } +} + +impl Write for bool { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + let v = match self { + true => 0x01, + false => 0x00, + }; + + Ok(w.write_all(&[v])?) + } +} + +impl Read<'_> for i8 { + fn read(r: &mut impl std::io::Read) -> Result { + Ok(r.read_i8()?) + } +} + +impl Write for i8 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_i8(*self)?) + } +} + +impl Read<'_> for u8 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_u8()?) + } +} + +impl Write for u8 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_u8(*self)?) + } +} + +impl Read<'_> for i16 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_i16::()?) + } +} + +impl Write for i16 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_i16::(*self)?) + } +} + +impl Read<'_> for u16 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_u16::()?) + } +} + +impl Read<'_> for i32 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_i32::()?) + } +} + +impl Write for i32 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_i32::(*self)?) + } +} + +impl Read<'_> for i64 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_i64::()?) + } +} + +impl Write for i64 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_i64::(*self)?) + } +} + +impl Read<'_> for u64 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_u64::()?) + } +} + +impl Write for u64 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_u64::(*self)?) + } +} + +impl Read<'_> for u128 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_u128::()?) + } +} + +impl Write for u128 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_u128::(*self)?) + } +} + +impl Read<'_> for f32 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_f32::()?) + } +} + +impl Write for f32 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_f32::(*self)?) + } +} + +impl Read<'_> for f64 { + fn read(r: &mut impl std::io::Read) -> Result + where + Self: Sized, + { + Ok(r.read_f64::()?) + } +} + +impl Write for f64 { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + Ok(w.write_f64::(*self)?) + } +} + +impl Write for Uuid { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + self.as_u128().write(w) + } +} + +impl Read<'_> for Uuid { + fn read(r: &mut impl std::io::Read) -> Result { + Ok(Uuid::from_u128(r.read_u128::()?)) + } +} + +impl Write for Option +where + T: Write, +{ + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + match self { + None => Ok(()), + Some(v) => v.write(w), + } + } +} + +impl Write for Vec +where + T: Write, +{ + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + for item in self { + item.write(w)?; + } + + Ok(()) + } +} + +impl<'a, T> Read<'a> for Vec +where + T: Read<'a>, +{ + fn read(r: &mut impl std::io::Read) -> Result { + let times = r.read_i32::()?; + let mut o = Vec::new(); + + for _ in 0..times { + o.push(T::read(r)?) + } + + Ok(o) + } +} diff --git a/crawlspace/src/protocol/datatypes/position.rs b/crawlspace-proto/src/datatypes/position.rs similarity index 81% rename from crawlspace/src/protocol/datatypes/position.rs rename to crawlspace-proto/src/datatypes/position.rs index 739a831..ece4708 100644 --- a/crawlspace/src/protocol/datatypes/position.rs +++ b/crawlspace-proto/src/datatypes/position.rs @@ -19,10 +19,9 @@ use bitfield_struct::bitfield; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use color_eyre::eyre::Result; use thiserror::Error; -use crate::protocol::{Decode, Encode}; +use crate::{ErrorKind, Read, Write}; #[derive(Debug)] pub struct Position { @@ -80,18 +79,17 @@ impl From for Position { } } -impl Encode for Position { - fn encode(&self, w: impl std::io::Write) -> Result<()> { - let encoded: PackedPosition = self.try_into()?; - encoded.encode(w) +impl Write for Position { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + let encoded: PackedPosition = self + .try_into() + .map_err(|_| ErrorKind::InvalidData("Invalid packed position".to_string()))?; + encoded.write(w) } } -impl Decode<'_> for Position { - fn decode(r: &mut &'_ [u8]) -> Result - where - Self: Sized, - { +impl Read<'_> for Position { + fn read(r: &mut impl std::io::Read) -> Result { let bytes = r.read_i64::()?; Ok(Self { @@ -102,8 +100,8 @@ impl Decode<'_> for Position { } } -impl Encode for PackedPosition { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { +impl Write for PackedPosition { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { Ok(w.write_u64::(self.0)?) } } diff --git a/crawlspace/src/protocol/datatypes/slot.rs b/crawlspace-proto/src/datatypes/slot.rs similarity index 76% rename from crawlspace/src/protocol/datatypes/slot.rs rename to crawlspace-proto/src/datatypes/slot.rs index 090d0f1..1fe985f 100644 --- a/crawlspace/src/protocol/datatypes/slot.rs +++ b/crawlspace-proto/src/datatypes/slot.rs @@ -17,7 +17,8 @@ * . */ -use crate::{protocol::Encode, server::registries::REGISTRIES, world::Item}; +// use crate::{protocol::Encode, server::registries::REGISTRIES, world::Item}; +use crate::{ErrorKind, Write}; use super::VarInt; @@ -32,19 +33,10 @@ pub struct Slot { #[derive(Debug, Clone)] pub enum Component {} -impl From for Slot { - fn from(value: Item) -> Self { - let item_id = REGISTRIES - .item - .entries - .get(&value.id) - .expect("Couldn't find registry entry for item") - .protocol_id; - - debug!("item id for {}: {item_id}", value.id); - +impl Slot { + pub fn new(item_id: i32, item_count: i8) -> Self { Self { - item_count: value.count as i8, + item_count, item_id: Some(item_id), components_to_add: None, components_to_remove: None, @@ -52,16 +44,16 @@ impl From for Slot { } } -impl Encode for Slot { - fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { - self.item_count.encode(&mut w)?; +impl Write for Slot { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + self.item_count.write(w)?; if self.item_count == 0 { return Ok(()); } if let Some(item_id) = self.item_id { - VarInt(item_id).encode(&mut w)?; + VarInt(item_id).write(w)?; VarInt( self.components_to_add @@ -69,7 +61,7 @@ impl Encode for Slot { .map(|v| v.len()) .unwrap_or(0) as i32, ) - .encode(&mut w)?; + .write(w)?; VarInt( self.components_to_remove @@ -77,7 +69,7 @@ impl Encode for Slot { .map(|v| v.len()) .unwrap_or(0) as i32, ) - .encode(&mut w)?; + .write(w)?; if let Some(ref components_to_add) = self.components_to_add { for _component in components_to_add { diff --git a/crawlspace-proto/src/datatypes/string.rs b/crawlspace-proto/src/datatypes/string.rs new file mode 100644 index 0000000..11d3a98 --- /dev/null +++ b/crawlspace-proto/src/datatypes/string.rs @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use crate::{ + ErrorKind::{self, InvalidData}, + Read, Write, +}; + +use super::VarInt; + +#[derive(Debug)] +pub struct Bounded(pub T); + +impl Read<'_> for Bounded { + fn read(r: &mut impl std::io::Read) -> Result { + let len = VarInt::read(r)?.0; + if len < 0 { + return Err(InvalidData( + "tried to decode string with negative length".to_string(), + )); + } + + let len = len as usize; + + let mut buf = vec![0; len]; + r.read_exact(&mut buf)?; + let content = String::from_utf8(buf) + .map_err(|_| ErrorKind::InvalidData("invalid utf8 string data".to_string()))?; + let utf16_len = content.encode_utf16().count(); + + if utf16_len > BOUND { + return Err(InvalidData(format!( + "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})" + ))); + } + + Ok(Bounded(content)) + } +} + +impl<'a, const BOUND: usize> Write for Bounded { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + let len = self.0.encode_utf16().count(); + + if len > BOUND { + return Err(InvalidData(format!( + "length of string {len} exceeds bound {BOUND}" + ))); + }; + + VarInt(self.0.len() as i32).write(w)?; + Ok(w.write_all(self.0.as_bytes())?) + } +} + +impl Write for str { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + VarInt(self.len() as i32).write(w)?; + Ok(w.write_all(self.as_bytes())?) + } +} + +#[derive(Debug)] +pub struct Rest(pub T); + +impl<'a, const BOUND: usize> Read<'_> for Rest { + fn read(r: &mut impl std::io::Read) -> Result { + let mut buf = Vec::new(); + r.read_to_end(&mut buf)?; + + let content = String::from_utf8(buf) + .map_err(|_| InvalidData("Rest was not valid UTF-8".to_string()))?; + + let utf16_len = content.encode_utf16().count(); + + if utf16_len > BOUND { + return Err(InvalidData(format!( + "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})" + ))); + }; + + Ok(Rest(content)) + } +} + +impl<'a, const BOUND: usize> Write for Rest { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { + let len = self.0.encode_utf16().count(); + + if len > BOUND { + return Err(InvalidData(format!( + "length of string {len} exceeds bound {BOUND}" + ))); + }; + + Ok(w.write_all(self.0.as_bytes())?) + } +} diff --git a/crawlspace/src/protocol/datatypes/text_component.rs b/crawlspace-proto/src/datatypes/text_component.rs similarity index 100% rename from crawlspace/src/protocol/datatypes/text_component.rs rename to crawlspace-proto/src/datatypes/text_component.rs diff --git a/crawlspace/src/protocol/datatypes/variable.rs b/crawlspace-proto/src/datatypes/variable.rs similarity index 91% rename from crawlspace/src/protocol/datatypes/variable.rs rename to crawlspace-proto/src/datatypes/variable.rs index d07d4e8..438cbbe 100644 --- a/crawlspace/src/protocol/datatypes/variable.rs +++ b/crawlspace-proto/src/datatypes/variable.rs @@ -18,23 +18,16 @@ */ use std::fmt::Display; -use std::io::Write; use byteorder::ReadBytesExt; -use color_eyre::eyre::Result; use serde::Deserialize; -use crate::protocol::{Decode, Encode}; +use crate::{ + ErrorKind::{self, InvalidData}, + Read, Write, +}; -#[derive(thiserror::Error, Debug)] -pub enum VariableDecodeError { - #[error("VarNum exceeds 32 bits")] - TooLong, - #[error("VarNum incomplete")] - Incomplete, -} - -pub trait VariableNumber<'a>: Sized + Encode + Decode<'a> { +pub trait VariableNumber: Sized + Write + for<'a> Read<'a> { const SEGMENT_BITS: u8 = 0b01111111; const CONTINUE_BITS: u8 = 0b10000000; @@ -49,7 +42,7 @@ macro_rules! make_var_num { #[serde(transparent)] pub struct $name(pub $type); - impl VariableNumber<'_> for $name { + impl VariableNumber for $name { const MAX_BYTES: usize = $max_bytes; fn len(self) -> usize { @@ -66,23 +59,21 @@ macro_rules! make_var_num { } } - impl Decode<'_> for $name { - fn decode(r: &mut &[u8]) -> Result { + impl Read<'_> for $name { + fn read(r: &mut impl std::io::Read) -> Result { let mut v: $type = 0; for i in 0..Self::MAX_BYTES { - let byte = r.read_u8().map_err(|_| VariableDecodeError::Incomplete)?; + let byte = r + .read_u8() + .map_err(|_| InvalidData("Incomplete variable number".to_string()))?; v |= <$type>::from(byte & Self::SEGMENT_BITS) << (i * 7); if byte & Self::CONTINUE_BITS == 0 { return Ok(Self(v)); } } - if r.len() > 0 { - Err(VariableDecodeError::TooLong)?; - } - - Err(VariableDecodeError::Incomplete)? + Err(InvalidData("Malformed variable number".to_string())) } } }; @@ -91,10 +82,10 @@ macro_rules! make_var_num { make_var_num!(VarInt, i32, 5); make_var_num!(VarLong, i64, 10); -impl Encode for VarInt { +impl Write for VarInt { // implementation taken from https://github.com/as-com/varint-simd/blob/0f468783da8e181929b01b9c6e9f741c1fe09825/src/encode/mod.rs#L71 // only the first branch is done here because we never need to change varint size - fn encode(&self, mut w: impl Write) -> Result<()> { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { let x = self.0 as u64; let stage1 = (x & 0x000000000000007f) | ((x & 0x0000000000003f80) << 1) @@ -243,10 +234,10 @@ impl VarLong { // } } -impl Encode for VarLong { +impl Write for VarLong { // ...and here's the second branch ^_^ #[cfg(any(target_feature = "bmi2", target_feature = "avx2"))] - fn encode(&self, mut w: impl Write) -> Result<()> { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { use std::arch::x86_64::*; unsafe { // Break the number into 7-bit parts and spread them out into a vector @@ -282,7 +273,7 @@ impl Encode for VarLong { // TODO: implement this using neon? not likely we'll use arm-based servers but maybe nice for // local testing? #[cfg(not(any(target_feature = "bmi2", target_feature = "avx2")))] - fn encode(&self, mut w: impl Write) -> Result<()> { + fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> { use byteorder::WriteBytesExt; let mut val = self.0 as u64; diff --git a/crawlspace-proto/src/lib.rs b/crawlspace-proto/src/lib.rs new file mode 100644 index 0000000..a0a1ca8 --- /dev/null +++ b/crawlspace-proto/src/lib.rs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +#[allow(unused_imports)] +pub mod datatypes { + mod impls; + mod position; + mod slot; + mod string; + mod text_component; + mod variable; + + pub use impls::*; + pub use position::*; + pub use slot::*; + pub use string::*; + pub use text_component::*; + pub use variable::*; +} + +pub enum PacketState { + Handshake, + Play, + Status, + Login, +} + +#[derive(thiserror::Error, Debug)] +pub enum ErrorKind { + #[error("IO error")] + Io(#[from] std::io::Error), + #[error("Invalid data: {0}")] + InvalidData(String), +} + +pub trait Read<'a> { + fn read(reader: &mut impl std::io::Read) -> Result + where + Self: Sized; +} + +pub trait Write { + fn write(&self, writer: &mut impl std::io::Write) -> Result<(), ErrorKind>; +} + +pub trait Packet { + fn packet_id(&self) -> &'static str; + fn packet_state(&self) -> PacketState; +} + +pub trait ServerboundPacket: Packet + for<'a> Read<'a> {} +pub trait ClientboundPacket: Packet + Write {} + +pub trait Protocol { + fn handshake_player(&mut self); + // fn login_player(&self); +} diff --git a/crawlspace/src/protocol/datatypes/impls.rs b/crawlspace/src/protocol/datatypes/impls.rs deleted file mode 100644 index c8531dc..0000000 --- a/crawlspace/src/protocol/datatypes/impls.rs +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2024 Andrew Brower. - * This file is part of Crawlspace. - * - * Crawlspace is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public - * License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Crawlspace is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with Crawlspace. If not, see - * . - */ - -use std::mem; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use color_eyre::eyre::{bail, Result}; -use uuid::Uuid; - -use crate::protocol::{Decode, DecodeSized, Encode}; - -impl<'a> Decode<'a> for bool { - fn decode(r: &mut &'a [u8]) -> Result { - Ok(match r.read_u8()? { - 0x01 => true, - 0x00 => false, - v => bail!("Expected 0x01 or 0x00 for bool, got {v}"), - }) - } -} - -impl Encode for bool { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let v = match self { - true => 0x01, - false => 0x00, - }; - - Ok(w.write_all(&[v])?) - } -} - -impl<'a> Decode<'a> for i8 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_i8()?) - } -} - -impl Encode for i8 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_i8(*self)?) - } -} - -impl<'a> Decode<'a> for u8 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_u8()?) - } -} - -impl Encode for u8 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_u8(*self)?) - } -} - -impl<'a> Decode<'a> for i16 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_i16::()?) - } -} - -impl Encode for i16 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_i16::(*self)?) - } -} - -impl<'a> Decode<'a> for u16 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_u16::()?) - } -} - -impl<'a> Decode<'a> for i32 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_i32::()?) - } -} - -impl Encode for i32 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_i32::(*self)?) - } -} - -impl<'a> Decode<'a> for i64 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_i64::()?) - } -} - -impl Encode for i64 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_i64::(*self)?) - } -} - -impl<'a> Decode<'a> for u64 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_u64::()?) - } -} - -impl Encode for u64 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_u64::(*self)?) - } -} - -impl<'a> Decode<'a> for u128 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_u128::()?) - } -} - -impl Encode for u128 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_u128::(*self)?) - } -} - -impl<'a> Decode<'a> for f32 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_f32::()?) - } -} - -impl Encode for f32 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_f32::(*self)?) - } -} - -impl<'a> Decode<'a> for f64 { - fn decode(r: &mut &'a [u8]) -> Result - where - Self: Sized, - { - Ok(r.read_f64::()?) - } -} - -impl Encode for f64 { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_f64::(*self)?) - } -} - -impl Encode for Uuid { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - self.as_u128().encode(&mut w) - } -} - -impl<'a> Decode<'a> for Uuid { - fn decode(r: &mut &'a [u8]) -> Result { - Ok(Uuid::from_u128(r.read_u128::()?)) - } -} - -impl Encode for Option -where - T: Encode, -{ - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - match self { - None => Ok(()), - Some(v) => v.encode(&mut w), - } - } -} - -impl Encode for Vec -where - T: Encode, -{ - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - for item in self { - item.encode(&mut w)?; - } - - Ok(()) - } -} - -impl<'a, T> DecodeSized<'a> for Vec -where - T: Decode<'a>, -{ - fn decode(times: usize, r: &mut &'a [u8]) -> Result { - let mut o = Vec::new(); - - for _ in 0..times { - o.push(T::decode(r)?) - } - - Ok(o) - } -} - -#[derive(Debug)] -pub struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Decode<'a> for Bytes<'a> { - fn decode(r: &mut &'a [u8]) -> Result { - Ok(Self(mem::take(r))) - } -} - -impl<'a> Encode for Bytes<'a> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - Ok(w.write_all(self.0)?) - } -} diff --git a/crawlspace/src/protocol/datatypes/string.rs b/crawlspace/src/protocol/datatypes/string.rs deleted file mode 100644 index 7204666..0000000 --- a/crawlspace/src/protocol/datatypes/string.rs +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2024 Andrew Brower. - * This file is part of Crawlspace. - * - * Crawlspace is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public - * License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Crawlspace is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with Crawlspace. If not, see - * . - */ - -use color_eyre::eyre::{ensure, Result}; - -use crate::protocol::{Decode, Encode}; - -use super::{Bytes, VarInt}; - -#[derive(Debug)] -pub struct Bounded(pub T); - -impl<'a, const BOUND: usize> Decode<'a> for Bounded<&'a str, BOUND> { - fn decode(r: &mut &'a [u8]) -> Result { - let len = VarInt::decode(r)?.0; - ensure!(len >= 0, "tried to decode string with negative length"); - - let len = len as usize; - ensure!( - len <= r.len(), - "malformed packet - not enough data to continue decoding (expected {len} got {})", - r.len(), - ); - - let (content, rest) = r.split_at(len); - let content = std::str::from_utf8(content)?; - let utf16_len = content.encode_utf16().count(); - - ensure!( - utf16_len <= BOUND, - "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})" - ); - - *r = rest; - - Ok(Bounded(content)) - } -} - -impl<'a, const BOUND: usize> Encode for Bounded<&'a str, BOUND> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let len = self.0.encode_utf16().count(); - - ensure!(len <= BOUND, "length of string {len} exceeds bound {BOUND}"); - - VarInt(self.0.len() as i32).encode(&mut w)?; - Ok(w.write_all(self.0.as_bytes())?) - } -} - -impl Encode for str { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - VarInt(self.len() as i32).encode(&mut w)?; - Ok(w.write_all(self.as_bytes())?) - } -} - -impl<'a, const BOUND: usize> Encode for Bounded, BOUND> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let len = self.0 .0.len(); - ensure!(len <= BOUND, "length of bytes {len} exceeds bound {BOUND}"); - VarInt(len as i32).encode(&mut w)?; - self.0.encode(&mut w) - } -} - -impl<'a, const BOUND: usize> Decode<'a> for Bounded, BOUND> { - fn decode(r: &mut &'a [u8]) -> Result { - let len = VarInt::decode(r)?.0; - ensure!(len >= 0, "tried to decode string with negative length"); - - let len = len as usize; - ensure!( - len <= r.len(), - "malformed packet - not enough data to continue decoding (expected {len} got {})", - r.len(), - ); - - let (mut content, rest) = r.split_at(len); - let content = Bytes::decode(&mut content)?; - let len = content.0.len(); - - ensure!( - len <= BOUND, - "raw byte length exceeds {BOUND} chars (is {len})" - ); - - *r = rest; - - Ok(Bounded(content)) - } -} - -#[derive(Debug)] -pub struct Rest(pub T); - -impl<'a, const BOUND: usize> Decode<'a> for Rest<&'a str, BOUND> { - fn decode(r: &mut &'a [u8]) -> Result { - let (content, rest) = r.split_at(r.len()); - let content = std::str::from_utf8(content)?; - let utf16_len = content.encode_utf16().count(); - - ensure!( - utf16_len <= BOUND, - "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})" - ); - - *r = rest; - - Ok(Rest(content)) - } -} - -impl<'a, const BOUND: usize> Encode for Rest<&'a str, BOUND> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let len = self.0.encode_utf16().count(); - - ensure!(len <= BOUND, "length of string {len} exceeds bound {BOUND}"); - - Ok(w.write_all(self.0.as_bytes())?) - } -} - -impl<'a, const BOUND: usize> Encode for Rest, BOUND> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let len = self.0 .0.len(); - - ensure!(len <= BOUND, "length of bytes {len} exceeds bound {BOUND}"); - - self.0.encode(&mut w) - } -} - -impl<'a, const BOUND: usize> Decode<'a> for Rest, BOUND> { - fn decode(r: &mut &'a [u8]) -> Result { - let (mut content, rest) = r.split_at(r.len()); - let content = Bytes::decode(&mut content)?; - let len = content.0.len(); - - ensure!( - len <= BOUND, - "raw byte length exceeds {BOUND} chars (is {len})" - ); - - *r = rest; - - Ok(Rest(content)) - } -} diff --git a/crawlspace/src/protocol/mod.rs b/crawlspace/src/protocol/mod.rs index 9289725..a05da75 100644 --- a/crawlspace/src/protocol/mod.rs +++ b/crawlspace/src/protocol/mod.rs @@ -17,22 +17,6 @@ * . */ -pub mod datatypes { - mod impls; - mod position; - mod slot; - mod string; - mod text_component; - mod variable; - - pub use impls::*; - pub use position::*; - pub use slot::*; - pub use string::*; - pub use text_component::*; - pub use variable::*; -} - pub mod packets { pub mod login { mod config; diff --git a/crawlspace/src/protocol/packets/login/registry/tags.rs b/crawlspace/src/protocol/packets/login/registry/tags.rs index 34be898..60c9955 100644 --- a/crawlspace/src/protocol/packets/login/registry/tags.rs +++ b/crawlspace/src/protocol/packets/login/registry/tags.rs @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + use crawlspace_macro::Packet; use crate::protocol::datatypes::{Bounded, VarInt}; -- 2.51.2