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};