aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md121
-rw-r--r--src/reader.rs31
-rw-r--r--src/tcp.rs78
3 files changed, 178 insertions, 52 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d1c280
--- /dev/null
+++ b/README.md
@@ -0,0 +1,121 @@
+# craftio-rs
+
+Version 0.1.0, by [Twister915](https://github.com/Twister915)!
+
+craftio-rs is a library which let's you read & write packets defined in [mcproto-rs](https://github.com/Twister915/mcproto-rs)
+to real Minecraft servers/clients.
+
+You can use this library to implement anything from a simple server status ping client, BungeeCord-like proxy, a bot
+to join your favorite server, or an entire Minecraft server or client implementation.
+
+The protocol definition is managed in a separate crate, mentioned above, called [mcproto-rs](https://github.com/Twister915/mcproto-rs)
+which defines a set of traits to support custom protocol implementations, and also defines all packets for a few of the
+versions of Minecraft.
+
+This crate optionally implements the following features:
+* `compression` (using the [flate2](https://crates.io/crates/flate2) crate)
+* `encryption` (using the [aes](https://crates.io/crates/aes) crate) with a fast implementation of CFB-8
+* `futures-io` enables reading/writing to implementors of the `AsyncRead`/`AsyncWrite` traits from the
+ [futures](https://crates.io/crates/futures) crate
+* `tokio-io` enables reading/writing to implementors of the `AsyncRead`/`AsyncWrite` traits from the
+ [tokio](https://crates.io/crates/tokio) crate
+
+# Usage
+
+```toml
+[dependencies]
+craftio-rs = "0.1"
+```
+
+This library can be used to connect to servers or host client connections. It implements all features of the Minecraft
+protocol, and these features can be disabled for simpler use-cases (such as hitting servers to gather status information).
+
+You can also use an async based I/O implementation, or a blocking I/O implementation.
+
+## Connecting to a Server
+
+To connect to a Minecraft server, you can write something like this:
+
+```rust
+let mut conn = CraftTokioConnection::connect_server_tokio("localhost:25565").await?;
+conn.write_packet_async(Packet578::Handshake(HandshakeSpec { ... })).await?;
+conn.set_state(State::Login);
+...
+```
+
+This `CraftTokioConnection` struct is actually a type alias for the more general `CraftConnection<R, W>` type which wraps
+any `R` (reader) and `W` (writer) type supported by `CraftReader` and `CraftWriter`. More detail on these types below.
+
+You can also connect using a blocking socket from `std::net` like this:
+
+```rust
+let mut conn = CraftTcpConnection::connect_server_std("localhost:25565")?;
+conn.write_packet(Packet578::Handshake(HandshakeSpec { ... }))?;
+conn.set_state(State::Login);
+...
+```
+
+## Serving Clients
+
+You can use `CraftConnection::from_std_with_state(your_client, PacketDirection::ServerBound, State::Handshaking)` to wrap
+a blocking `TcpStream`, and you can use `CraftConnection::from_async_with_state((client_read_half, client_write_half), PacketDirection::ServerBound, State::Handshaking)`
+to wrap an async `TcpStream`. In the async case you must split your connection into reader/writer halves before passing it to the
+`CraftConnection`.
+
+In all cases it is recommended to first wrap the reader in a buffering reader implementation of your choice. This is because
+this crate typically reads the packet length (first 5 bytes) as one call, then the entire packet body as another call. If
+you choose to not use a buffering implementation, these two calls could have an undesirable overhead, because both may actually
+require an operating system call.
+
+# Types
+
+There are two structs which implement the behavior of this crate: `CraftReader<R>` and `CraftWriter<W>`.
+
+They are defined to implement the `CraftAsyncReader`/`CraftSyncReader` and `CraftAsyncWriter`/`CraftSyncWriter` traits
+when wrapping `R`/`W` types which implement the `craftio_rs::AsyncReadExact`/`std::io::Read` and
+`craftio_rs::AsyncWriteExact`/`std::io::Write` traits respectively.
+
+This crate provides implementations of `craftio_rs::AsyncReadExact` and `craftio_rs::AsyncWriteExact` for implementors of
+the `tokio::io::AsyncRead`/`tokio::io::AsyncWrite` and `futures::AsyncRead`/`futures::AsyncWrite` traits when you enable
+the `tokio-io` and `futures-io` features respectively.
+
+## Performance
+
+A `CraftReader<R>` and `CraftWriter<W>` hold some buffers, both of which are lazily allocated `Vec<u8>`s:
+* `raw_buf` which is a buffer for packet bytes
+* `compress_buf`/`decompress_buf`. When compression is enabled (both as a crate-feature called `compression` and after
+ a call to `.set_compression_threshold` with a `Some(> 0)` value) this buffer is used to store a compressed packet
+ (in the case of a writer) or the decompressed packet (in the case of a reader).
+
+These buffers can be eagerly allocated using calls to `.ensure_buf_capacity(usize)` and `.ensure_compression_buf_capacity(usize)`,
+but they cannot yet be provided by the user.
+
+### Motivation
+
+This library was designed when I was working on these three projects: a replacement for BungeeCord, a bot client that can
+join servers for me, and a tool to ping a list of servers quickly and print their status. This crate tries to avoid dynamic
+allocation, but does have some buffers to make serialization/deserialization fast. These allocations are done lazily by
+default, but can be done eagerly (described below) if desired.
+
+When implementing something like a game server, or a proxy like BungeeCord, you are dealing with tens to hundreds of joins
+per second in the maximum case, so the dynamic allocation is not going to dramatically impact performance. Therefore,
+lazily allocation and growing of the buffers aren't going to impact your flame-graph.
+
+However, in the case of trying to ping servers, I really wanted to ensure we only allocate once per connection half. To
+that end, you can eagerly allocate a large-enough buffer and also limit the max packet size to prevent it from growing
+any further (call `.set_max_packet_size` and `.ensure_buf_capacity`).
+
+A great feature would be allowing the user to provide a `&mut Vec<u8>` which can be used by the wrapper types until the
+connection is closed. This way, in a many-worker model (like a ping tool), you can simply allocate a buffer for each worker,
+which you re-use for each subsequent connection. This does not exist yet.
+
+## Adapting to different I/O implementations
+
+To add your favorite I/O library, you can either implement the std I/O traits (`std::io::Read` and `std::io::Write`) or
+for an async implementation you can implement the traits provided by this crate (`AsyncReadExact` and `AsyncWriteExact`).
+
+# Todo
+
+* Allow user to provide buffers which they already allocated for `raw_buf`
+* See if we can stop managing the `Vec<u8>` ourselves and just use `BufReader` traits that already exist?
+* Extract the offset tracking from `CraftReader` struct. \ No newline at end of file
diff --git a/src/reader.rs b/src/reader.rs
index b3bb2dd..1439a17 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -401,37 +401,6 @@ where
}
#[cfg(any(feature = "futures-io", feature = "tokio-io"))]
-pub trait IntoBufferedAsyncRead {
- type Target: AsyncReadExact;
-
- fn into_buffered(self, capacity: usize) -> Self::Target;
-}
-
-#[cfg(all(feature = "futures-io", not(feature = "tokio-io")))]
-impl<R> IntoBufferedAsyncRead for R
-where
- R: futures::io::AsyncRead + Send + Sync + Unpin,
-{
- type Target = futures::io::BufReader<R>;
-
- fn into_buffered(self, capacity: usize) -> Self::Target {
- futures::io::BufReader::with_capacity(capacity, self)
- }
-}
-
-#[cfg(feature = "tokio-io")]
-impl<R> IntoBufferedAsyncRead for R
-where
- R: tokio::io::AsyncRead + Send + Sync + Unpin,
-{
- type Target = tokio::io::BufReader<R>;
-
- fn into_buffered(self, capacity: usize) -> Self::Target {
- tokio::io::BufReader::with_capacity(capacity, self)
- }
-}
-
-#[cfg(any(feature = "futures-io", feature = "tokio-io"))]
#[async_trait]
pub trait AsyncReadExact: Unpin + Sync + Send {
async fn read_exact(&mut self, to: &mut [u8]) -> Result<(), io::Error>;
diff --git a/src/tcp.rs b/src/tcp.rs
index bd88885..f16a3c3 100644
--- a/src/tcp.rs
+++ b/src/tcp.rs
@@ -6,14 +6,30 @@ use std::io::BufReader as StdBufReader;
use std::net::TcpStream;
#[cfg(any(feature = "futures-io", feature = "tokio-io"))]
-use crate::{CraftAsyncReader, CraftAsyncWriter, IntoBufferedAsyncRead};
+use crate::{CraftAsyncReader, CraftAsyncWriter};
+
+#[cfg(feature = "tokio-io")]
+use tokio::{
+ net::{
+ TcpStream as TokioTcpStream,
+ tcp::{
+ OwnedReadHalf as TokioReadHalf,
+ OwnedWriteHalf as TokioWriteHalf,
+ },
+ ToSocketAddrs as TokioToSocketAddrs,
+ },
+ io::{
+ BufReader as TokioBufReader,
+ Error as TokioIoError,
+ },
+};
pub const BUF_SIZE: usize = 8192;
pub type CraftTcpConnection = CraftConnection<StdBufReader<TcpStream>, TcpStream>;
-impl CraftConnection<StdBufReader<TcpStream>, TcpStream> {
- pub fn connect_server_std(to: String) -> Result<Self, std::io::Error> {
+impl CraftTcpConnection {
+ pub fn connect_server_std<A>(to: A) -> Result<Self, std::io::Error> where A: std::net::ToSocketAddrs {
Self::from_std(TcpStream::connect(to)?, PacketDirection::ClientBound)
}
@@ -43,32 +59,52 @@ impl CraftConnection<StdBufReader<TcpStream>, TcpStream> {
}
}
-#[cfg(any(feature = "futures-io", feature = "tokio-io"))]
-impl<R, W> CraftConnection<R, W>
-where
- CraftReader<R>: CraftAsyncReader,
- CraftWriter<W>: CraftAsyncWriter,
-{
- pub fn from_unbuffered_async<U>(tuple: (U, W), read_direction: PacketDirection) -> Self
+#[cfg(feature = "tokio-io")]
+pub type CraftTokioConnection = CraftConnection<TokioBufReader<TokioReadHalf>, TokioWriteHalf>;
+
+#[cfg(feature = "tokio-io")]
+impl CraftTokioConnection {
+ pub async fn connect_server_tokio<A>(
+ to: A
+ ) -> Result<Self, TokioIoError>
where
- U: IntoBufferedAsyncRead<Target = R>,
+ A: TokioToSocketAddrs
{
- Self::from_unbuffered_async_with_state(tuple, read_direction, State::Handshaking)
+ let conn = TokioTcpStream::connect(to).await?;
+ conn.set_nodelay(true)?;
+ let (reader, writer) = conn.into_split();
+ let reader = TokioBufReader::with_capacity(BUF_SIZE, reader);
+ Ok(Self::from_async((reader, writer), PacketDirection::ClientBound))
}
+}
- pub fn from_unbuffered_async_with_state<U>(
- tuple: (U, W),
- read_direction: PacketDirection,
- state: State,
- ) -> Self
+#[cfg(feature = "tokio-io")]
+pub type CraftUnbufferedTokioConnection = CraftConnection<TokioReadHalf, TokioWriteHalf>;
+
+#[cfg(feature = "tokio-io")]
+impl CraftUnbufferedTokioConnection {
+ pub async fn connect_server_tokio_unbuffered<A>(
+ to: A
+ ) -> Result<Self, TokioIoError>
where
- U: IntoBufferedAsyncRead<Target = R>,
+ A: TokioToSocketAddrs
{
- let (ru, writer) = tuple;
- let reader = ru.into_buffered(BUF_SIZE);
- Self::from_async_with_state((reader, writer), read_direction, state)
+ let conn = TokioTcpStream::connect(to).await?;
+ conn.set_nodelay(true)?;
+
+ Ok(Self::from_async(
+ conn.into_split(),
+ PacketDirection::ClientBound,
+ ))
}
+}
+#[cfg(any(feature = "futures-io", feature = "tokio-io"))]
+impl<R, W> CraftConnection<R, W>
+where
+ CraftReader<R>: CraftAsyncReader,
+ CraftWriter<W>: CraftAsyncWriter,
+{
pub fn from_async(tuple: (R, W), read_direction: PacketDirection) -> Self {
Self::from_async_with_state(tuple, read_direction, State::Handshaking)
}