diff --git a/Cargo.lock b/Cargo.lock index 817431c..a8fc768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "derivative" version = "2.2.0" @@ -208,6 +218,12 @@ dependencies = [ "syn-unnamed-struct", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.3" @@ -313,6 +329,7 @@ dependencies = [ "bytes", "falcon_packet_core_derive", "fastnbt", + "pretty_assertions", "thiserror", "uuid 1.1.2", ] @@ -657,6 +674,15 @@ dependencies = [ "regex", ] +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -706,6 +732,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -1167,3 +1205,9 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/crates/logic/src/connection/mod.rs b/crates/logic/src/connection/mod.rs index fb16506..8f74da1 100644 --- a/crates/logic/src/connection/mod.rs +++ b/crates/logic/src/connection/mod.rs @@ -6,6 +6,7 @@ use std::time::Duration; use bytes::Bytes; use falcon_core::network::{ConnectionState, PacketHandlerState, UNKNOWN_PROTOCOL}; use falcon_core::ShutdownHandle; +use falcon_packet_core::special::PacketPrepare; use falcon_packet_core::{ReadError, WriteError}; use ignore_result::Ignore; use mc_chat::ChatComponent; diff --git a/crates/logic/src/connection/writer.rs b/crates/logic/src/connection/writer.rs index 8577633..e56a312 100644 --- a/crates/logic/src/connection/writer.rs +++ b/crates/logic/src/connection/writer.rs @@ -35,43 +35,6 @@ impl SocketWrite { } } - pub fn finish(&mut self) { - if self.ready_pos == self.output_buffer.len() { - return; - } - - self.flush(); - - if self.compression_threshold >= 0 { - if self.next_is_compressed { - let offset = VarI32::from(self.compression.total_in() as usize).size(); - let overall_len = self.next_len_size - offset; - write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, overall_len, &mut self.output_buffer[self.ready_pos..]); - write_fixed_varint(self.compression.total_in() as i32, offset, &mut self.output_buffer[self.ready_pos + overall_len..]); - } else { - let overall_len = self.next_len_size - 1; - write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, overall_len, &mut self.output_buffer[self.ready_pos..]); - } - } else { - let overall_len = self.next_len_size; - write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, self.next_len_size, &mut self.output_buffer[self.ready_pos..]); - } - - // TODO: do encryption - - self.compression.reset(); - self.ready_pos = self.output_buffer.len(); - - if self.output_buffer.len() < COMPRESSION_BUFFER_LEN { - let capacity = self.output_buffer.capacity(); - if capacity > COMPRESSION_BUFFER_LEN && capacity > 3 * self.output_buffer.len() { - let new_buffer = BytesMut::with_capacity(COMPRESSION_BUFFER_LEN); - let old_buffer = std::mem::replace(&mut self.output_buffer, new_buffer); - self.output_buffer.put(old_buffer); - } - } - } - fn flush(&mut self) { self.write_all(); self.compression_position = 0; @@ -154,16 +117,6 @@ fn write_fixed_varint(mut value: i32, size: usize, buf: &mut [u8]) { } impl PacketPrepare for SocketWrite { - /// # Important - /// This function optimizes allocations by assuming - /// a single call to this function spans an entire packet. - /// This is important because this is the function that determins - /// whether a packet gets compressed or not. It is also used - /// for marking the start of a new packet. Depending on - /// this, it may allocate up to 3-9 bytes extra. - /// - /// As a result of the above, the packet should be written to - /// this writer immediately after calling this. fn prepare(&mut self, additional: usize) { let len_size = VarI32::from(additional).size(); let mut capacity = additional; @@ -183,6 +136,43 @@ impl PacketPrepare for SocketWrite { self.output_buffer.reserve(capacity + self.next_len_size); self.output_buffer.put_bytes(0, self.next_len_size); } + + fn finish(&mut self) { + if self.ready_pos == self.output_buffer.len() { + return; + } + + self.flush(); + + if self.compression_threshold >= 0 { + if self.next_is_compressed { + let offset = VarI32::from(self.compression.total_in() as usize).size(); + let overall_len = self.next_len_size - offset; + write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, overall_len, &mut self.output_buffer[self.ready_pos..]); + write_fixed_varint(self.compression.total_in() as i32, offset, &mut self.output_buffer[self.ready_pos + overall_len..]); + } else { + let overall_len = self.next_len_size - 1; + write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, overall_len, &mut self.output_buffer[self.ready_pos..]); + } + } else { + let overall_len = self.next_len_size; + write_fixed_varint((self.output_buffer.len() - self.ready_pos - overall_len) as i32, self.next_len_size, &mut self.output_buffer[self.ready_pos..]); + } + + // TODO: do encryption + + self.compression.reset(); + self.ready_pos = self.output_buffer.len(); + + if self.output_buffer.len() < COMPRESSION_BUFFER_LEN { + let capacity = self.output_buffer.capacity(); + if capacity > COMPRESSION_BUFFER_LEN && capacity > 3 * self.output_buffer.len() { + let new_buffer = BytesMut::with_capacity(COMPRESSION_BUFFER_LEN); + let old_buffer = std::mem::replace(&mut self.output_buffer, new_buffer); + self.output_buffer.put(old_buffer); + } + } + } } // TODO: explain unsafe code diff --git a/crates/packet_core/Cargo.toml b/crates/packet_core/Cargo.toml index fa3d2e4..5a810ce 100644 --- a/crates/packet_core/Cargo.toml +++ b/crates/packet_core/Cargo.toml @@ -14,3 +14,6 @@ thiserror = "1.0.32" uuid = "1.1.2" fastnbt = "2.3.2" + +[dev-dependencies] +pretty_assertions = "1.3.0" diff --git a/crates/packet_core/src/error.rs b/crates/packet_core/src/error.rs index e8c1213..e2e0733 100644 --- a/crates/packet_core/src/error.rs +++ b/crates/packet_core/src/error.rs @@ -2,28 +2,55 @@ use std::string::FromUtf8Error; use thiserror::Error; +/// Errors that can occur when writing types to a minecraft connection using +/// [`PacketWrite`](super::PacketWrite) or +/// [`PacketWriteSeed`](super::PacketWriteSeed). #[derive(Debug, Error)] pub enum WriteError { + /// Returned when a string was longer than the maximum length allowed by the + /// protocol. + /// + /// The first argument is what was attempted to write, the + /// second argument is the maximum length. #[error("String was longer than allowed: {1} > {0}")] StringTooLong(usize, usize), + /// Returned by [`fastnbt`](fastnbt). #[error("Couldn't serialize to NBT")] FastNbtError(#[from] fastnbt::error::Error), + /// Returned when there is unsufficient space in the supplied + /// [`BufMut`](bytes::BufMut). #[error("Buffer ran out of space")] EndOfBuffer, } +/// Errors that can occur when reading types from a minecraft connection using +/// [`PacketRead`](super::PacketRead) or +/// [`PacketReadSeed`](super::PacketReadSeed). #[derive(Debug, Error)] pub enum ReadError { + /// Returned when parsing UTF-8 fails. #[error("Invalid UTF-8 received")] FromUTF8Error(#[from] FromUtf8Error), + /// Returned when parsing [`Uuid`](uuid::Uuid) in invalid strinng + /// representation. #[error("Invalid StrUuid received")] UuidError(#[from] uuid::Error), + /// Returned by [`fastnbt`](fastnbt). #[error("Couldn't deserialize from NBT")] FastNbtError(#[from] fastnbt::error::Error), + /// Returned when a string was longer than the maximum length allowed by the + /// protocol. + /// + /// The first argument is the length that was read, the + /// second argument is the maximum length. #[error("String was longer than allowed: {1} > {0}")] StringTooLong(usize, usize), + /// Returned when a [`VarI32`](super::VarI32) is larger in size than 5 bytes + /// or when a [`VarI64`](super::VarI64) is larger in size than 10 bytes. #[error("VarInt was longer than allowed")] VarTooLong, + /// Returned when there are unsufficient bytes remaining in + /// [`Buf`](bytes::Buf). #[error("Reached EOF of input buffer")] NoMoreBytes, } diff --git a/crates/packet_core/src/lib.rs b/crates/packet_core/src/lib.rs index 098d172..9f48ed7 100644 --- a/crates/packet_core/src/lib.rs +++ b/crates/packet_core/src/lib.rs @@ -1,3 +1,5 @@ +#![doc(html_favicon_url = "https://wiki.falconmc.org/perm/icons/favicon.ico?v=1")] +#![doc(html_logo_url = "https://wiki.falconmc.org/perm/icons/android-chrome-512x512.png?v=1")] //! ## **Packet Core** //! This is the main component of [Falcon](https://github.com/FalconMC-Dev/FalconMC)'s //! networking system. It defines how types should be read from and written to a @@ -95,6 +97,7 @@ pub trait PacketRead { /// /// [derive macros]: falcon_packet_core#derives pub trait PacketWrite: PacketSize { + /// Write self to the buffer according to the minecraft protocol. fn write(&self, buffer: &mut B) -> Result<(), WriteError> where B: BufMut + ?Sized; @@ -109,6 +112,8 @@ pub trait PacketWrite: PacketSize { /// /// [derive macros]: falcon_packet_core#derives pub trait PacketSize { + /// Determine the size Self would have if it was written to + /// a minecraft connection, this should be an exact value. fn size(&self) -> usize; } @@ -120,8 +125,11 @@ pub trait PacketSize { /// This trait should rarely be implemented manually, if you implement this for /// a general type, please contribute it to this project. pub trait PacketReadSeed { + /// The target type this struct will produce. type Value; + /// Read [`Self::Value`](PacketReadSeed::Value) from the buffer using self, + /// according to the minecraft protocol. fn read(self, buffer: &mut B) -> Result where B: Buf + ?Sized; @@ -135,6 +143,8 @@ pub trait PacketReadSeed { /// This trait should rarely be implemented manually, if you implement this for /// a general type, please contribute it to this project. pub trait PacketWriteSeed<'a>: PacketSizeSeed<'a> { + /// Write [`Self::Value`](PacketSizeSeed::Value) to the buffer using self, + /// according to the minecraft protocol. fn write(self, value: &'a Self::Value, buffer: &'a mut B) -> Result<(), WriteError> where B: BufMut + ?Sized; @@ -148,7 +158,11 @@ pub trait PacketWriteSeed<'a>: PacketSizeSeed<'a> { /// This trait should rarely be implemented manually, if you implement this for /// a general type, please contribute it to this project. pub trait PacketSizeSeed<'a> { + /// The target type this struct will write. type Value; + /// Determine the size [`Self::Value`](PacketReadSeed::Value) would have if + /// it was written to a minecraft connection, this should be an exact + /// value. fn size(self, value: &'a Self::Value) -> usize; } diff --git a/crates/packet_core/src/primitives/array.rs b/crates/packet_core/src/primitives/array.rs index e5533ca..05216df 100644 --- a/crates/packet_core/src/primitives/array.rs +++ b/crates/packet_core/src/primitives/array.rs @@ -6,6 +6,7 @@ use bytes::{Buf, BufMut}; use crate::error::{ReadError, WriteError}; use crate::{PacketRead, PacketReadSeed, PacketSize, PacketSizeSeed, PacketWrite, PacketWriteSeed}; +/// Helper type to write and read [`array`]s to and from a minecraft connection. pub struct PacketArray(PhantomData); impl Default for PacketArray { diff --git a/crates/packet_core/src/primitives/bytes.rs b/crates/packet_core/src/primitives/bytes.rs index a9b7d73..9e39104 100644 --- a/crates/packet_core/src/primitives/bytes.rs +++ b/crates/packet_core/src/primitives/bytes.rs @@ -7,6 +7,8 @@ use bytes::BufMut; use crate::error::{ReadError, WriteError}; use crate::{PacketReadSeed, PacketSize, PacketSizeSeed, PacketWrite, PacketWriteSeed}; +/// Helper type to write any type `T` that implements [`AsRef<[u8]>`](AsRef) to +/// a minecraft connection. #[derive(Default)] pub struct AsRefU8(PhantomData); @@ -46,12 +48,15 @@ impl<'a, T: AsRef<[u8]>> PacketSizeSeed<'a> for AsRefU8 { fn size(self, value: &Self::Value) -> usize { value.as_ref().len() } } +/// Helper type to read any type `T` that implements [`From>`] from a +/// minecraft connection. pub struct Bytes { size: usize, _marker: PhantomData, } impl Bytes { + /// Creates a new `Bytes`. pub fn new(size: usize) -> Self { Self { size, diff --git a/crates/packet_core/src/primitives/iter.rs b/crates/packet_core/src/primitives/iter.rs index 95a32a3..ccc5684 100644 --- a/crates/packet_core/src/primitives/iter.rs +++ b/crates/packet_core/src/primitives/iter.rs @@ -2,9 +2,19 @@ use bytes::BufMut; use crate::{PacketSize, PacketWrite, WriteError}; +/// Helper type to write iterators over implementors of the seed-less flavored +/// traits to a minecraft connection. +/// +/// ## Note +/// Do not use this type to write byte slices, use [`AsRefU8`](super::AsRefU8) +/// for that. +/// +/// Do not use this type to read a byte vec, use [`Bytes`](super::Bytes) for +/// that. pub struct PacketIter(I); impl PacketIter { + /// Create a new `PacketIter`. pub fn new(iterator: I) -> Self { Self(iterator) } } @@ -13,6 +23,8 @@ where T: PacketSize + 'a, I: Iterator, { + /// Determine the size of the contained iterator. The iterator will iterate + /// over **references** of `T`. pub fn size_ref(self) -> usize { self.0.map(|elem| elem.size()).sum() } } @@ -21,6 +33,8 @@ where T: PacketSize, I: Iterator, { + /// Determine the size of the contained iterator. The iterator will iterate + /// over **owned values** of `T`. pub fn size_owned(self) -> usize { self.0.map(|elem| elem.size()).sum() } } @@ -29,6 +43,8 @@ where T: PacketWrite + 'a, I: Iterator, { + /// Write the contained iterator to a buffer. The iterator will iterate over + /// **references** of `T`. pub fn write_ref(mut self, buffer: &mut B) -> Result<(), WriteError> where B: BufMut + ?Sized, @@ -42,6 +58,8 @@ where T: PacketWrite, I: Iterator, { + /// Write the contained iterator to a buffer. The iterator will iterate over + /// **owned values** of `T`. pub fn write_owned(mut self, buffer: &mut B) -> Result<(), WriteError> where B: BufMut + ?Sized, diff --git a/crates/packet_core/src/primitives/mod.rs b/crates/packet_core/src/primitives/mod.rs index 74d02f4..b991385 100644 --- a/crates/packet_core/src/primitives/mod.rs +++ b/crates/packet_core/src/primitives/mod.rs @@ -15,7 +15,13 @@ pub use self::str::PacketString; pub use self::vec::PacketVec; macro_rules! impl_var_int { - ($($var:ident: $base:ident => $($in:ident),+ + $($out_ty:ident = $out:ident),+);*$(;)?) => {$( + ($($var:ident: $base:ident => $($in:ident),+ + $($out_ty:ident),+);*$(;)?) => {$( + #[doc = "Transparent wrapper around an "] + #[doc = stringify!($base)] + #[doc = "."] + /// + /// When writing to or reading from a minecraft connection, this will read itself using the + /// [varint](https://wiki.vg/Protocol#VarInt_and_VarLong) representation. #[repr(transparent)] pub struct $var { val: $base, @@ -36,13 +42,10 @@ macro_rules! impl_var_int { } impl $var { + /// Return the stored value. pub fn val(self) -> $base { self.val } - - $(pub fn $out(self) -> $out_ty { - self.val as $out_ty - })+ } impl From<$base> for $var { @@ -76,7 +79,6 @@ macro_rules! impl_var_int { } impl_var_int! { - VarI32: i32 => i8, u8, i16, u16, isize, usize, u32 + - isize = as_isize, usize = as_usize, u32 = as_u32, i64 = as_i64, u64 = as_u64, i128 = as_i128, u128 = as_u128; - VarI64: i64 => i8, u8, i16, u16, i32, u32, isize, usize, u64 + u64 = as_u64, i128 = as_i128, u128 = as_u128; + VarI32: i32 => i8, u8, i16, u16, isize, usize, u32 + isize, usize, u32, i64, u64, i128, u128; + VarI64: i64 => i8, u8, i16, u16, i32, u32, isize, usize, u64 + u64, i128, u128; } diff --git a/crates/packet_core/src/primitives/str.rs b/crates/packet_core/src/primitives/str.rs index 0b434ce..5b9627e 100644 --- a/crates/packet_core/src/primitives/str.rs +++ b/crates/packet_core/src/primitives/str.rs @@ -5,12 +5,16 @@ use bytes::{Buf, BufMut}; use crate::error::{ReadError, WriteError}; use crate::{Bytes, PacketRead, PacketReadSeed, PacketSize, PacketSizeSeed, PacketWrite, PacketWriteSeed, VarI32}; +/// Helper type to read any type `T` that implements [`From`] from a +/// buffer and write any type `T` that implements [`AsRef`](AsRef) to a +/// buffer. pub struct PacketString { size: usize, _marker: PhantomData, } impl PacketString { + /// Creates a new `PacketString`. pub fn new(size: usize) -> Self { Self { size, @@ -47,7 +51,7 @@ impl> PacketReadSeed for PacketString { where B: Buf + ?Sized, { - let len = VarI32::read(buffer)?.as_usize(); + let len = usize::from(VarI32::read(buffer)?); if len > self.size * 4 { return Err(ReadError::StringTooLong(self.size * 4, len)); } diff --git a/crates/packet_core/src/primitives/vec.rs b/crates/packet_core/src/primitives/vec.rs index 472eba0..cba8a82 100644 --- a/crates/packet_core/src/primitives/vec.rs +++ b/crates/packet_core/src/primitives/vec.rs @@ -4,6 +4,16 @@ use std::marker::PhantomData; use super::iter::PacketIter; use crate::{PacketRead, PacketReadSeed, PacketSizeSeed, PacketWrite, PacketWriteSeed}; +/// Helper type to read any type `I` that implements [`FromIterator`] over `T` +/// from a buffer and write any type `I` that implements [`IntoIterator`] over +/// `T` to a buffer. +/// +/// ## Note +/// Do not use this type to write byte slices, use [`AsRefU8`](super::AsRefU8) +/// for that. +/// +/// Do not use this type to read a byte vec, use [`Bytes`](super::Bytes) for +/// that. pub struct PacketVec { size: usize, _marker: PhantomData, @@ -15,6 +25,7 @@ impl Default for PacketVec { } impl PacketVec { + /// Creates a new `PacketVec`. pub fn new(size: usize) -> Self { Self { size, diff --git a/crates/packet_core/src/special/counter.rs b/crates/packet_core/src/special/counter.rs index c59d07e..c06168d 100644 --- a/crates/packet_core/src/special/counter.rs +++ b/crates/packet_core/src/special/counter.rs @@ -1,13 +1,45 @@ use std::io::Write; +/// A [`Write`] implementation that discards all bytes written to it but keeps +/// track of how many were written. +/// +/// Useful for determining the size of types that implements serialization to a +/// [`Write`], for example [fastnbt](fastnbt). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Hash)] pub struct Counter { count: usize, } impl Counter { + /// Returns a new `Counter` with an initial value of 0. + /// + /// # Example + /// ``` + /// # use pretty_assertions::assert_eq; + /// use falcon_packet_core::special::Counter; + /// + /// let counter = Counter::new(); + /// assert_eq!(counter.count(), 0); // initial count of 0 + /// ``` pub fn new() -> Self { Default::default() } + /// Returns the amount of bytes that have been written to this counter so + /// far. + /// + /// # Example + /// ``` + /// # use pretty_assertions::assert_eq; + /// use std::io::Write; + /// use falcon_packet_core::special::Counter; + /// + /// let mut counter = Counter::new(); + /// counter.write_all(b"Hello")?; // do a first write + /// assert_eq!(counter.count(), 5); + /// + /// counter.write_all(b" world!")?; // do a second write + /// assert_eq!(counter.count(), 12); + /// # Ok::<(), std::io::Error>(()) + /// ``` pub fn count(&self) -> usize { self.count } } diff --git a/crates/packet_core/src/special/mod.rs b/crates/packet_core/src/special/mod.rs index 0954c59..9a0cf30 100644 --- a/crates/packet_core/src/special/mod.rs +++ b/crates/packet_core/src/special/mod.rs @@ -1,3 +1,18 @@ +//! Extra types that are are not considered primitives. Also contains utility +//! types used in the [derive macros](falcon_packet_core#derives). +//! +//! **Data types**: +//! - [`StrUuid`]: String representation of [`Uuid`](::uuid::Uuid). +//! +//! **Traits**: +//! - [`PacketPrepare`]: Used to turn [`BufMut`](bytes::BufMut) into a packet +//! sink. +//! +//! **Utility types**: +//! - [`Counter`]: Counts bytes that are written to it. +//! - [`Reader`]: Wrapper around [`Buf`](bytes::Buf). +//! - [`Writer`]: Wrapper around [`BufMut`](bytes::BufMut). + mod counter; mod packet; mod reader; diff --git a/crates/packet_core/src/special/packet.rs b/crates/packet_core/src/special/packet.rs index 708f9f4..9f36de3 100644 --- a/crates/packet_core/src/special/packet.rs +++ b/crates/packet_core/src/special/packet.rs @@ -1,9 +1,38 @@ use bytes::{BufMut, BytesMut}; +/// A buffer type that supports writing packet data to. This generally means +/// that this type automatically compresses data that is written to it and +/// prefixes that data with its length according to the minecraft protocol. +/// +/// Because of this, **care** should be taken when using this type. The +/// [`prepare`](PacketPrepare::prepare) function must be called before the very +/// first byte of a packet is written. The [`finish`](PacketPrepare::finish) +/// function must be called after the last byte of a packet is written. If this +/// contract is not fulfilled, this type cannot guarantee a correct packet +/// stream. +/// +/// ## **API usage** +/// This type should only be implemented by `FalconMC`. pub trait PacketPrepare: BufMut { - fn prepare(&mut self, additional: usize); + /// Prepares the buffer for the next packet to be written, given its length. + /// + /// # **Important** + /// Only exact or upper limits of the packet's length will guarantee correct + /// behavior. + fn prepare(&mut self, length: usize); + + /// Called to signal that a packet has been written fully to this type. This + /// allows the type to finish up the packet, for example prefixing it + /// with the correct length. + fn finish(&mut self); } +/// Just writes data without any processing. impl PacketPrepare for BytesMut { + /// Any length is fine since `BytesMut` will indefinitely grow when + /// required. fn prepare(&mut self, additional: usize) { self.reserve(additional); } + + /// No-op + fn finish(&mut self) {} } diff --git a/crates/packet_core/src/special/reader.rs b/crates/packet_core/src/special/reader.rs index 720ab42..49def8d 100644 --- a/crates/packet_core/src/special/reader.rs +++ b/crates/packet_core/src/special/reader.rs @@ -3,12 +3,34 @@ use std::io::Read; use bytes::Buf; +/// A [`Read`] implementation that wraps around a mutable reference to a +/// [`Buf`](bytes::Buf). +/// +/// Useful for reading to a type that implements deserialization from a +/// [`Read`], e.g. [fastnbt](fastnbt). #[derive(Debug)] pub struct Reader<'a, B: ?Sized> { buf: &'a mut B, } impl<'a, B: ?Sized> Reader<'a, B> { + /// Creates a new `Reader`. + /// + /// # Example + /// ``` + /// use std::io::Read; + /// use bytes::Bytes; + /// use falcon_packet_core::special::Reader; + /// + /// let mut buffer = Bytes::from_static(b"Hello world!"); // Any given `Buf` + /// + /// let mut reader = Reader::new(&mut buffer); + /// let mut result = Vec::new(); + /// reader.read_to_end(&mut result)?; + /// + /// assert_eq!(result, b"Hello world!"); + /// # Ok::<(), std::io::Error>(()) + /// ``` pub fn new(buf: &'a mut B) -> Self { Self { buf } } } diff --git a/crates/packet_core/src/special/uuid.rs b/crates/packet_core/src/special/uuid.rs index 70bcbd6..449d586 100644 --- a/crates/packet_core/src/special/uuid.rs +++ b/crates/packet_core/src/special/uuid.rs @@ -32,6 +32,53 @@ impl PacketRead for Uuid { } } +/// String representation of [`Uuid`] as a wrapper. +/// +/// Instance creation should happen using [`From`]. +/// +/// Unlike normal strings, the string representation of a uuid always has a +/// maximum length of 36. This allows this type to implement [`PacketRead`], +/// [`PacketSize`] and [`PacketWrite`] directly instead of the seed-flavored +/// traits. +/// +/// # Examples +/// Writing a `StrUuid`: +/// ``` +/// use uuid::Uuid; +/// use bytes::BytesMut; +/// use falcon_packet_core::special::StrUuid; +/// use falcon_packet_core::{PacketSize, PacketWrite}; +/// +/// let mut buffer = BytesMut::new(); +/// +/// let uuid = Uuid::parse_str("ec174daf-b5a5-4ea1-adc6-35a7f9fc4a60").unwrap(); // random uuid +/// let str_uuid = StrUuid::from(uuid); +/// +/// assert_eq!(str_uuid.size(), 37); +/// +/// str_uuid.write(&mut buffer)?; +/// assert_eq!(buffer.len(), 37); // assert a minecraft string of length 36 has been written +/// # Ok::<(), falcon_packet_core::WriteError>(()) +/// ``` +/// +/// Reading a `StrUuid`: +/// ``` +/// use uuid::Uuid; +/// use bytes::{BytesMut, BufMut}; +/// use falcon_packet_core::special::StrUuid; +/// use falcon_packet_core::PacketRead; +/// +/// // buffer containing a random uuid +/// let mut buffer = BytesMut::new(); +/// buffer.put_u8(36); +/// buffer.put_slice(b"ec174daf-b5a5-4ea1-adc6-35a7f9fc4a60"); +/// +/// let str_uuid = StrUuid::read(&mut buffer)?; +/// let uuid = Uuid::from(str_uuid); +/// +/// assert_eq!(uuid, Uuid::parse_str("ec174daf-b5a5-4ea1-adc6-35a7f9fc4a60").unwrap()); +/// # Ok::<(), falcon_packet_core::ReadError>(()) +/// ``` pub struct StrUuid(pub(crate) Uuid); const STR_UUID_LEN: usize = { diff --git a/crates/packet_core/src/special/writer.rs b/crates/packet_core/src/special/writer.rs index 9acb621..6b15acb 100644 --- a/crates/packet_core/src/special/writer.rs +++ b/crates/packet_core/src/special/writer.rs @@ -3,12 +3,33 @@ use std::io::Write; use bytes::BufMut; +/// A [`Write`] implementation that wraps around a mutable reference to +/// [`BufMut`]. +/// +/// Useful for writing a type that implementats serialization to [`Write`], e.g. +/// [fastnbt](fastnbt). #[derive(Debug)] pub struct Writer<'a, B: ?Sized> { buf: &'a mut B, } impl<'a, B: ?Sized> Writer<'a, B> { + /// Creates a new `Writer`. + /// + /// # Example + /// ``` + /// use std::io::Write; + /// use bytes::BytesMut; + /// use falcon_packet_core::special::Writer; + /// + /// let mut buffer = BytesMut::new(); + /// + /// let mut writer = Writer::new(&mut buffer); + /// writer.write_all(b"Hello world!")?; + /// + /// assert_eq!(&buffer[..], b"Hello world!"); + /// Ok::<(), std::io::Error>(()) + /// ``` pub fn new(buf: &'a mut B) -> Self { Self { buf } } } diff --git a/crates/packet_core_derive/src/lib.rs b/crates/packet_core_derive/src/lib.rs index a04d0b0..25e9908 100644 --- a/crates/packet_core_derive/src/lib.rs +++ b/crates/packet_core_derive/src/lib.rs @@ -11,6 +11,47 @@ mod size; pub(crate) mod util; mod write; +/// Derive macro for [`PacketWrite`](falcon_packet_core::PacketWrite). +/// +/// Currently, this trait can only be derived for structs with named fields. The struct must +/// implement [`PacketSize`](falcon_packet_core::PacketSize) and each field's type must +/// implement [`PacketWrite`](falcon_packet_core::PacketWrite). +/// +/// The following field attributes are available: +/// +/// name | argument(s) | function +/// --- | --- | --- +/// `var32` | | Write the field using [varint format](https://wiki.vg/Protocol#VarInt_and_VarLong). +/// `var64` | | Write the field using [varlong format](https://wiki.vg/Protocol#VarInt_and_VarLong). +/// `array` | | Required for any [`array`] except byte arrays. +/// `bytes` | | Required for any [`AsRef<[u8]>`](AsRef). +/// `into` | `= "type"` | Before writing, first conver the field into the given type. +/// `convert` | `= "type"` | Before writing, first convert the field into the given type. (overlaps with [`PacketRead`](falcon_packet_core_derive::PacketRead)) +/// `nbt` | | Write as nbt data using [fastnbt](::fastnbt). +/// `string` | `= length` | Required for any [`AsRef`](AsRef). The given length should be the maximum length allowed by the protocol. +/// `to_string` | `= length` | Required for any [`ToString`]. The given length should be the maximum length allowed by the prococol. +/// `vec` | | Required for any type that implements [`IntoIterator`]. +/// +/// # Example +/// ```ignore +/// use falcon_packet_core::PacketWrite; +/// +/// #[derive(PacketSize, PacketWrite)] +/// struct MyStruct { +/// #[falcon(var32)] +/// id: i32, +/// number: i32, +/// #[falcon(string = 30)] +/// text: String, +/// #[falcon(to_string = 20)] +/// fancy_string: String, +/// #[falcon(vec)] +/// uuids: Vec, +/// } +/// ``` +/// +#[rustfmt::skip] +#[allow(rustdoc::broken_intra_doc_links)] #[proc_macro_derive(PacketWrite, attributes(falcon))] pub fn derive_packet_write(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let item = parse_macro_input!(item as ItemStruct); @@ -21,6 +62,46 @@ pub fn derive_packet_write(item: proc_macro::TokenStream) -> proc_macro::TokenSt } } +/// Derive macro for [`PacketSize`](falcon_packet_core::PacketSize). +/// +/// Currently, this trait can only be derived for structs with named fields. Each field's +/// type must implement [`PacketSize`](falcon_packet_core::PacketSize). +/// +/// The following field attributes are available: +/// +/// name | argument(s) | function +/// --- | --- | --- +/// `var32` | | Write the field using [varint format](https://wiki.vg/Protocol#VarInt_and_VarLong). +/// `var64` | | Write the field using [varlong format](https://wiki.vg/Protocol#VarInt_and_VarLong). +/// `array` | | Required for any [`array`] except byte arrays. +/// `bytes` | | Required for any [`AsRef<[u8]>`](AsRef). +/// `into` | `= "type"` | Before writing, first conver the field into the given type. +/// `convert` | `= "type"` | Before writing, first convert the field into the given type. (overlaps with [`PacketRead`](falcon_packet_core_derive::PacketRead)) +/// `nbt` | | Write as nbt data using [fastnbt](::fastnbt). +/// `string` | `= length` | Required for any [`AsRef`](AsRef). The given length should be the maximum length allowed by the protocol. +/// `to_string` | `= length` | Required for any [`ToString`]. The given length should be the maximum length allowed by the prococol. +/// `vec` | | Required for any type that implements [`IntoIterator`]. +/// +/// # Example +/// ```ignore +/// use falcon_packet_core::PacketSize; +/// +/// #[derive(PacketSize)] +/// struct MyStruct { +/// #[falcon(var32)] +/// id: i32, +/// number: i32, +/// #[falcon(string = 30)] +/// text: String, +/// #[falcon(to_string = 20)] +/// fancy_string: String, +/// #[falcon(vec)] +/// uuids: Vec, +/// } +/// ``` +/// +#[rustfmt::skip] +#[allow(rustdoc::broken_intra_doc_links)] #[proc_macro_derive(PacketSize, attributes(falcon))] pub fn derive_packet_size(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let item = parse_macro_input!(item as ItemStruct);