Restructure project

This commit is contained in:
Garen Tyler 2023-05-02 13:49:07 -06:00
parent 6589eb2836
commit 866f39a356
Signed by: garentyler
GPG Key ID: D7A048C454CB7054
35 changed files with 2171 additions and 3068 deletions

3
.gitignore vendored
View File

@ -1,6 +1,7 @@
.DS_Store
target
/output.log
/*.log
/logs
/world
/server-icon.png
/composition.toml

819
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,9 @@
[workspace]
members = [ "crates/*", "crates/*/examples/*" ]
members = [ "crates/*" ]
[workspace.dependencies]
tracing = { version = "0.1", features = ["log"] }
composition-protocol = { path = "./crates/composition-protocol" }
[package]
name = "composition"
@ -8,8 +12,32 @@ edition = "2021"
authors = ["Garen Tyler <garentyler@garen.dev>"]
description = "An extremely fast Minecraft server"
license = "MIT"
build = "build.rs"
[dependencies]
composition-core = { path = "./crates/composition-core" }
log = "0.4"
tokio = "1"
ctrlc = "3.1.8"
clap = { version = "4.2.1", features = ["derive"] }
composition-protocol = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "*", features = ["tracing-log"] }
tracing-appender = "0.2"
toml = "0.5"
once_cell = "1.17"
serde = { version = "1.0.114", features = ["serde_derive"] }
serde_json = "1.0.59"
quartz_nbt = { version = "0.2.6", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
uuid = "0.8.2"
tokio-util = "0.7.7"
futures = "0.3.28"
# Unused but possibly useful dependencies:
# async-trait = "0.1.48"
# backtrace = "0.3.50"
# base64 = "0.12.3"
# colorful = "0.2.1"
# fastnbt = "*"
# mojang-api = "0.6.1"
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)
# radix64 = "0.6.2"
# toml = "0.5.6"

View File

@ -11,6 +11,7 @@ Composition is broken up into multiple crates to speed up build times and improv
- `composition-core` implements the main server logic, such as handling clients and loading world chunks.
- `composition-protocol` handles the types and packets needed for network communication.
The library was designed to be able to used by anyone looking to implement a Minecraft server.
- `composition-config` handles the server configuration files and command line argument parsing.
## Useful Resources
- [Protocol Specification](https://wiki.vg/Protocol)

24
build.rs Normal file
View File

@ -0,0 +1,24 @@
use std::process::Command;
fn main() {
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
let git_hash = String::from_utf8_lossy(&output.stdout).to_string();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
} else {
println!("cargo:rustc-env=GIT_HASH=000000000");
}
if let Ok(output) = Command::new("git")
.args(["log", "-1", "--format=%cI"])
.output()
{
let iso_date = String::from_utf8_lossy(&output.stdout).to_string();
println!("cargo:rustc-env=GIT_DATE={}", iso_date);
} else {
println!("cargo:rustc-env=GIT_DATE=1970-01-01");
}
println!(
"cargo:rustc-env=BUILD_TARGET={}",
std::env::var("TARGET").unwrap()
);
}

View File

@ -1,31 +0,0 @@
[package]
name = "composition-core"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
chrono = "0.4.13"
ctrlc = "3.1.8"
fern = { version = "0.6", features = ["colored"] }
lazy_static = "1.4.0"
log = "0.4"
quartz_nbt = { version = "0.2.6", features = ["serde"] }
radix64 = "0.6.2"
serde = { version = "1.0.114", features = ["serde_derive"] }
serde_json = "1.0.59"
substring = "1.4.5"
tokio = { version = "1", features = ["full"] }
toml = "0.5"
uuid = "0.8.2"
# async-trait = "0.1.48"
# backtrace = "0.3.50"
# base64 = "0.12.3"
# colorful = "0.2.1"
# composition-protocol = { path = "../composition-protocol" }
# fastnbt = "*"
# futures = "0.3.13"
# mojang-api = "0.6.1"
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)
# toml = "0.5.6"

View File

@ -1,9 +0,0 @@
use std::process::Command;
fn main() {
if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() {
let git_hash = String::from_utf8_lossy(&output.stdout).to_string();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
} else {
println!("cargo:rustc-env=GIT_HASH=00000000");
}
}

View File

@ -1,246 +0,0 @@
use crate::prelude::*;
use std::{fs::File, path::Path};
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
let mut data = vec![];
let mut file = File::open(path)?;
file.read_to_end(&mut data)?;
Ok(data)
}
pub struct Config {
pub port: u16,
pub max_players: usize,
pub motd: String,
pub server_icon: String,
pub server_icon_bytes: Vec<u8>,
pub server_string: String,
pub log_level: log::LevelFilter,
pub protocol_version: i32,
pub game_version: String,
pub server_version: String,
}
impl Default for Config {
fn default() -> Self {
let server_version = format!(
"composition/{} ({})",
env!("CARGO_PKG_VERSION"),
env!("GIT_HASH").substring(0, 8)
);
Config {
port: 25565,
max_players: 20,
motd: "Hello world!".to_owned(),
server_icon: "server-icon.png".to_owned(),
server_icon_bytes: include_bytes!("./server-icon.png").to_vec(),
server_string: server_version.clone(),
log_level: if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
},
protocol_version: 761,
game_version: "1.19.3".to_owned(),
server_version,
}
}
}
impl Config {
pub fn from_toml(cfg: toml::Value) -> Config {
let mut config = Config::default();
let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String {
if let Some(s) = cfg.get(field) {
if let Some(s) = s.as_str() {
return s.to_owned();
} else {
warn!("{}", error);
}
} else {
warn!("{}", error);
}
default.to_owned()
};
if let Some(&toml::Value::Integer(port)) = cfg.get("port") {
if port < u16::MIN as i64 || port > u16::MAX as i64 {
warn!(
"Config port must be an integer in the range of {}-{}, using default port: {}",
u16::MIN,
u16::MAX,
config.port
);
} else {
config.port = port as u16;
}
} else {
warn!(
"Config port must be an integer in the range of {}-{}, using default port: {}",
u16::MIN,
u16::MAX,
config.port
);
}
if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") {
if max_players < 0 {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
} else {
config.max_players = max_players as usize;
}
} else {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
}
config.motd = get_string(
&cfg,
"motd",
&config.motd,
&format!(
"Config motd must be a string, using default motd: \"{}\"",
config.motd
),
);
config.game_version = get_string(
&cfg,
"ping_game_version",
&config.game_version,
&format!(
"Config ping_game_version must be a string, using default ping_game_version: \"{}\"",
config.game_version
),
);
config.server_icon = get_string(
&cfg,
"server_icon",
&config.server_icon,
&format!(
"Config server_icon must be a string, using default server_icon: \"{}\"",
config.server_icon
),
);
let default_log_level = format!("{}", config.log_level).to_ascii_lowercase();
config.log_level = match &get_string(
&cfg,
"log_level",
&default_log_level,
&format!(
"Config log_level must be a string, using default log_level: {}",
default_log_level
),
)[..]
{
"off" => log::LevelFilter::Off,
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace");
config.log_level
}
};
config
}
pub fn load() -> Config {
let mut config = Config::default();
// Load the config
let config_path = Path::new("composition.toml");
if config_path.exists() {
if let Ok(cfg) = read_file(config_path) {
let cfg = String::from_utf8_lossy(&cfg);
if let Ok(cfg) = cfg.parse::<toml::Value>() {
config = Config::from_toml(cfg);
} else {
error!("Could not parse configuration file");
std::process::exit(1);
}
} else {
warn!(
"Could not read configuration file, creating {}",
config_path.to_str().unwrap_or("")
);
if config.write(config_path).is_err() {
error!("Could not write configuration file");
std::process::exit(1);
}
}
} else {
warn!(
"Configuration file does not exist, creating {}",
config_path.to_str().unwrap_or("")
);
if config.write(config_path).is_err() {
error!("Could not write configuration file");
std::process::exit(1);
}
}
// Load the server icon
let server_icon_path = Path::new(&config.server_icon);
if server_icon_path.exists() {
if let Ok(server_icon_bytes) = read_file(server_icon_path) {
config.server_icon_bytes = server_icon_bytes;
} else {
warn!(
"Could not read server icon file, creating {}",
server_icon_path.to_str().unwrap_or("")
);
if config.write_server_icon(server_icon_path).is_err() {
error!("Could not write server icon file");
std::process::exit(1);
}
}
} else {
warn!(
"Server icon file does not exist, creating {}",
server_icon_path.to_str().unwrap_or("")
);
if config.write_server_icon(server_icon_path).is_err() {
error!("Could not write server icon file");
std::process::exit(1);
}
}
config
}
pub fn write(&self, path: &Path) -> std::io::Result<()> {
use toml::{map::Map, Value};
let config = Value::Table({
let mut m = Map::new();
m.insert(
"server_icon".to_owned(),
Value::String(self.server_icon.clone()),
);
m.insert(
"log_level".to_owned(),
Value::String(format!("{}", self.log_level).to_ascii_lowercase()),
);
m.insert("max_players".to_owned(), Value::Integer(20));
m.insert("motd".to_owned(), Value::String(self.motd.clone()));
m.insert(
"ping_game_version".to_owned(),
Value::String(self.game_version.clone()),
);
m.insert("port".to_owned(), Value::Integer(25565));
m
});
if path.exists() {
std::fs::remove_file(path)?;
}
let mut file = File::create(path)?;
file.write_all(&toml::ser::to_vec(&config).unwrap())?;
Ok(())
}
pub fn write_server_icon(&self, path: &Path) -> std::io::Result<()> {
if path.exists() {
std::fs::remove_file(path)?;
}
let mut file = File::create(path)?;
file.write_all(&self.server_icon_bytes)?;
Ok(())
}
}

View File

@ -1,69 +0,0 @@
#[macro_use]
extern crate lazy_static;
pub mod config;
pub mod net;
pub mod server;
use crate::prelude::*;
use std::sync::mpsc::{self, Receiver};
lazy_static! {
pub static ref CONFIG: Config = Config::load();
pub static ref START_TIME: std::time::Instant = std::time::Instant::now();
}
/// Set up logging, read the config file, etc.
pub fn init() -> Receiver<()> {
let _ = START_TIME.elapsed();
// Set up fern logging.
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"[{date}][{target}][{level}] {message}",
date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
target = record.target(),
level = record.level(),
message = message,
))
})
.level(log::LevelFilter::Trace)
.chain(std::io::stdout())
.chain(fern::log_file("output.log").unwrap())
.apply()
.unwrap();
log::set_max_level(CONFIG.log_level);
// Set up the ctrl-c handler.
let (ctrlc_tx, ctrlc_rx) = mpsc::channel();
ctrlc::set_handler(move || {
ctrlc_tx.send(()).expect("Ctrl-C receiver disconnected");
})
.expect("Error setting Ctrl-C handler");
ctrlc_rx
}
/// Start the server.
pub async fn start_server() -> server::Server {
server::Server::new(format!("0.0.0.0:{}", CONFIG.port)).await
}
pub mod prelude {
pub use crate::{config::Config, CONFIG, START_TIME};
pub use log::*;
pub use serde::{Deserialize, Serialize};
pub use serde_json::json;
pub use uuid::Uuid;
pub type JSON = serde_json::Value;
pub type NBT = quartz_nbt::NbtCompound;
pub use std::collections::VecDeque;
pub use std::io::{Read, Write};
pub use substring::Substring;
pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
#[derive(Clone, Debug, PartialEq)]
pub enum ParseError {
NotEnoughData,
InvalidData,
VarIntTooBig,
}
pub type ParseResult<T> = Result<(T, usize), ParseError>;
}

View File

@ -1,5 +0,0 @@
pub mod numbers;
pub mod other;
pub use numbers::*;
pub use other::*;

View File

@ -1,211 +0,0 @@
use crate::prelude::*;
pub fn parse_byte(data: &[u8]) -> ParseResult<i8> {
if data.is_empty() {
Err(ParseError::NotEnoughData)
} else {
let value = i8::from_be_bytes([data[0]]);
Ok((value, 1))
}
}
pub fn serialize_byte(value: i8) -> [u8; 1] {
value.to_be_bytes()
}
pub fn parse_short(data: &[u8]) -> ParseResult<i16> {
if data.len() < 2 {
Err(ParseError::NotEnoughData)
} else {
let value = i16::from_be_bytes([data[0], data[1]]);
Ok((value, 2))
}
}
pub fn serialize_short(value: i16) -> [u8; 2] {
value.to_be_bytes()
}
pub fn parse_int(data: &[u8]) -> ParseResult<i32> {
if data.len() < 4 {
Err(ParseError::NotEnoughData)
} else {
let value = i32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Ok((value, 4))
}
}
pub fn serialize_int(value: i32) -> [u8; 4] {
value.to_be_bytes()
}
pub fn parse_long(data: &[u8]) -> ParseResult<i64> {
if data.len() < 8 {
Err(ParseError::NotEnoughData)
} else {
let value = i64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Ok((value, 8))
}
}
pub fn serialize_long(value: i64) -> [u8; 8] {
value.to_be_bytes()
}
pub fn parse_varint(data: &[u8]) -> ParseResult<i32> {
let mut offset = 0;
let mut output = 0i32;
let mut bytes_read = 0i32;
loop {
if data.len() <= offset {
return Err(ParseError::NotEnoughData);
}
output |= (((data[offset] & 0x7f) as i32) << (bytes_read * 7)) as i32;
bytes_read += 1;
if data[offset] & 0x80 != 0x80 {
break;
}
offset += 1;
if bytes_read >= 5 {
return Err(ParseError::VarIntTooBig);
}
}
Ok((output, bytes_read as usize))
}
pub fn serialize_varint(value: i32) -> Vec<u8> {
let mut value = value as u32;
let mut output = vec![];
loop {
let data = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
output.push(data);
break;
} else {
output.push(data | 0x80);
}
}
output
}
pub fn parse_float(data: &[u8]) -> ParseResult<f32> {
if data.len() < 4 {
Err(ParseError::NotEnoughData)
} else {
let value = f32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Ok((value, 4))
}
}
pub fn serialize_float(value: f32) -> [u8; 4] {
value.to_be_bytes()
}
pub fn parse_double(data: &[u8]) -> ParseResult<f64> {
if data.len() < 8 {
Err(ParseError::NotEnoughData)
} else {
let value = f64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Ok((value, 4))
}
}
pub fn serialize_double(value: f64) -> [u8; 8] {
value.to_be_bytes()
}
pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult<u8> {
if data.is_empty() {
Err(ParseError::NotEnoughData)
} else {
let value = u8::from_be_bytes([data[0]]);
Ok((value, 1))
}
}
pub fn serialize_unsigned_byte(value: u8) -> [u8; 1] {
value.to_be_bytes()
}
pub fn parse_unsigned_short(data: &[u8]) -> ParseResult<u16> {
if data.len() < 2 {
Err(ParseError::NotEnoughData)
} else {
let value = u16::from_be_bytes([data[0], data[1]]);
Ok((value, 2))
}
}
pub fn serialize_unsigned_short(value: u16) -> [u8; 2] {
value.to_be_bytes()
}
pub fn parse_unsigned_int(data: &[u8]) -> ParseResult<u32> {
if data.len() < 4 {
Err(ParseError::NotEnoughData)
} else {
let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Ok((value, 4))
}
}
pub fn serialize_unsigned_int(value: u32) -> [u8; 4] {
value.to_be_bytes()
}
pub fn parse_unsigned_long(data: &[u8]) -> ParseResult<u64> {
if data.len() < 8 {
Err(ParseError::NotEnoughData)
} else {
let value = u64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Ok((value, 8))
}
}
pub fn serialize_unsigned_long(value: u64) -> [u8; 8] {
value.to_be_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_varint_works() {
let tests = vec![
(Ok((0, 1)), vec![0x00]),
(Ok((1, 1)), vec![0x01]),
(Ok((2, 1)), vec![0x02]),
(Ok((127, 1)), vec![0x7f]),
(Ok((128, 2)), vec![0x80, 0x01]),
(Ok((255, 2)), vec![0xff, 0x01]),
(Ok((25565, 3)), vec![0xdd, 0xc7, 0x01]),
(Ok((2097151, 3)), vec![0xff, 0xff, 0x7f]),
(Ok((2147483647, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x07]),
(Ok((-1, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x0f]),
(Ok((-2147483648, 5)), vec![0x80, 0x80, 0x80, 0x80, 0x08]),
];
for test in &tests {
assert_eq!(test.0, parse_varint(&test.1));
}
}
#[test]
fn serialize_varint_works() {
let tests = vec![
(0, vec![0x00]),
(1, vec![0x01]),
(2, vec![0x02]),
(127, vec![0x7f]),
(128, vec![0x80, 0x01]),
(255, vec![0xff, 0x01]),
(25565, vec![0xdd, 0xc7, 0x01]),
(2097151, vec![0xff, 0xff, 0x7f]),
(2147483647, vec![0xff, 0xff, 0xff, 0xff, 0x07]),
(-1, vec![0xff, 0xff, 0xff, 0xff, 0x0f]),
(-2147483648, vec![0x80, 0x80, 0x80, 0x80, 0x08]),
];
for test in &tests {
assert_eq!(serialize_varint(test.0), test.1);
}
}
}

View File

@ -1,92 +0,0 @@
use super::*;
use crate::prelude::*;
pub fn parse_bool(data: &[u8]) -> ParseResult<bool> {
if data.is_empty() {
Err(ParseError::NotEnoughData)
} else {
Ok((data[0] == 1, 1))
}
}
pub fn serialize_bool(value: bool) -> [u8; 1] {
if value {
[0x01]
} else {
[0x00]
}
}
pub fn parse_string(data: &[u8]) -> ParseResult<String> {
let mut offset = 0;
let (length, offset_delta) = parse_varint(&data[offset..])?;
offset += offset_delta;
let length = length as usize;
if data.len() < offset + length {
return Err(ParseError::NotEnoughData);
}
let output = String::from_utf8_lossy(&data[offset..offset + length]).to_string();
offset += length;
Ok((output, offset))
}
pub fn serialize_string(value: &str) -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(value.len() as i32));
output.extend_from_slice(value.as_bytes());
output
}
pub fn parse_json(data: &[u8]) -> ParseResult<JSON> {
let (value_string, offset) = parse_string(data)?;
if let Ok(value) = serde_json::from_str(&value_string) {
Ok((value, offset))
} else {
Err(ParseError::InvalidData)
}
}
pub fn serialize_json(value: JSON) -> Vec<u8> {
serialize_string(&serde_json::to_string(&value).expect("Could not serialize JSON"))
}
pub fn parse_nbt(data: &[u8]) -> ParseResult<NBT> {
use quartz_nbt::io::{read_nbt, Flavor};
use std::io::Cursor;
let mut data = Cursor::new(data);
// let (value_string, offset) = parse_string(data)?;
if let Ok(value) = read_nbt(&mut data, Flavor::Uncompressed) {
Ok((value.0, data.position() as usize))
} else {
Err(ParseError::InvalidData)
}
}
pub fn serialize_nbt(value: NBT) -> Vec<u8> {
use quartz_nbt::io::{write_nbt, Flavor};
// serialize_string(&fastnbt::to_string(&value).expect("Could not serialize JSON"))
let mut out = vec![];
write_nbt(&mut out, None, &value, Flavor::Uncompressed).expect("Could not serialize NBT");
out
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Position {
pub x: i32,
pub y: i16,
pub z: i32,
}
impl Position {
pub fn new(x: i32, y: i16, z: i32) -> Position {
Position { x, y, z }
}
pub fn parse(data: &[u8]) -> ParseResult<Position> {
let (value, offset) = parse_unsigned_long(data)?;
let x = (value >> 38) as i32;
let y = (value & 0xFFF) as i16;
let z = ((value >> 12) & 0x3FFFFFF) as i32;
Ok((Position::new(x, y, z), offset))
}
pub fn serialize(&self) -> [u8; 8] {
(((self.x as u64 & 0x3FFFFFF) << 38)
| ((self.z as u64 & 0x3FFFFFF) << 12)
| (self.y as u64 & 0xFFF))
.to_be_bytes()
}
}

View File

@ -1,97 +0,0 @@
pub mod mctypes;
pub mod packets;
use crate::prelude::*;
use mctypes::*;
pub use packets::Packet;
use std::time::Instant;
use tokio::net::TcpStream;
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum NetworkClientState {
Disconnected,
Handshake,
Status,
Login,
Play,
}
pub struct NetworkClient {
pub id: u128,
pub state: NetworkClientState,
pub stream: TcpStream,
pub buffer: VecDeque<u8>,
pub packet_queue: VecDeque<Packet>,
pub last_data_time: Instant,
}
impl NetworkClient {
pub fn new(id: u128, stream: TcpStream) -> NetworkClient {
NetworkClient {
id,
state: NetworkClientState::Handshake,
stream,
buffer: VecDeque::new(),
packet_queue: VecDeque::new(),
last_data_time: Instant::now(),
}
}
pub async fn read_data(&mut self) -> Result<(), tokio::io::Error> {
trace!("NetworkClient.read_data() id {}", self.id);
// Try to read 4kb at a time until there is no more data.
loop {
let mut buf = [0; 4096];
match self.stream.try_read(&mut buf) {
// There is no data available.
Ok(0) => break,
// Data was read.
Ok(n) => {
trace!("Setting last_data_time for client {}", self.id);
self.last_data_time = Instant::now();
self.buffer.extend(&buf[0..n]);
debug!("Read {} bytes from client {}", n, self.id);
}
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => break,
Err(e) => {
return Err(e);
}
}
}
Ok(())
}
pub fn read_packet(&mut self) -> Result<(), ParseError> {
trace!("NetworkClient.read_packet() id {}", self.id);
self.buffer.make_contiguous();
if let (data, &[]) = self.buffer.as_slices() {
let mut offset = 0;
let (packet_length, offset_delta) = parse_varint(&data[offset..])?;
offset += offset_delta;
let packet_length = packet_length as usize;
let (packet_id, offset_delta) = parse_varint(&data[offset..])?;
offset += offset_delta;
let packet_id = packet_id as usize;
let (packet, offset_delta) =
Packet::parse_body(&data[offset..], packet_length, packet_id, self.state, true)?;
debug!("Got packet {:?} from client {}", packet, self.id);
offset += offset_delta;
self.packet_queue.push_back(packet);
let remaining_data = self.buffer.split_off(offset);
self.buffer = remaining_data;
}
Ok(())
}
pub async fn send_packet(&mut self, packet: Packet) -> Result<(), tokio::io::Error> {
trace!("NetworkClient.send_packet()");
debug!("Sending packet {:?} to client {}", packet, self.id);
let bytes = packet.serialize();
self.stream.write(&bytes).await?;
Ok(())
}
pub async fn disconnect(&mut self, reason: Option<JSON>) {
if let Some(reason) = reason {
if self.state == NetworkClientState::Login {
let _ = self.send_packet(Packet::CL00Disconnect { reason }).await;
}
}
self.state = NetworkClientState::Disconnected;
}
}

View File

@ -1,301 +0,0 @@
use super::{mctypes::*, NetworkClientState};
use crate::prelude::*;
#[derive(Clone, PartialEq, Debug)]
pub enum Packet {
// Handshake
SH00Handshake {
protocol_version: i32,
server_address: String,
server_port: u16,
next_state: NetworkClientState,
},
// Status
CS00Response {
version_name: String,
protocol_version: i32,
max_players: usize,
current_players: usize,
description: JSON,
},
CS01Pong {
payload: i64,
},
SS00Request,
SS01Ping {
payload: i64,
},
// Login
CL00Disconnect {
reason: JSON,
},
CL01EncryptionRequest {
server_id: String,
public_key: Vec<u8>,
verify_token: Vec<u8>,
},
CL02LoginSuccess {
uuid: u128,
username: String,
},
CL03SetCompression {
threshold: usize,
},
CL04LoginPluginRequest {
message_id: i32,
channel: String,
data: Vec<u8>,
},
SL00LoginStart {
username: String,
},
SL01EncryptionResponse {
shared_secret: Vec<u8>,
verify_token: Vec<u8>,
},
SL02LoginPluginResponse {
message_id: i32,
successful: bool,
data: Option<Vec<u8>>,
},
// Play
CP14WindowItems {
window_id: u8,
state_id: i32,
slots: Vec<NBT>,
carried_item: NBT,
},
CP26JoinGame,
CP48HeldItemChange,
CP66DeclareRecipes,
CP67Tags,
CP1BEntityStatus,
CP12DeclareCommands,
CP39UnlockRecipes,
CP22ChunkDataAndUpdateLight,
CP38PlayerPositionAndLook {
x: (f64, bool),
y: (f64, bool),
z: (f64, bool),
yaw: (f32, bool),
pitch: (f32, bool),
teleport_id: i32,
dismount_vehicle: bool,
},
CP36PlayerInfo,
CP49UpdateViewPosition,
CP25UpdateLight,
CP4BSpawnPosition {
location: Position,
angle: f32,
},
CP00TeleportConfirm,
SP05ClientSettings,
SP04ClientStatus,
}
impl Packet {
pub fn parse_body(
data: &[u8],
_length: usize,
id: usize,
state: NetworkClientState,
serverbound: bool,
) -> ParseResult<Packet> {
use NetworkClientState::*;
use Packet::*;
let mut offset = 0;
match state {
Disconnected => Err(ParseError::InvalidData),
Handshake => {
if id == 0x00 && serverbound {
let (protocol_version, offset_delta) = parse_varint(&data[offset..])?;
offset += offset_delta;
let (server_address, offset_delta) = parse_string(&data[offset..])?;
offset += offset_delta;
let (server_port, offset_delta) = parse_unsigned_short(&data[offset..])?;
offset += offset_delta;
let (next_state, offset_delta) = parse_varint(&data[offset..])?;
offset += offset_delta;
let next_state = match next_state {
1 => NetworkClientState::Status,
2 => NetworkClientState::Login,
_ => return Err(ParseError::InvalidData),
};
Ok((
SH00Handshake {
protocol_version,
server_address,
server_port,
next_state,
},
offset,
))
} else {
Err(ParseError::InvalidData)
}
}
Status => match id {
0x00 => {
if serverbound {
Ok((SS00Request, offset))
} else {
unimplemented!("Parse CS00Response")
}
}
0x01 => {
if serverbound {
let (payload, offset_delta) = parse_long(&data[offset..])?;
offset += offset_delta;
Ok((SS01Ping { payload }, offset))
} else {
unimplemented!("Parse CS01Pong")
}
}
_ => Err(ParseError::InvalidData),
},
Login => match id {
0x00 => {
if serverbound {
let (username, offset_delta) = parse_string(&data[offset..])?;
offset += offset_delta;
Ok((SL00LoginStart { username }, offset))
} else {
unimplemented!("Parse CL00Disconnect")
}
}
0x01 => {
if serverbound {
unimplemented!("Parse SL01EncryptionResponse")
} else {
unimplemented!("Parse CL01EncryptionRequest")
}
}
0x02 => {
if serverbound {
unimplemented!("Parse SL02LoginPluginResponse")
} else {
unimplemented!("Parse CL02LoginSuccess")
}
}
0x03 => {
if serverbound {
Err(ParseError::InvalidData)
} else {
unimplemented!("Parse CL03SetCompression")
}
}
0x04 => {
if serverbound {
Err(ParseError::InvalidData)
} else {
unimplemented!("Parse CL04LoginPluginRequest")
}
}
_ => Err(ParseError::InvalidData),
},
Play => unimplemented!("Parse Play packet"),
}
}
pub fn serialize(&self) -> Vec<u8> {
use Packet::*;
let (id, mut body): (usize, Vec<u8>) = match self {
CS00Response {
version_name,
protocol_version,
max_players,
current_players,
description,
} => (
0x00,
serialize_json(json!({
"version": {
"name": version_name,
"protocol": protocol_version,
},
"players": {
"max": max_players,
"online": current_players,
},
"description": description,
"favicon": format!("data:image/png;base64,{}", radix64::STD_NO_PAD.encode(&CONFIG.server_icon_bytes)),
})),
),
CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()),
CL00Disconnect { reason } => (0x00, serialize_json(reason.clone())),
CL02LoginSuccess { uuid, username } => (0x02, {
let mut out = vec![];
out.extend(uuid.to_be_bytes());
out.extend(serialize_string(username));
out
}),
CP14WindowItems {
window_id,
state_id,
slots,
carried_item,
} => (0x14, {
let mut out = vec![*window_id];
out.extend(serialize_varint(*state_id));
out.extend(serialize_varint(slots.len() as i32));
for slot in slots {
out.extend(serialize_nbt(slot.clone()));
}
out.extend(serialize_nbt(carried_item.clone()));
out
}),
CP38PlayerPositionAndLook {
x,
y,
z,
yaw,
pitch,
teleport_id,
dismount_vehicle,
} => (0x38, {
let mut out = vec![];
out.extend(serialize_double(x.0));
out.extend(serialize_double(y.0));
out.extend(serialize_double(z.0));
out.extend(serialize_float(yaw.0));
out.extend(serialize_float(pitch.0));
let mut flags = 0x00;
if x.1 {
flags |= 0x01;
}
if y.1 {
flags |= 0x02;
}
if z.1 {
flags |= 0x04;
}
if yaw.1 {
flags |= 0x10;
}
if pitch.1 {
flags |= 0x08;
}
out.push(flags);
out.extend(serialize_varint(*teleport_id));
out.extend(serialize_bool(*dismount_vehicle));
out
}),
CP4BSpawnPosition { location, angle } => (0x4b, {
let mut out = vec![];
out.extend(location.serialize());
out.extend(serialize_float(*angle));
out
}),
_ => unimplemented!("Serializing unknown packet"),
};
let mut id_and_body = serialize_varint(id as i32);
id_and_body.append(&mut body);
let mut output = serialize_varint(id_and_body.len() as i32);
output.append(&mut id_and_body);
output
}
}

View File

@ -1,196 +0,0 @@
use crate::{
net::{mctypes::Position, *},
prelude::*,
};
use std::time::Duration;
use tokio::{
net::{TcpListener, ToSocketAddrs},
sync::mpsc::{self, error::TryRecvError, UnboundedReceiver},
};
#[derive(Clone, Debug, PartialEq)]
pub enum ServerError {}
pub struct Server {
network_client_receiver: UnboundedReceiver<NetworkClient>,
clients: Vec<NetworkClient>,
}
impl Server {
pub async fn new<A: 'static + ToSocketAddrs + Send>(bind_address: A) -> Server {
let (client_tx, client_rx) = mpsc::unbounded_channel();
tokio::task::spawn(async move {
let listener = TcpListener::bind(bind_address)
.await
.expect("Could not bind to given address");
let mut id = 0u128;
loop {
trace!("Server accepting new client");
match listener.accept().await {
Ok((socket, addr)) => {
let _ = client_tx.send(NetworkClient::new(id, socket));
debug!("Connected client {} at {:?}", id, addr);
id += 1;
}
Err(_) => break,
}
}
});
Server {
network_client_receiver: client_rx,
clients: vec![],
}
}
pub async fn update(&mut self) -> Result<(), ServerError> {
trace!("Server.update()");
// Read new clients from the receiver
loop {
match self.network_client_receiver.try_recv() {
Ok(client) => self.clients.push(client),
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("Client sender disconnected"),
}
}
// Remove disconnected clients.
let mut i = 0;
while i < self.clients.len() {
if self.clients[i].state == NetworkClientState::Disconnected {
debug!("Removed client {}", self.clients[i].id);
self.clients.remove(i);
} else if self.clients[i].last_data_time.elapsed() > Duration::from_secs(10) {
debug!("Client {} timed out", self.clients[i].id);
self.clients[i].disconnect(None).await;
} else {
i += 1;
}
}
// Read data and try to parse packets from each client.
for client in self.clients.iter_mut() {
if client.state == NetworkClientState::Disconnected {
continue;
}
let _ = client.read_data().await;
'packet: loop {
match client.read_packet() {
Ok(_) => {}
Err(ParseError::NotEnoughData) => break 'packet,
Err(_) => {}
}
}
}
// Handle each packet for each player.
'client: for i in 0..self.clients.len() {
while let Some(packet) = self.clients[i].packet_queue.pop_front() {
if self.handle_packet(i, packet).await.is_err() {
continue 'client;
}
}
}
// Handle general world updates.
// Send out packets to each client.
Ok(())
}
pub async fn handle_packet(&mut self, client_index: usize, packet: Packet) -> Result<(), ()> {
use Packet::*;
trace!("Server.handle_packet()");
debug!("Handling packet {:?}", packet);
let mut current_players = 0;
for client in &self.clients {
if client.state == NetworkClientState::Play {
current_players += 1;
}
}
let client = &mut self.clients[client_index];
match packet {
SH00Handshake {
protocol_version,
server_address: _,
server_port: _,
next_state,
} => {
if protocol_version != CONFIG.protocol_version
&& next_state == NetworkClientState::Login
{
debug!(
"Disconnecting client {} for mismatched protocols: {} (expected {})",
client.id, protocol_version, CONFIG.protocol_version
);
client.disconnect(None).await;
return Err(());
}
client.state = next_state;
}
SS00Request => {
let _ = client
.send_packet(CS00Response {
version_name: CONFIG.game_version.clone(),
protocol_version: CONFIG.protocol_version,
max_players: CONFIG.max_players,
current_players,
description: json!({
"text": CONFIG.motd
}),
})
.await;
}
SS01Ping { payload } => {
let _ = client.send_packet(CS01Pong { payload }).await;
debug!("Disconnecting client {}, SLP completed", client.id);
client.disconnect(None).await;
}
SL00LoginStart { username } => {
debug!(
"Client {} is connecting with username {}",
client.id, username
);
if current_players >= CONFIG.max_players {
client
.disconnect(Some(json!({ "text": "Server full!" })))
.await;
}
// TODO: Authentication
// TODO: Encryption
// TODO: Compression
let _ = client
.send_packet(CL02LoginSuccess {
uuid: client.id,
username,
})
.await;
client.state = NetworkClientState::Play;
// Log them in.
let _ = client
.send_packet(CP4BSpawnPosition {
location: Position::new(0, 0, 0),
angle: 0.0,
})
.await;
let _ = client
.send_packet(CP14WindowItems {
window_id: 0,
state_id: 0,
slots: vec![quartz_nbt::compound! {}; 44],
carried_item: quartz_nbt::compound! {},
})
.await;
let _ = client
.send_packet(CP38PlayerPositionAndLook {
x: (0.0, false),
y: (0.0, false),
z: (0.0, false),
yaw: (0.0, false),
pitch: (0.0, false),
teleport_id: 0,
dismount_vehicle: false,
})
.await;
}
_ => unimplemented!("Handling unknown packet"),
}
Ok(())
}
pub async fn shutdown(self) -> Result<(), ServerError> {
trace!("Server.shutdown()");
Ok(())
}
}

View File

@ -2,13 +2,13 @@
name = "composition-protocol"
version = "0.1.0"
edition = "2021"
authors = ["Garen Tyler <garentyler@garen.dev>"]
description = "The Minecraft protocol implemented in a network-agnostic way"
license = "MIT"
[dependencies]
nom = "7"
serde_json = "1.0.94"
anyhow = "1.0.69"
tracing = { workspace = true }
byteorder = "1"
serde_json = "1.0.94"
thiserror = "1.0.39"
[[example]]
name = "packet_logger"
path = "examples/packet_logger/src/main.rs"

View File

@ -1,9 +0,0 @@
[package]
name = "packet_logger"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.69"
composition-protocol = { path = "../../" }
tokio = { version = "1.26.0", features = [ "full" ] }

View File

@ -1,117 +0,0 @@
use tokio::net::{TcpListener, TcpStream};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = TcpListener::bind("0.0.0.0:25566").await?;
println!("Proxy listening on port 25566, expecting server at 25565");
loop {
let (stream, _) = listener.accept().await?;
process_client(stream).await?;
}
}
async fn process_client(client: TcpStream) -> anyhow::Result<()> {
use composition_protocol::packet::GenericPacket;
client.readable().await?;
println!("Client connected");
let server = TcpStream::connect("localhost:25565").await?;
server.writable().await?;
println!("Server connected");
let mut client_state = composition_protocol::ClientState::Handshake;
let mut last_client_data_time = std::time::Instant::now();
let mut last_client_data = vec![];
let mut last_server_data_time = std::time::Instant::now();
let mut last_server_data = vec![];
loop {
let bytes = copy_bytes(&client, &server).await?;
if !bytes.is_empty() {
last_client_data_time = std::time::Instant::now();
last_client_data.extend_from_slice(&bytes);
if let Ok((d, packet)) =
GenericPacket::parse_uncompressed(&client_state, true, &last_client_data)
{
last_client_data = d.to_vec();
println!("C -> S: {:?}", packet);
match packet {
GenericPacket::SH00Handshake(handshake) => {
client_state = handshake.next_state;
}
GenericPacket::CP17Disconnect(_) => {
break;
}
_ => {}
}
}
}
let bytes = copy_bytes(&server, &client).await?;
if !bytes.is_empty() {
last_server_data_time = std::time::Instant::now();
last_server_data.extend_from_slice(&bytes);
if let Ok((d, packet)) =
GenericPacket::parse_uncompressed(&client_state, false, &last_server_data)
{
last_server_data = d.to_vec();
println!("S -> C: {:?}", packet);
match packet {
GenericPacket::CS01PingResponse(_) | GenericPacket::CL00Disconnect(_) => {
break;
}
GenericPacket::CL02LoginSuccess(_) => {
client_state = composition_protocol::ClientState::Play;
}
_ => {}
}
}
}
if last_client_data_time.elapsed() > std::time::Duration::from_secs(10)
|| last_server_data_time.elapsed() > std::time::Duration::from_secs(10)
{
println!("timed out");
break;
}
}
Ok(())
}
async fn copy_bytes(from: &TcpStream, to: &TcpStream) -> anyhow::Result<Vec<u8>> {
let mut bytes = vec![];
loop {
// Read 8kb at a time
let mut buf = vec![0u8; 8192];
let num_bytes = match from.try_read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
break;
}
Err(e) => {
return Err(e.into());
}
};
bytes.extend_from_slice(&buf[0..num_bytes]);
match to.try_write(&buf[0..num_bytes]) {
Ok(_n) => {}
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
break;
}
Err(e) => {
return Err(e.into());
}
}
}
Ok(bytes)
}

View File

@ -38,12 +38,10 @@ impl TryFrom<u8> for Difficulty {
#[derive(Error, Debug)]
pub enum ProtocolError {
#[error("invalid packet data")]
InvalidPacket,
#[error("invalid data")]
InvalidData,
#[error("not enough data")]
NotEnoughData,
#[error("unexpected end of file")]
UnexpectedEOF,
#[error("stream timed out")]
Timeout,
#[error("communicating to disconnected client")]

View File

@ -1,74 +1,38 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{
parse_json, parse_string, parse_uuid, parse_varint, serialize_json, serialize_string,
serialize_uuid, serialize_varint,
},
Chat, Uuid,
};
use crate::{util::*, Chat, Uuid};
#[derive(Clone, Debug, PartialEq)]
pub struct CL00Disconnect {
reason: Chat,
pub reason: Chat,
}
impl Packet for CL00Disconnect {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CL00Disconnect,
0x00,
crate::ClientState::Login,
false,
|data: &'data [u8]| -> ParseResult<'data, CL00Disconnect> {
let (data, reason) = parse_json(data)?;
Ok((data, CL00Disconnect { reason }))
}
fn serialize_body(&self) -> Vec<u8> {
serialize_json(&self.reason)
}
}
impl From<CL00Disconnect> for GenericPacket {
fn from(value: CL00Disconnect) -> Self {
GenericPacket::CL00Disconnect(value)
}
}
impl TryFrom<GenericPacket> for CL00Disconnect {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CL00Disconnect(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CL00Disconnect| -> Vec<u8> { serialize_json(&packet.reason) }
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL01EncryptionRequest {
server_id: String,
public_key: Vec<u8>,
verify_token: Vec<u8>,
pub server_id: String,
pub public_key: Vec<u8>,
pub verify_token: Vec<u8>,
}
impl Packet for CL01EncryptionRequest {
fn id() -> PacketId {
0x01
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CL01EncryptionRequest,
0x01,
crate::ClientState::Login,
false,
|data: &'data [u8]| -> ParseResult<'data, CL01EncryptionRequest> {
let (data, server_id) = parse_string(data)?;
let (data, public_key_len) = parse_varint(data)?;
let (data, public_key) = nom::bytes::streaming::take(public_key_len as usize)(data)?;
let (data, public_key) = take_bytes(public_key_len as usize)(data)?;
let (data, verify_token_len) = parse_varint(data)?;
let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?;
let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?;
Ok((
data,
@ -78,50 +42,35 @@ impl Packet for CL01EncryptionRequest {
verify_token: verify_token.to_vec(),
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CL01EncryptionRequest| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_string(&self.server_id));
output.extend_from_slice(&serialize_varint(self.public_key.len() as i32));
output.extend_from_slice(&self.public_key);
output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32));
output.extend_from_slice(&self.verify_token);
output.extend_from_slice(&serialize_string(&packet.server_id));
output.extend_from_slice(&serialize_varint(packet.public_key.len() as i32));
output.extend_from_slice(&packet.public_key);
output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32));
output.extend_from_slice(&packet.verify_token);
output
}
}
impl From<CL01EncryptionRequest> for GenericPacket {
fn from(value: CL01EncryptionRequest) -> Self {
GenericPacket::CL01EncryptionRequest(value)
}
}
impl TryFrom<GenericPacket> for CL01EncryptionRequest {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CL01EncryptionRequest(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL02LoginSuccess {
uuid: Uuid,
username: String,
properties: Vec<CL02LoginSuccessProperty>,
pub uuid: Uuid,
pub username: String,
pub properties: Vec<CL02LoginSuccessProperty>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CL02LoginSuccessProperty {
name: String,
value: String,
signature: Option<String>,
pub name: String,
pub value: String,
pub signature: Option<String>,
}
impl CL02LoginSuccessProperty {
pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> {
pub fn parse(data: &[u8]) -> ParseResult<'_, Self> {
let (data, name) = parse_string(data)?;
let (data, value) = parse_string(data)?;
let (data, is_signed) = nom::bytes::streaming::take(1usize)(data)?;
let (data, is_signed) = take_bytes(1usize)(data)?;
if is_signed == [0x01] {
let (data, signature) = parse_string(data)?;
Ok((
@ -157,18 +106,12 @@ impl CL02LoginSuccessProperty {
output
}
}
impl Packet for CL02LoginSuccess {
fn id() -> PacketId {
0x02
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CL02LoginSuccess,
0x02,
crate::ClientState::Login,
false,
|data: &'data [u8]| -> ParseResult<'data, CL02LoginSuccess> {
let (data, uuid) = parse_uuid(data)?;
let (data, username) = parse_string(data)?;
let (mut data, properties_len) = parse_varint(data)?;
@ -187,91 +130,47 @@ impl Packet for CL02LoginSuccess {
properties,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CL02LoginSuccess| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_uuid(&self.uuid));
output.extend_from_slice(&serialize_string(&self.username));
output.extend_from_slice(&serialize_varint(self.properties.len() as i32));
for property in &self.properties {
output.extend_from_slice(&serialize_uuid(&packet.uuid));
output.extend_from_slice(&serialize_string(&packet.username));
output.extend_from_slice(&serialize_varint(packet.properties.len() as i32));
for property in &packet.properties {
output.extend_from_slice(&property.serialize());
}
output
}
}
impl From<CL02LoginSuccess> for GenericPacket {
fn from(value: CL02LoginSuccess) -> Self {
GenericPacket::CL02LoginSuccess(value)
}
}
impl TryFrom<GenericPacket> for CL02LoginSuccess {
type Error = ();
);
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CL02LoginSuccess(packet) => Ok(packet),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CL03SetCompression {
threshold: i32,
pub threshold: i32,
}
impl Packet for CL03SetCompression {
fn id() -> PacketId {
0x03
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CL03SetCompression,
0x03,
crate::ClientState::Login,
false,
|data: &'data [u8]| -> ParseResult<'data, CL03SetCompression> {
let (data, threshold) = parse_varint(data)?;
Ok((data, CL03SetCompression { threshold }))
}
fn serialize_body(&self) -> Vec<u8> {
serialize_varint(self.threshold)
}
}
impl From<CL03SetCompression> for GenericPacket {
fn from(value: CL03SetCompression) -> Self {
GenericPacket::CL03SetCompression(value)
}
}
impl TryFrom<GenericPacket> for CL03SetCompression {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CL03SetCompression(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CL03SetCompression| -> Vec<u8> { serialize_varint(packet.threshold) }
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL04LoginPluginRequest {
message_id: i32,
channel: String,
data: Vec<u8>,
pub message_id: i32,
pub channel: String,
pub data: Vec<u8>,
}
impl Packet for CL04LoginPluginRequest {
fn id() -> PacketId {
0x04
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CL04LoginPluginRequest,
0x04,
crate::ClientState::Login,
false,
|data: &'data [u8]| -> ParseResult<'data, CL04LoginPluginRequest> {
let (data, message_id) = parse_varint(data)?;
let (data, channel) = parse_string(data)?;
Ok((
@ -282,27 +181,12 @@ impl Packet for CL04LoginPluginRequest {
data: data.to_vec(),
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CL04LoginPluginRequest| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.message_id));
output.extend_from_slice(&serialize_string(&self.channel));
output.extend_from_slice(&self.data);
output.extend_from_slice(&serialize_varint(packet.message_id));
output.extend_from_slice(&serialize_string(&packet.channel));
output.extend_from_slice(&packet.data);
output
}
}
impl From<CL04LoginPluginRequest> for GenericPacket {
fn from(value: CL04LoginPluginRequest) -> Self {
GenericPacket::CL04LoginPluginRequest(value)
}
}
impl TryFrom<GenericPacket> for CL04LoginPluginRequest {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CL04LoginPluginRequest(packet) => Ok(packet),
_ => Err(()),
}
}
}
);

View File

@ -1,51 +1,58 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{
parse_json, parse_uuid, parse_varint, serialize_json, serialize_uuid, serialize_varint,
Position,
},
Chat, Difficulty, Uuid,
};
use crate::{util::*, Chat, Difficulty, ProtocolError, Uuid};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP00SpawnEntity {
entity_id: i32,
entity_uuid: Uuid,
kind: i32,
x: f64,
y: f64,
z: f64,
pitch: u8,
yaw: u8,
head_yaw: u8,
data: i32,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
pub entity_id: i32,
pub entity_uuid: Uuid,
pub kind: i32,
pub x: f64,
pub y: f64,
pub z: f64,
pub pitch: u8,
pub yaw: u8,
pub head_yaw: u8,
pub data: i32,
pub velocity_x: i16,
pub velocity_y: i16,
pub velocity_z: i16,
}
impl Packet for CP00SpawnEntity {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CP00SpawnEntity,
0x00,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP00SpawnEntity> {
let (data, entity_id) = parse_varint(data)?;
let (data, entity_uuid) = parse_uuid(data)?;
let (data, kind) = parse_varint(data)?;
let (data, x) = nom::number::streaming::be_f64(data)?;
let (data, y) = nom::number::streaming::be_f64(data)?;
let (data, z) = nom::number::streaming::be_f64(data)?;
let (data, t) = nom::bytes::streaming::take(3usize)(data)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let x = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let y = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let z = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, t) = take_bytes(3usize)(data)?;
let (data, d) = parse_varint(data)?;
let (data, velocity_x) = nom::number::streaming::be_i16(data)?;
let (data, velocity_y) = nom::number::streaming::be_i16(data)?;
let (data, velocity_z) = nom::number::streaming::be_i16(data)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_x = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_y = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_z = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((
data,
CP00SpawnEntity {
@ -64,64 +71,43 @@ impl Packet for CP00SpawnEntity {
velocity_z,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP00SpawnEntity| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.entity_id));
output.extend_from_slice(&serialize_uuid(&self.entity_uuid));
output.extend_from_slice(&serialize_varint(self.kind));
output.extend_from_slice(&self.x.to_be_bytes());
output.extend_from_slice(&self.y.to_be_bytes());
output.extend_from_slice(&self.z.to_be_bytes());
output.push(self.pitch);
output.push(self.yaw);
output.push(self.head_yaw);
output.extend_from_slice(&serialize_varint(self.data));
output.extend_from_slice(&self.velocity_x.to_be_bytes());
output.extend_from_slice(&self.velocity_y.to_be_bytes());
output.extend_from_slice(&self.velocity_z.to_be_bytes());
output.extend_from_slice(&serialize_varint(packet.entity_id));
output.extend_from_slice(&serialize_uuid(&packet.entity_uuid));
output.extend_from_slice(&serialize_varint(packet.kind));
output.extend_from_slice(&packet.x.to_be_bytes());
output.extend_from_slice(&packet.y.to_be_bytes());
output.extend_from_slice(&packet.z.to_be_bytes());
output.push(packet.pitch);
output.push(packet.yaw);
output.push(packet.head_yaw);
output.extend_from_slice(&serialize_varint(packet.data));
output.extend_from_slice(&packet.velocity_x.to_be_bytes());
output.extend_from_slice(&packet.velocity_y.to_be_bytes());
output.extend_from_slice(&packet.velocity_z.to_be_bytes());
output
}
}
impl From<CP00SpawnEntity> for GenericPacket {
fn from(value: CP00SpawnEntity) -> Self {
GenericPacket::CP00SpawnEntity(value)
}
}
impl TryFrom<GenericPacket> for CP00SpawnEntity {
type Error = ();
);
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP00SpawnEntity(packet) => Ok(packet),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP0BChangeDifficulty {
difficulty: Difficulty,
is_locked: bool,
pub difficulty: Difficulty,
pub is_locked: bool,
}
impl Packet for CP0BChangeDifficulty {
fn id() -> PacketId {
0x0b
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, difficulty) = nom::number::streaming::be_u8(data)?;
let difficulty: Difficulty = difficulty
crate::packet::packet!(
CP0BChangeDifficulty,
0x0b,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP0BChangeDifficulty> {
let (data, difficulty) = take_bytes(1)(data)?;
let difficulty: Difficulty = difficulty[0]
.try_into()
.expect("TODO: handle incorrect difficulty");
let (data, is_locked) = nom::number::streaming::be_u8(data)?;
let is_locked = is_locked > 0;
let (data, is_locked) = take_bytes(1)(data)?;
let is_locked = is_locked[0] > 0;
Ok((
data,
CP0BChangeDifficulty {
@ -129,131 +115,73 @@ impl Packet for CP0BChangeDifficulty {
is_locked,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP0BChangeDifficulty| -> Vec<u8> {
let mut output = vec![];
output.push(self.difficulty as u8);
output.push(if self.is_locked { 0x01 } else { 0x00 });
output.push(packet.difficulty as u8);
output.push(if packet.is_locked { 0x01 } else { 0x00 });
output
}
}
impl From<CP0BChangeDifficulty> for GenericPacket {
fn from(value: CP0BChangeDifficulty) -> Self {
GenericPacket::CP0BChangeDifficulty(value)
}
}
impl TryFrom<GenericPacket> for CP0BChangeDifficulty {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP0BChangeDifficulty(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CP17Disconnect {
reason: Chat,
pub reason: Chat,
}
impl Packet for CP17Disconnect {
fn id() -> PacketId {
0x17
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CP17Disconnect,
0x17,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP17Disconnect> {
let (data, reason) = parse_json(data)?;
Ok((data, CP17Disconnect { reason }))
}
fn serialize_body(&self) -> Vec<u8> {
serialize_json(&self.reason)
}
}
impl From<CP17Disconnect> for GenericPacket {
fn from(value: CP17Disconnect) -> Self {
GenericPacket::CP17Disconnect(value)
}
}
impl TryFrom<GenericPacket> for CP17Disconnect {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP17Disconnect(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CP17Disconnect| -> Vec<u8> { serialize_json(&packet.reason) }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP1FKeepAlive {
payload: i64,
pub payload: i64,
}
impl Packet for CP1FKeepAlive {
fn id() -> PacketId {
0x1f
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, payload) = nom::number::streaming::be_i64(data)?;
crate::packet::packet!(
CP1FKeepAlive,
0x1f,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP1FKeepAlive> {
let (data, mut bytes) = take_bytes(8)(data)?;
let payload = bytes
.read_i64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((data, CP1FKeepAlive { payload }))
}
fn serialize_body(&self) -> Vec<u8> {
self.payload.to_be_bytes().to_vec()
}
}
impl From<CP1FKeepAlive> for GenericPacket {
fn from(value: CP1FKeepAlive) -> Self {
GenericPacket::CP1FKeepAlive(value)
}
}
impl TryFrom<GenericPacket> for CP1FKeepAlive {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP1FKeepAlive(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CP1FKeepAlive| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP21WorldEvent {
event: i32,
location: Position,
data: i32,
disable_relative_volume: bool,
pub event: i32,
pub location: Position,
pub data: i32,
pub disable_relative_volume: bool,
}
impl Packet for CP21WorldEvent {
fn id() -> PacketId {
0x21
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, event) = nom::number::streaming::be_i32(data)?;
crate::packet::packet!(
CP21WorldEvent,
0x21,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP21WorldEvent> {
let (data, mut bytes) = take_bytes(4)(data)?;
let event = bytes
.read_i32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, location) = Position::parse(data)?;
let (data, d) = nom::number::streaming::be_i32(data)?;
let (data, disable_relative_volume) = nom::bytes::streaming::take(1usize)(data)?;
let (data, mut bytes) = take_bytes(4)(data)?;
let d = bytes
.read_i32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, disable_relative_volume) = take_bytes(1usize)(data)?;
let disable_relative_volume = disable_relative_volume == [0x01];
Ok((
data,
@ -264,59 +192,47 @@ impl Packet for CP21WorldEvent {
disable_relative_volume,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP21WorldEvent| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.event.to_be_bytes());
output.extend_from_slice(&self.location.serialize());
output.extend_from_slice(&self.data.to_be_bytes());
output.push(if self.disable_relative_volume {
output.extend_from_slice(&packet.event.to_be_bytes());
output.extend_from_slice(&packet.location.serialize());
output.extend_from_slice(&packet.data.to_be_bytes());
output.push(if packet.disable_relative_volume {
0x01
} else {
0x00
});
output
}
}
impl From<CP21WorldEvent> for GenericPacket {
fn from(value: CP21WorldEvent) -> Self {
GenericPacket::CP21WorldEvent(value)
}
}
impl TryFrom<GenericPacket> for CP21WorldEvent {
type Error = ();
);
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP21WorldEvent(packet) => Ok(packet),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP50SetEntityVelocity {
entity_id: i32,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
pub entity_id: i32,
pub velocity_x: i16,
pub velocity_y: i16,
pub velocity_z: i16,
}
impl Packet for CP50SetEntityVelocity {
fn id() -> PacketId {
0x50
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CP50SetEntityVelocity,
0x50,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP50SetEntityVelocity> {
let (data, entity_id) = parse_varint(data)?;
let (data, velocity_x) = nom::number::streaming::be_i16(data)?;
let (data, velocity_y) = nom::number::streaming::be_i16(data)?;
let (data, velocity_z) = nom::number::streaming::be_i16(data)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_x = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_y = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let velocity_z = bytes
.read_i16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((
data,
CP50SetEntityVelocity {
@ -326,51 +242,33 @@ impl Packet for CP50SetEntityVelocity {
velocity_z,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP50SetEntityVelocity| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.entity_id));
output.extend_from_slice(&self.velocity_x.to_be_bytes());
output.extend_from_slice(&self.velocity_y.to_be_bytes());
output.extend_from_slice(&self.velocity_z.to_be_bytes());
output.extend_from_slice(&serialize_varint(packet.entity_id));
output.extend_from_slice(&packet.velocity_x.to_be_bytes());
output.extend_from_slice(&packet.velocity_y.to_be_bytes());
output.extend_from_slice(&packet.velocity_z.to_be_bytes());
output
}
}
impl From<CP50SetEntityVelocity> for GenericPacket {
fn from(value: CP50SetEntityVelocity) -> Self {
GenericPacket::CP50SetEntityVelocity(value)
}
}
impl TryFrom<GenericPacket> for CP50SetEntityVelocity {
type Error = ();
);
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP50SetEntityVelocity(packet) => Ok(packet),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP52SetExperience {
experience_bar: f32,
total_experience: i32,
level: i32,
pub experience_bar: f32,
pub total_experience: i32,
pub level: i32,
}
impl Packet for CP52SetExperience {
fn id() -> PacketId {
0x52
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, experience_bar) = nom::number::streaming::be_f32(data)?;
crate::packet::packet!(
CP52SetExperience,
0x52,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP52SetExperience> {
let (data, mut bytes) = take_bytes(4)(data)?;
let experience_bar = bytes
.read_f32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, total_experience) = parse_varint(data)?;
let (data, level) = parse_varint(data)?;
Ok((
@ -381,65 +279,46 @@ impl Packet for CP52SetExperience {
level,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP52SetExperience| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.experience_bar.to_be_bytes());
output.extend_from_slice(&serialize_varint(self.total_experience));
output.extend_from_slice(&serialize_varint(self.level));
output.extend_from_slice(&packet.experience_bar.to_be_bytes());
output.extend_from_slice(&serialize_varint(packet.total_experience));
output.extend_from_slice(&serialize_varint(packet.level));
output
}
}
impl From<CP52SetExperience> for GenericPacket {
fn from(value: CP52SetExperience) -> Self {
GenericPacket::CP52SetExperience(value)
}
}
impl TryFrom<GenericPacket> for CP52SetExperience {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP52SetExperience(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CP68EntityEffect {
entity_id: i32,
effect_id: i32,
amplifier: i8,
duration: i32,
is_ambient: bool,
show_particles: bool,
show_icon: bool,
has_factor_data: bool,
// TODO: factor_codec: NBT
pub entity_id: i32,
pub effect_id: i32,
pub amplifier: i8,
pub duration: i32,
pub is_ambient: bool,
pub show_particles: bool,
pub show_icon: bool,
pub has_factor_data: bool,
// TODO: pub factor_codec: NBT
}
impl Packet for CP68EntityEffect {
fn id() -> PacketId {
0x68
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CP68EntityEffect,
0x68,
crate::ClientState::Play,
false,
|data: &'data [u8]| -> ParseResult<'data, CP68EntityEffect> {
let (data, entity_id) = parse_varint(data)?;
let (data, effect_id) = parse_varint(data)?;
let (data, amplifier) = nom::number::streaming::be_i8(data)?;
let (data, amplifier) = take_bytes(1)(data)?;
let amplifier = amplifier[0] as i8;
let (data, duration) = parse_varint(data)?;
let (data, flags) = nom::number::streaming::be_i8(data)?;
let (data, flags) = take_bytes(1)(data)?;
let flags = flags[0] as i8;
let is_ambient = flags & 0x01 > 0;
let show_particles = flags & 0x02 > 0;
let show_icon = flags & 0x04 > 0;
let (data, has_factor_data) = nom::number::streaming::be_u8(data)?;
let has_factor_data = has_factor_data > 0;
let (data, has_factor_data) = take_bytes(1)(data)?;
let has_factor_data = has_factor_data[0] > 0;
// TODO: factor_codec
Ok((
@ -455,40 +334,25 @@ impl Packet for CP68EntityEffect {
has_factor_data,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &CP68EntityEffect| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.entity_id));
output.extend_from_slice(&serialize_varint(self.effect_id));
output.push(self.amplifier as u8);
output.extend_from_slice(&serialize_varint(self.duration));
output.extend_from_slice(&serialize_varint(packet.entity_id));
output.extend_from_slice(&serialize_varint(packet.effect_id));
output.push(packet.amplifier as u8);
output.extend_from_slice(&serialize_varint(packet.duration));
let mut flags = 0x00i8;
if self.is_ambient {
if packet.is_ambient {
flags |= 0x01;
}
if self.show_particles {
if packet.show_particles {
flags |= 0x02;
}
if self.show_icon {
if packet.show_icon {
flags |= 0x04;
}
output.push(flags as u8);
// TODO: factor_codec
output
}
}
impl From<CP68EntityEffect> for GenericPacket {
fn from(value: CP68EntityEffect) -> Self {
GenericPacket::CP68EntityEffect(value)
}
}
impl TryFrom<GenericPacket> for CP68EntityEffect {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CP68EntityEffect(packet) => Ok(packet),
_ => Err(()),
}
}
}
);

View File

@ -1,83 +1,37 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{parse_json, serialize_json},
Json,
};
use crate::{util::*, Json, ProtocolError};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Clone, Debug, PartialEq)]
pub struct CS00StatusResponse {
response: Json,
pub response: Json,
}
impl Packet for CS00StatusResponse {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Status
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
CS00StatusResponse,
0x00,
crate::ClientState::Status,
false,
|data: &'data [u8]| -> ParseResult<'data, CS00StatusResponse> {
let (data, response) = parse_json(data)?;
Ok((data, CS00StatusResponse { response }))
}
fn serialize_body(&self) -> Vec<u8> {
serialize_json(&self.response)
}
}
impl From<CS00StatusResponse> for GenericPacket {
fn from(value: CS00StatusResponse) -> Self {
GenericPacket::CS00StatusResponse(value)
}
}
impl TryFrom<GenericPacket> for CS00StatusResponse {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CS00StatusResponse(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CS00StatusResponse| -> Vec<u8> { serialize_json(&packet.response) }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CS01PingResponse {
payload: i64,
pub payload: i64,
}
impl Packet for CS01PingResponse {
fn id() -> PacketId {
0x01
}
fn client_state() -> crate::ClientState {
crate::ClientState::Status
}
fn serverbound() -> bool {
false
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, payload) = nom::number::streaming::be_i64(data)?;
crate::packet::packet!(
CS01PingResponse,
0x01,
crate::ClientState::Status,
false,
|data: &'data [u8]| -> ParseResult<'data, CS01PingResponse> {
let (data, mut bytes) = take_bytes(8)(data)?;
let payload = bytes
.read_i64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((data, CS01PingResponse { payload }))
}
fn serialize_body(&self) -> Vec<u8> {
self.payload.to_be_bytes().to_vec()
}
}
impl From<CS01PingResponse> for GenericPacket {
fn from(value: CS01PingResponse) -> Self {
GenericPacket::CS01PingResponse(value)
}
}
impl TryFrom<GenericPacket> for CS01PingResponse {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::CS01PingResponse(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &CS01PingResponse| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
);

View File

@ -1,76 +1,48 @@
pub mod clientbound;
pub mod serverbound;
use crate::ClientState;
pub type PacketId = i32;
pub trait Packet: TryFrom<GenericPacket> + Into<GenericPacket> + std::fmt::Debug {
fn id() -> PacketId;
fn client_state() -> crate::ClientState;
fn serverbound() -> bool;
pub trait Packet: std::fmt::Debug + Clone + TryFrom<GenericPacket> + Into<GenericPacket> {
const ID: PacketId;
const CLIENT_STATE: crate::ClientState;
const IS_SERVERBOUND: bool;
// The slice given should only be the exact amount of data in the body.
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self>
fn parse_body(data: &[u8]) -> crate::util::ParseResult<'_, Self>
where
Self: Sized;
fn serialize_body(&self) -> Vec<u8>;
}
macro_rules! generic_packet {
($($packet_type: ident),*) => {
#[derive(Clone, Debug, PartialEq)]
pub enum GenericPacket {
// Handshake
SH00Handshake(serverbound::SH00Handshake),
// Status
SS00StatusRequest(serverbound::SS00StatusRequest),
SS01PingRequest(serverbound::SS01PingRequest),
CS00StatusResponse(clientbound::CS00StatusResponse),
CS01PingResponse(clientbound::CS01PingResponse),
// Login
SL00LoginStart(serverbound::SL00LoginStart),
SL01EncryptionResponse(serverbound::SL01EncryptionResponse),
SL02LoginPluginResponse(serverbound::SL02LoginPluginResponse),
CL00Disconnect(clientbound::CL00Disconnect),
CL01EncryptionRequest(clientbound::CL01EncryptionRequest),
CL02LoginSuccess(clientbound::CL02LoginSuccess),
CL03SetCompression(clientbound::CL03SetCompression),
CL04LoginPluginRequest(clientbound::CL04LoginPluginRequest),
// Play
SP08CommandSuggestionsRequest(serverbound::SP08CommandSuggestionsRequest),
SP11KeepAlive(serverbound::SP11KeepAlive),
SP13SetPlayerPosition(serverbound::SP13SetPlayerPosition),
SP14SetPlayerPositionAndRotation(serverbound::SP14SetPlayerPositionAndRotation),
SP15SetPlayerRotation(serverbound::SP15SetPlayerRotation),
CP00SpawnEntity(clientbound::CP00SpawnEntity),
CP0BChangeDifficulty(clientbound::CP0BChangeDifficulty),
CP17Disconnect(clientbound::CP17Disconnect),
CP1FKeepAlive(clientbound::CP1FKeepAlive),
CP21WorldEvent(clientbound::CP21WorldEvent),
CP50SetEntityVelocity(clientbound::CP50SetEntityVelocity),
CP52SetExperience(clientbound::CP52SetExperience),
CP68EntityEffect(clientbound::CP68EntityEffect),
// Until we implement all the packets this will stay.
UnimplementedPacket(PacketId),
$(
$packet_type($packet_type),
)*
}
impl GenericPacket {
#[tracing::instrument]
pub fn parse_uncompressed<'data>(
client_state: &ClientState,
serverbound: bool,
data: &'data [u8],
) -> nom::IResult<&'data [u8], Self> {
client_state: crate::ClientState,
is_serverbound: bool,
data: &'data [u8]
) -> crate::util::ParseResult<'data, Self> {
tracing::trace!(
"GenericPacket::parse_uncompressed: {:?} {} {:?}",
client_state,
is_serverbound,
data
);
let (data, packet_length) = crate::util::parse_varint(data)?;
let (data, packet_data) = nom::bytes::streaming::take(packet_length as usize)(data)?;
let (data, packet_data) = crate::util::take_bytes(packet_length as usize)(data)?;
let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?;
let (_packet_data, packet_body) =
Self::parse_body(client_state, packet_id, serverbound, packet_data)?;
Self::parse_body(client_state, packet_id, is_serverbound, packet_data)?;
// if !packet_data.is_empty() {
// println!("Packet data not empty after parsing!");
@ -78,211 +50,119 @@ impl GenericPacket {
Ok((data, packet_body))
}
#[tracing::instrument]
pub fn parse_body<'data>(
client_state: &ClientState,
packet_id: PacketId,
serverbound: bool,
client_state: crate::ClientState,
packet_id: crate::packet::PacketId,
is_serverbound: bool,
data: &'data [u8],
) -> nom::IResult<&'data [u8], Self> {
fn mapper<P: Into<GenericPacket> + Sized>(
(data, packet): (&[u8], P),
) -> (&[u8], GenericPacket) {
(data, Into::<GenericPacket>::into(packet))
) -> crate::util::ParseResult<'data, Self> {
tracing::trace!(
"GenericPacket::parse_body: {:?} {} {}",
client_state,
packet_id,
is_serverbound
);
match (client_state, packet_id, is_serverbound) {
$(
($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse_body(data).map(|(data, packet)| (data, Into::<GenericPacket>::into(packet))),
)*
_ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))),
}
}
match (client_state, packet_id, serverbound) {
// Handshake
(ClientState::Handshake, 0x00, true) => {
serverbound::SH00Handshake::parse_body(data).map(mapper)
}
// Status
(ClientState::Status, 0x00, true) => {
serverbound::SS00StatusRequest::parse_body(data).map(mapper)
}
(ClientState::Status, 0x01, true) => {
serverbound::SS00StatusRequest::parse_body(data).map(mapper)
}
(ClientState::Status, 0x00, false) => {
clientbound::CS00StatusResponse::parse_body(data).map(mapper)
}
(ClientState::Status, 0x01, false) => {
clientbound::CS01PingResponse::parse_body(data).map(mapper)
}
// Login
(ClientState::Login, 0x00, true) => {
serverbound::SL00LoginStart::parse_body(data).map(mapper)
}
(ClientState::Login, 0x01, true) => {
serverbound::SL01EncryptionResponse::parse_body(data).map(mapper)
}
(ClientState::Login, 0x02, true) => {
serverbound::SL02LoginPluginResponse::parse_body(data).map(mapper)
}
(ClientState::Login, 0x00, false) => {
clientbound::CL00Disconnect::parse_body(data).map(mapper)
}
(ClientState::Login, 0x01, false) => {
clientbound::CL01EncryptionRequest::parse_body(data).map(mapper)
}
(ClientState::Login, 0x02, false) => {
clientbound::CL02LoginSuccess::parse_body(data).map(mapper)
}
(ClientState::Login, 0x03, false) => {
clientbound::CL03SetCompression::parse_body(data).map(mapper)
}
(ClientState::Login, 0x04, false) => {
clientbound::CL04LoginPluginRequest::parse_body(data).map(mapper)
}
// Play
(ClientState::Play, 0x08, true) => {
serverbound::SP08CommandSuggestionsRequest::parse_body(data).map(mapper)
}
(ClientState::Play, 0x11, true) => {
serverbound::SP11KeepAlive::parse_body(data).map(mapper)
}
(ClientState::Play, 0x13, true) => {
serverbound::SP13SetPlayerPosition::parse_body(data).map(mapper)
}
(ClientState::Play, 0x14, true) => {
serverbound::SP14SetPlayerPositionAndRotation::parse_body(data).map(mapper)
}
(ClientState::Play, 0x15, true) => {
serverbound::SP15SetPlayerRotation::parse_body(data).map(mapper)
}
(ClientState::Play, 0x00, false) => {
clientbound::CP00SpawnEntity::parse_body(data).map(mapper)
}
(ClientState::Play, 0x0b, false) => {
clientbound::CP0BChangeDifficulty::parse_body(data).map(mapper)
}
(ClientState::Play, 0x17, false) => {
clientbound::CP17Disconnect::parse_body(data).map(mapper)
}
(ClientState::Play, 0x1f, false) => {
clientbound::CP1FKeepAlive::parse_body(data).map(mapper)
}
(ClientState::Play, 0x21, false) => {
clientbound::CP21WorldEvent::parse_body(data).map(mapper)
}
(ClientState::Play, 0x50, false) => {
clientbound::CP50SetEntityVelocity::parse_body(data).map(mapper)
}
(ClientState::Play, 0x52, false) => {
clientbound::CP52SetExperience::parse_body(data).map(mapper)
}
(ClientState::Play, 0x68, false) => {
clientbound::CP68EntityEffect::parse_body(data).map(mapper)
}
_ => Ok((&data[0..0], GenericPacket::UnimplementedPacket(packet_id))),
// Invalid packet
// _ => Err(nom::Err::Failure(nom::error::Error::new(
// data,
// nom::error::ErrorKind::Verify,
// ))),
}
}
pub fn serialize(&self) -> (PacketId, Vec<u8>) {
use GenericPacket::*;
#[tracing::instrument]
pub fn serialize(&self) -> (crate::packet::PacketId, Vec<u8>) {
tracing::trace!("GenericPacket::serialize: {:?}", self);
match self {
$(
Self::$packet_type(packet) => ($packet_type::ID, packet.serialize_body()),
)*
}
}
}
};
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UnimplementedPacket(i32);
packet!(
UnimplementedPacket,
0x00,
crate::ClientState::Disconnected,
false,
|data: &'data [u8]| -> crate::util::ParseResult<'data, UnimplementedPacket> {
Ok((data, UnimplementedPacket(0i32)))
},
|_packet: &UnimplementedPacket| -> Vec<u8> { vec![] }
);
use clientbound::*;
use serverbound::*;
generic_packet!(
UnimplementedPacket,
// Handshake
SH00Handshake(packet) => (serverbound::SH00Handshake::id(), packet.serialize_body()),
SH00Handshake,
// Status
SS00StatusRequest(packet) => (
serverbound::SS00StatusRequest::id(),
packet.serialize_body(),
),
SS01PingRequest(packet) => {
(serverbound::SS01PingRequest::id(), packet.serialize_body())
}
CS00StatusResponse(packet) => (
clientbound::CS00StatusResponse::id(),
packet.serialize_body(),
),
CS01PingResponse(packet) => {
(clientbound::CS01PingResponse::id(), packet.serialize_body())
}
SS00StatusRequest,
SS01PingRequest,
CS00StatusResponse,
CS01PingResponse,
// Login
SL00LoginStart(packet) => (serverbound::SL00LoginStart::id(), packet.serialize_body()),
SL01EncryptionResponse(packet) => (
serverbound::SL01EncryptionResponse::id(),
packet.serialize_body(),
),
SL02LoginPluginResponse(packet) => (
serverbound::SL02LoginPluginResponse::id(),
packet.serialize_body(),
),
CL00Disconnect(packet) => (clientbound::CL00Disconnect::id(), packet.serialize_body()),
CL01EncryptionRequest(packet) => (
clientbound::CL01EncryptionRequest::id(),
packet.serialize_body(),
),
CL02LoginSuccess(packet) => {
(clientbound::CL02LoginSuccess::id(), packet.serialize_body())
}
CL03SetCompression(packet) => (
clientbound::CL03SetCompression::id(),
packet.serialize_body(),
),
CL04LoginPluginRequest(packet) => (
clientbound::CL04LoginPluginRequest::id(),
packet.serialize_body(),
),
SL00LoginStart,
SL01EncryptionResponse,
SL02LoginPluginResponse,
CL00Disconnect,
CL01EncryptionRequest,
CL02LoginSuccess,
CL03SetCompression,
CL04LoginPluginRequest,
// Play
SP08CommandSuggestionsRequest(packet) => (
serverbound::SP08CommandSuggestionsRequest::id(),
packet.serialize_body(),
),
SP11KeepAlive(packet) => (serverbound::SP11KeepAlive::id(), packet.serialize_body()),
SP13SetPlayerPosition(packet) => (
serverbound::SP13SetPlayerPosition::id(),
packet.serialize_body(),
),
SP14SetPlayerPositionAndRotation(packet) => (
serverbound::SP14SetPlayerPositionAndRotation::id(),
packet.serialize_body(),
),
SP15SetPlayerRotation(packet) => (
serverbound::SP15SetPlayerRotation::id(),
packet.serialize_body(),
),
SP08CommandSuggestionsRequest,
SP11KeepAlive,
SP13SetPlayerPosition,
SP14SetPlayerPositionAndRotation,
SP15SetPlayerRotation,
CP00SpawnEntity,
CP0BChangeDifficulty,
CP17Disconnect,
CP1FKeepAlive,
CP21WorldEvent,
CP50SetEntityVelocity,
CP52SetExperience,
CP68EntityEffect
);
CP00SpawnEntity(packet) => {
(clientbound::CP00SpawnEntity::id(), packet.serialize_body())
}
CP0BChangeDifficulty(packet) => (
clientbound::CP0BChangeDifficulty::id(),
packet.serialize_body(),
),
CP17Disconnect(packet) => (clientbound::CP17Disconnect::id(), packet.serialize_body()),
CP1FKeepAlive(packet) => (clientbound::CP1FKeepAlive::id(), packet.serialize_body()),
CP21WorldEvent(packet) => (clientbound::CP21WorldEvent::id(), packet.serialize_body()),
CP50SetEntityVelocity(packet) => (
clientbound::CP50SetEntityVelocity::id(),
packet.serialize_body(),
),
CP52SetExperience(packet) => (
clientbound::CP52SetExperience::id(),
packet.serialize_body(),
),
CP68EntityEffect(packet) => {
(clientbound::CP68EntityEffect::id(), packet.serialize_body())
}
macro_rules! packet {
($packet_type: ident, $id: literal, $client_state: expr, $serverbound: literal, $parse_body: expr, $serialize_body: expr) => {
impl crate::packet::Packet for $packet_type {
const ID: crate::packet::PacketId = $id;
const CLIENT_STATE: crate::ClientState = $client_state;
const IS_SERVERBOUND: bool = $serverbound;
// Unimplemented packets get no body.
UnimplementedPacket(packet_id) => (*packet_id, vec![]),
fn parse_body<'data>(data: &'data [u8]) -> crate::util::ParseResult<'_, $packet_type> {
$parse_body(data)
}
fn serialize_body(&self) -> Vec<u8> {
$serialize_body(self)
}
}
impl From<$packet_type> for crate::packet::GenericPacket {
fn from(value: $packet_type) -> Self {
crate::packet::GenericPacket::$packet_type(value)
}
}
impl TryFrom<crate::packet::GenericPacket> for $packet_type {
type Error = ();
fn try_from(value: crate::packet::GenericPacket) -> Result<Self, Self::Error> {
match value {
crate::packet::GenericPacket::$packet_type(packet) => Ok(packet),
_ => Err(()),
}
}
}
};
}
pub(crate) use packet;

View File

@ -1,8 +1,5 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{parse_string, parse_varint, serialize_string, serialize_varint},
ClientState,
};
use crate::{util::*, ClientState, ProtocolError};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Clone, Debug, PartialEq)]
pub struct SH00Handshake {
@ -11,21 +8,18 @@ pub struct SH00Handshake {
pub server_port: u16,
pub next_state: ClientState,
}
impl Packet for SH00Handshake {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Handshake
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
SH00Handshake,
0x00,
ClientState::Handshake,
true,
|data: &'data [u8]| -> ParseResult<'data, SH00Handshake> {
let (data, protocol_version) = parse_varint(data)?;
let (data, server_address) = parse_string(data)?;
let (data, server_port) = nom::number::streaming::be_u16(data)?;
let (data, mut bytes) = take_bytes(2)(data)?;
let server_port = bytes
.read_u16::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, next_state) = parse_varint(data)?;
Ok((
@ -41,32 +35,17 @@ impl Packet for SH00Handshake {
},
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SH00Handshake| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.protocol_version.to_be_bytes());
output.extend_from_slice(&serialize_string(&self.server_address));
output.extend_from_slice(&self.server_port.to_be_bytes());
output.extend_from_slice(&serialize_varint(match self.next_state {
output.extend_from_slice(&packet.protocol_version.to_be_bytes());
output.extend_from_slice(&serialize_string(&packet.server_address));
output.extend_from_slice(&packet.server_port.to_be_bytes());
output.extend_from_slice(&serialize_varint(match packet.next_state {
ClientState::Status => 0x01,
ClientState::Login => 0x02,
_ => panic!("invalid SH00Handshake next_state"),
}));
output
}
}
impl From<SH00Handshake> for GenericPacket {
fn from(value: SH00Handshake) -> Self {
GenericPacket::SH00Handshake(value)
}
}
impl TryFrom<GenericPacket> for SH00Handshake {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SH00Handshake(packet) => Ok(packet),
_ => Err(()),
}
}
}
);

View File

@ -1,30 +1,18 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{
parse_string, parse_uuid, parse_varint, serialize_string, serialize_uuid, serialize_varint,
},
Uuid,
};
use crate::{util::*, Uuid};
#[derive(Clone, Debug, PartialEq)]
pub struct SL00LoginStart {
name: String,
uuid: Option<Uuid>,
pub name: String,
pub uuid: Option<Uuid>,
}
impl Packet for SL00LoginStart {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
SL00LoginStart,
0x00,
crate::ClientState::Login,
true,
|data: &'data [u8]| -> ParseResult<'data, SL00LoginStart> {
let (data, name) = parse_string(data)?;
let (data, has_uuid) = nom::bytes::streaming::take(1usize)(data)?;
let (data, has_uuid) = take_bytes(1usize)(data)?;
if has_uuid == [0x01] {
let (data, uuid) = parse_uuid(data)?;
Ok((
@ -37,11 +25,11 @@ impl Packet for SL00LoginStart {
} else {
Ok((data, SL00LoginStart { name, uuid: None }))
}
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SL00LoginStart| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_string(&self.name));
match self.uuid {
output.extend_from_slice(&serialize_string(&packet.name));
match packet.uuid {
Some(uuid) => {
output.push(0x01);
output.extend_from_slice(&serialize_uuid(&uuid));
@ -50,44 +38,23 @@ impl Packet for SL00LoginStart {
}
output
}
}
impl From<SL00LoginStart> for GenericPacket {
fn from(value: SL00LoginStart) -> Self {
GenericPacket::SL00LoginStart(value)
}
}
impl TryFrom<GenericPacket> for SL00LoginStart {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SL00LoginStart(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct SL01EncryptionResponse {
shared_secret: Vec<u8>,
verify_token: Vec<u8>,
pub shared_secret: Vec<u8>,
pub verify_token: Vec<u8>,
}
impl Packet for SL01EncryptionResponse {
fn id() -> PacketId {
0x01
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
SL01EncryptionResponse,
0x01,
crate::ClientState::Login,
true,
|data: &'data [u8]| -> ParseResult<'data, SL01EncryptionResponse> {
let (data, shared_secret_len) = parse_varint(data)?;
let (data, shared_secret) = nom::bytes::streaming::take(shared_secret_len as usize)(data)?;
let (data, shared_secret) = take_bytes(shared_secret_len as usize)(data)?;
let (data, verify_token_len) = parse_varint(data)?;
let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?;
let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?;
Ok((
data,
@ -96,52 +63,31 @@ impl Packet for SL01EncryptionResponse {
verify_token: verify_token.to_vec(),
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SL01EncryptionResponse| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.shared_secret.len() as i32));
output.extend_from_slice(&self.shared_secret);
output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32));
output.extend_from_slice(&self.verify_token);
output.extend_from_slice(&serialize_varint(packet.shared_secret.len() as i32));
output.extend_from_slice(&packet.shared_secret);
output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32));
output.extend_from_slice(&packet.verify_token);
output
}
}
impl From<SL01EncryptionResponse> for GenericPacket {
fn from(value: SL01EncryptionResponse) -> Self {
GenericPacket::SL01EncryptionResponse(value)
}
}
impl TryFrom<GenericPacket> for SL01EncryptionResponse {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SL01EncryptionResponse(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct SL02LoginPluginResponse {
message_id: i32,
successful: bool,
data: Vec<u8>,
pub message_id: i32,
pub successful: bool,
pub data: Vec<u8>,
}
impl Packet for SL02LoginPluginResponse {
fn id() -> PacketId {
0x02
}
fn client_state() -> crate::ClientState {
crate::ClientState::Login
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
SL02LoginPluginResponse,
0x02,
crate::ClientState::Login,
true,
|data: &'data [u8]| -> ParseResult<'data, SL02LoginPluginResponse> {
let (data, message_id) = parse_varint(data)?;
let (data, successful) = nom::bytes::streaming::take(1usize)(data)?;
let (data, successful) = take_bytes(1usize)(data)?;
let successful = successful == [0x01];
Ok((
data,
@ -154,31 +100,16 @@ impl Packet for SL02LoginPluginResponse {
},
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SL02LoginPluginResponse| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.message_id));
if self.successful {
output.extend_from_slice(&serialize_varint(packet.message_id));
if packet.successful {
output.push(0x01);
output.extend_from_slice(&self.data);
output.extend_from_slice(&packet.data);
} else {
output.push(0x00);
}
output
}
}
impl From<SL02LoginPluginResponse> for GenericPacket {
fn from(value: SL02LoginPluginResponse) -> Self {
GenericPacket::SL02LoginPluginResponse(value)
}
}
impl TryFrom<GenericPacket> for SL02LoginPluginResponse {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SL02LoginPluginResponse(packet) => Ok(packet),
_ => Err(()),
}
}
}
);

View File

@ -1,25 +1,17 @@
use crate::{
packet::{GenericPacket, Packet, PacketId},
util::{parse_string, parse_varint, serialize_string, serialize_varint},
};
use crate::{util::*, ProtocolError};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Clone, Debug, PartialEq)]
pub struct SP08CommandSuggestionsRequest {
transaction_id: i32,
text: String,
pub transaction_id: i32,
pub text: String,
}
impl Packet for SP08CommandSuggestionsRequest {
fn id() -> PacketId {
0x08
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
crate::packet::packet!(
SP08CommandSuggestionsRequest,
0x08,
crate::ClientState::Play,
true,
|data: &'data [u8]| -> ParseResult<'data, SP08CommandSuggestionsRequest> {
let (data, transaction_id) = parse_varint(data)?;
let (data, text) = parse_string(data)?;
Ok((
@ -29,147 +21,109 @@ impl Packet for SP08CommandSuggestionsRequest {
text,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SP08CommandSuggestionsRequest| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(self.transaction_id));
output.extend_from_slice(&serialize_string(&self.text));
output.extend_from_slice(&serialize_varint(packet.transaction_id));
output.extend_from_slice(&serialize_string(&packet.text));
output
}
}
impl From<SP08CommandSuggestionsRequest> for GenericPacket {
fn from(value: SP08CommandSuggestionsRequest) -> Self {
GenericPacket::SP08CommandSuggestionsRequest(value)
}
}
impl TryFrom<GenericPacket> for SP08CommandSuggestionsRequest {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SP08CommandSuggestionsRequest(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP11KeepAlive {
payload: i64,
pub payload: i64,
}
impl Packet for SP11KeepAlive {
fn id() -> PacketId {
0x11
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, payload) = nom::number::streaming::be_i64(data)?;
crate::packet::packet!(
SP11KeepAlive,
0x11,
crate::ClientState::Play,
true,
|data: &'data [u8]| -> ParseResult<'data, SP11KeepAlive> {
let (data, mut bytes) = take_bytes(8)(data)?;
let payload = bytes
.read_i64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((data, SP11KeepAlive { payload }))
}
fn serialize_body(&self) -> Vec<u8> {
self.payload.to_be_bytes().to_vec()
}
}
impl From<SP11KeepAlive> for GenericPacket {
fn from(value: SP11KeepAlive) -> Self {
GenericPacket::SP11KeepAlive(value)
}
}
impl TryFrom<GenericPacket> for SP11KeepAlive {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SP11KeepAlive(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &SP11KeepAlive| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP13SetPlayerPosition {
x: f64,
y: f64,
z: f64,
on_ground: bool,
pub x: f64,
pub y: f64,
pub z: f64,
pub on_ground: bool,
}
impl Packet for SP13SetPlayerPosition {
fn id() -> PacketId {
0x13
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, x) = nom::number::streaming::be_f64(data)?;
let (data, y) = nom::number::streaming::be_f64(data)?;
let (data, z) = nom::number::streaming::be_f64(data)?;
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
crate::packet::packet!(
SP13SetPlayerPosition,
0x13,
crate::ClientState::Play,
true,
|data: &'data [u8]| -> ParseResult<'data, SP13SetPlayerPosition> {
let (data, mut bytes) = take_bytes(8)(data)?;
let x = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let y = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let z = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, on_ground) = take_bytes(1usize)(data)?;
let on_ground = on_ground == [0x01];
Ok((data, SP13SetPlayerPosition { x, y, z, on_ground }))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SP13SetPlayerPosition| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.x.to_be_bytes());
output.extend_from_slice(&self.y.to_be_bytes());
output.extend_from_slice(&self.z.to_be_bytes());
output.push(if self.on_ground { 0x01 } else { 0x00 });
output.extend_from_slice(&packet.x.to_be_bytes());
output.extend_from_slice(&packet.y.to_be_bytes());
output.extend_from_slice(&packet.z.to_be_bytes());
output.push(if packet.on_ground { 0x01 } else { 0x00 });
output
}
}
impl From<SP13SetPlayerPosition> for GenericPacket {
fn from(value: SP13SetPlayerPosition) -> Self {
GenericPacket::SP13SetPlayerPosition(value)
}
}
impl TryFrom<GenericPacket> for SP13SetPlayerPosition {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SP13SetPlayerPosition(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP14SetPlayerPositionAndRotation {
x: f64,
y: f64,
z: f64,
yaw: f32,
pitch: f32,
on_ground: bool,
pub x: f64,
pub y: f64,
pub z: f64,
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
impl Packet for SP14SetPlayerPositionAndRotation {
fn id() -> PacketId {
0x14
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, x) = nom::number::streaming::be_f64(data)?;
let (data, y) = nom::number::streaming::be_f64(data)?;
let (data, z) = nom::number::streaming::be_f64(data)?;
let (data, yaw) = nom::number::streaming::be_f32(data)?;
let (data, pitch) = nom::number::streaming::be_f32(data)?;
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
crate::packet::packet!(
SP14SetPlayerPositionAndRotation,
0x14,
crate::ClientState::Play,
true,
|data: &'data [u8]| -> ParseResult<'data, SP14SetPlayerPositionAndRotation> {
let (data, mut bytes) = take_bytes(8)(data)?;
let x = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let y = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(8)(data)?;
let z = bytes
.read_f64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(4)(data)?;
let yaw = bytes
.read_f32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(4)(data)?;
let pitch = bytes
.read_f32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, on_ground) = take_bytes(1usize)(data)?;
let on_ground = on_ground == [0x01];
Ok((
data,
@ -182,55 +136,40 @@ impl Packet for SP14SetPlayerPositionAndRotation {
on_ground,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SP14SetPlayerPositionAndRotation| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.x.to_be_bytes());
output.extend_from_slice(&self.y.to_be_bytes());
output.extend_from_slice(&self.z.to_be_bytes());
output.extend_from_slice(&self.yaw.to_be_bytes());
output.extend_from_slice(&self.pitch.to_be_bytes());
output.push(if self.on_ground { 0x01 } else { 0x00 });
output.extend_from_slice(&packet.x.to_be_bytes());
output.extend_from_slice(&packet.y.to_be_bytes());
output.extend_from_slice(&packet.z.to_be_bytes());
output.extend_from_slice(&packet.yaw.to_be_bytes());
output.extend_from_slice(&packet.pitch.to_be_bytes());
output.push(if packet.on_ground { 0x01 } else { 0x00 });
output
}
}
impl From<SP14SetPlayerPositionAndRotation> for GenericPacket {
fn from(value: SP14SetPlayerPositionAndRotation) -> Self {
GenericPacket::SP14SetPlayerPositionAndRotation(value)
}
}
impl TryFrom<GenericPacket> for SP14SetPlayerPositionAndRotation {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SP14SetPlayerPositionAndRotation(packet) => Ok(packet),
_ => Err(()),
}
}
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP15SetPlayerRotation {
yaw: f32,
pitch: f32,
on_ground: bool,
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
impl Packet for SP15SetPlayerRotation {
fn id() -> PacketId {
0x15
}
fn client_state() -> crate::ClientState {
crate::ClientState::Play
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, yaw) = nom::number::streaming::be_f32(data)?;
let (data, pitch) = nom::number::streaming::be_f32(data)?;
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
crate::packet::packet!(
SP15SetPlayerRotation,
0x15,
crate::ClientState::Play,
true,
|data: &'data [u8]| -> ParseResult<'data, SP15SetPlayerRotation> {
let (data, mut bytes) = take_bytes(4)(data)?;
let yaw = bytes
.read_f32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, mut bytes) = take_bytes(4)(data)?;
let pitch = bytes
.read_f32::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
let (data, on_ground) = take_bytes(1usize)(data)?;
let on_ground = on_ground == [0x01];
Ok((
data,
@ -240,27 +179,12 @@ impl Packet for SP15SetPlayerRotation {
on_ground,
},
))
}
fn serialize_body(&self) -> Vec<u8> {
},
|packet: &SP15SetPlayerRotation| -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&self.yaw.to_be_bytes());
output.extend_from_slice(&self.pitch.to_be_bytes());
output.push(if self.on_ground { 0x01 } else { 0x00 });
output.extend_from_slice(&packet.yaw.to_be_bytes());
output.extend_from_slice(&packet.pitch.to_be_bytes());
output.push(if packet.on_ground { 0x01 } else { 0x00 });
output
}
}
impl From<SP15SetPlayerRotation> for GenericPacket {
fn from(value: SP15SetPlayerRotation) -> Self {
GenericPacket::SP15SetPlayerRotation(value)
}
}
impl TryFrom<GenericPacket> for SP15SetPlayerRotation {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SP15SetPlayerRotation(packet) => Ok(packet),
_ => Err(()),
}
}
}
);

View File

@ -1,76 +1,32 @@
use crate::packet::{GenericPacket, Packet, PacketId};
use crate::{util::*, ProtocolError};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SS00StatusRequest;
impl Packet for SS00StatusRequest {
fn id() -> PacketId {
0x00
}
fn client_state() -> crate::ClientState {
crate::ClientState::Status
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
Ok((data, SS00StatusRequest))
}
fn serialize_body(&self) -> Vec<u8> {
vec![]
}
}
impl From<SS00StatusRequest> for GenericPacket {
fn from(value: SS00StatusRequest) -> Self {
GenericPacket::SS00StatusRequest(value)
}
}
impl TryFrom<GenericPacket> for SS00StatusRequest {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SS00StatusRequest(packet) => Ok(packet),
_ => Err(()),
}
}
}
crate::packet::packet!(
SS00StatusRequest,
0x00,
crate::ClientState::Status,
true,
|data: &'data [u8]| -> ParseResult<'data, SS00StatusRequest> { Ok((data, SS00StatusRequest)) },
|_packet: &SS00StatusRequest| -> Vec<u8> { vec![] }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SS01PingRequest {
payload: i64,
pub payload: i64,
}
impl Packet for SS01PingRequest {
fn id() -> PacketId {
0x01
}
fn client_state() -> crate::ClientState {
crate::ClientState::Status
}
fn serverbound() -> bool {
true
}
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, payload) = nom::number::streaming::be_i64(data)?;
crate::packet::packet!(
SS01PingRequest,
0x01,
crate::ClientState::Status,
true,
|data: &'data [u8]| -> ParseResult<'data, SS01PingRequest> {
let (data, mut bytes) = take_bytes(8)(data)?;
let payload = bytes
.read_i64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((data, SS01PingRequest { payload }))
}
fn serialize_body(&self) -> Vec<u8> {
self.payload.to_be_bytes().to_vec()
}
}
impl From<SS01PingRequest> for GenericPacket {
fn from(value: SS01PingRequest) -> Self {
GenericPacket::SS01PingRequest(value)
}
}
impl TryFrom<GenericPacket> for SS01PingRequest {
type Error = ();
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
match value {
GenericPacket::SS01PingRequest(packet) => Ok(packet),
_ => Err(()),
}
}
}
},
|packet: &SS01PingRequest| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
);

View File

@ -1,13 +1,33 @@
use nom::error::FromExternalError;
use crate::ProtocolError;
use byteorder::{BigEndian, ReadBytesExt};
use tracing::trace;
pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> {
pub type ParseResult<'data, T> = crate::Result<(&'data [u8], T)>;
pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> {
move |data| {
if data.len() < num {
Err(ProtocolError::NotEnoughData)
} else {
Ok(data.split_at(num))
}
}
}
#[tracing::instrument]
pub fn parse_varint(mut data: &[u8]) -> ParseResult<'_, i32> {
trace!("{:?}", data);
let mut output = 0i32;
let mut bytes_read = 0i32;
loop {
let (d, next_byte) = nom::bytes::streaming::take(1usize)(data)?;
let (d, next_byte) = take_bytes(1usize)(data)?;
data = d;
if next_byte.is_empty() {
return Err(ProtocolError::NotEnoughData);
}
output |= ((next_byte[0] & 0x7f) as i32) << (bytes_read * 7);
bytes_read += 1;
if next_byte[0] & 0x80 != 0x80 {
@ -17,8 +37,9 @@ pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> {
break;
}
}
nom::IResult::Ok((data, output))
Ok((data, output))
}
#[tracing::instrument]
pub fn serialize_varint(value: i32) -> Vec<u8> {
let mut value = value as u32;
let mut output = vec![];
@ -36,12 +57,14 @@ pub fn serialize_varint(value: i32) -> Vec<u8> {
output
}
pub fn parse_string(data: &[u8]) -> nom::IResult<&[u8], String> {
#[tracing::instrument]
pub fn parse_string(data: &[u8]) -> ParseResult<'_, String> {
let (data, len) = parse_varint(data)?;
let (data, str_bytes) = nom::bytes::streaming::take(len as usize)(data)?;
let (data, str_bytes) = take_bytes(len as usize)(data)?;
let s = String::from_utf8_lossy(str_bytes).to_string();
nom::IResult::Ok((data, s))
Ok((data, s))
}
#[tracing::instrument]
pub fn serialize_string(value: &str) -> Vec<u8> {
let mut output = vec![];
output.extend_from_slice(&serialize_varint(value.len() as i32));
@ -49,43 +72,63 @@ pub fn serialize_string(value: &str) -> Vec<u8> {
output
}
pub fn parse_json(data: &[u8]) -> nom::IResult<&[u8], crate::Json> {
use nom::error::{Error, ErrorKind};
#[tracing::instrument]
pub fn parse_json(data: &[u8]) -> ParseResult<'_, crate::Json> {
trace!("parse_json: {:?}", data);
let (data, json) = parse_string(data)?;
let json = serde_json::from_str(&json)
.map_err(|e| nom::Err::Error(Error::from_external_error(data, ErrorKind::Verify, e)))?;
let json = serde_json::from_str(&json)?;
Ok((data, json))
}
#[tracing::instrument]
pub fn serialize_json(value: &crate::Json) -> Vec<u8> {
trace!("serialize_json: {:?}", value);
serialize_string(&serde_json::to_string(value).expect("valid json"))
}
pub fn parse_chat(data: &[u8]) -> nom::IResult<&[u8], crate::Chat> {
#[tracing::instrument]
pub fn parse_chat(data: &[u8]) -> ParseResult<'_, crate::Chat> {
trace!("parse_chat: {:?}", data);
parse_json(data)
}
#[tracing::instrument]
pub fn serialize_chat(value: &crate::Chat) -> Vec<u8> {
trace!("serialize_chat: {:?}", value);
serialize_json(value)
}
pub fn parse_uuid(data: &[u8]) -> nom::IResult<&[u8], crate::Uuid> {
nom::number::streaming::be_u128(data)
#[tracing::instrument]
pub fn parse_uuid(data: &[u8]) -> ParseResult<'_, crate::Uuid> {
trace!("parse_uuid: {:?}", data);
let (data, mut bytes) = take_bytes(16)(data)?;
let uuid = bytes
.read_u128::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
Ok((data, uuid))
}
#[tracing::instrument]
pub fn serialize_uuid(value: &crate::Uuid) -> Vec<u8> {
trace!("serialize_uuid: {:?}", value);
value.to_be_bytes().to_vec()
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Position {
x: i32,
y: i32,
z: i32,
pub x: i32,
pub y: i32,
pub z: i32,
}
impl Position {
#[tracing::instrument]
pub fn new(x: i32, y: i32, z: i32) -> Self {
Position { x, y, z }
}
pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> {
let (data, i) = nom::number::streaming::be_i64(data)?;
#[tracing::instrument]
pub fn parse(data: &[u8]) -> ParseResult<'_, Self> {
trace!("Position::parse: {:?}", data);
let (data, mut bytes) = take_bytes(8)(data)?;
let i = bytes
.read_i64::<BigEndian>()
.map_err(|_| ProtocolError::NotEnoughData)?;
// x: i26, z: i26, y: i12
let x = i >> 38;
@ -97,7 +140,9 @@ impl Position {
Ok((data, Position::new(x as i32, y as i32, z as i32)))
}
#[tracing::instrument]
pub fn serialize(&self) -> Vec<u8> {
trace!("Position::serialize: {:?}", self);
let i: i64 = ((self.x as i64 & 0x3FF_FFFF) << 38)
| ((self.z as i64 & 0x3FF_FFFF) << 12)
| (self.y as i64 & 0xFFF);

263
src/config.rs Normal file
View File

@ -0,0 +1,263 @@
use clap::Arg;
use once_cell::sync::{Lazy, OnceCell};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
use std::{fs::File, path::Path, path::PathBuf};
use tracing::{error, trace, warn};
/// The globally-accessible static instance of Config.
/// On program startup, Config::load() should be called to initialize it.
pub static CONFIG: OnceCell<Config> = OnceCell::new();
/// The globablly-accessible static instance of Args.
/// On program startup, Args::load() should be called to initialize it.
pub static ARGS: OnceCell<Args> = OnceCell::new();
static DEFAULT_ARGS: Lazy<Args> = Lazy::new(Args::default);
#[tracing::instrument]
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
trace!("{:?}", path);
let mut data = vec![];
let mut file = File::open(path)?;
file.read_to_end(&mut data)?;
Ok(data)
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Config {
pub port: u16,
pub max_players: usize,
pub motd: String,
pub server_icon: PathBuf,
#[serde(skip)]
pub server_icon_bytes: Vec<u8>,
pub protocol_version: i32,
pub game_version: String,
#[serde(skip)]
pub server_version: String,
pub server_threads: Option<usize>,
}
impl Default for Config {
fn default() -> Self {
let server_version = format!(
"composition {} ({} {})",
env!("CARGO_PKG_VERSION"),
&env!("GIT_HASH")[0..9],
&env!("GIT_DATE")[0..10]
);
Config {
port: 25565,
max_players: 20,
motd: "Hello world!".to_owned(),
server_icon: PathBuf::from("server-icon.png"),
server_icon_bytes: include_bytes!("./server-icon.png").to_vec(),
protocol_version: 761,
game_version: "1.19.3".to_owned(),
server_version,
server_threads: None,
}
}
}
impl Config {
pub fn instance() -> &'static Self {
match CONFIG.get() {
Some(a) => a,
None => Self::load(),
}
}
#[tracing::instrument]
pub fn load() -> &'static Self {
trace!("Config::load()");
let args = Args::instance();
let mut config = Config::default();
let config_path = Path::new(&args.config_file);
if !config_path.exists() {
warn!(
"Configuration file does not exist, creating {}",
config_path.to_str().unwrap_or("")
);
config.write(config_path);
}
if let Ok(cfg) = read_file(config_path) {
let cfg: Result<Config, _> = toml::from_slice(&cfg);
if let Ok(cfg) = cfg {
config = cfg;
} else {
error!("Could not parse configuration file, using default");
}
} else {
error!("Could not read configuration file, using default");
}
// Load the server icon
config.server_icon = args.server_icon.clone();
let server_icon_path = Path::new(&config.server_icon);
if server_icon_path.exists() {
if let Ok(server_icon_bytes) = read_file(server_icon_path) {
config.server_icon_bytes = server_icon_bytes;
} else {
warn!("Could not read server icon file, using default");
}
} else {
warn!(
"Server icon file does not exist, creating {}",
server_icon_path.to_str().unwrap_or("")
);
config.write_server_icon(server_icon_path);
}
CONFIG.set(config).expect("could not set CONFIG");
Self::instance()
}
#[tracing::instrument]
fn write(&self, path: &Path) {
trace!("Config.write()");
if let Ok(mut file) = File::options().write(true).create(true).open(path) {
if file
.write_all(toml::to_string(&self).unwrap().as_bytes())
.is_ok()
{
return;
}
}
error!("Could not write configuration file");
std::process::exit(1);
}
#[tracing::instrument]
fn write_server_icon(&self, path: &Path) {
trace!("Config.write_server_icon()");
if let Ok(mut file) = File::options().write(true).create(true).open(path) {
if file.write_all(&self.server_icon_bytes).is_ok() {
return;
}
}
error!("Could not write server icon file");
std::process::exit(1);
}
}
#[derive(Debug)]
pub struct Args {
pub config_file: PathBuf,
pub server_icon: PathBuf,
pub log_level: Option<tracing::Level>,
pub log_dir: PathBuf,
}
impl Default for Args {
fn default() -> Self {
let config = Config::default();
Args {
config_file: PathBuf::from("composition.toml"),
server_icon: config.server_icon,
log_level: None,
log_dir: PathBuf::from("logs"),
}
}
}
impl Args {
pub fn instance() -> &'static Self {
match ARGS.get() {
Some(a) => a,
None => Self::load(),
}
}
pub fn load() -> &'static Self {
ARGS.set(Self::parse()).expect("could not set ARGS");
Self::instance()
}
fn parse() -> Self {
use std::ffi::OsStr;
let m = clap::Command::new("composition")
.about(env!("CARGO_PKG_DESCRIPTION"))
.disable_version_flag(true)
.arg(
Arg::new("version")
.short('V')
.long("version")
.help("Print version")
.global(true)
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Set log level to debug")
.global(true)
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("config-file")
.short('c')
.long("config-file")
.help("Configuration file path")
.value_hint(clap::ValueHint::FilePath)
.default_value(OsStr::new(&DEFAULT_ARGS.config_file)),
)
.arg(
Arg::new("server-icon")
.long("server-icon")
.help("Server icon file path")
.value_hint(clap::ValueHint::FilePath)
.default_value(OsStr::new(&DEFAULT_ARGS.server_icon)),
)
.arg(
Arg::new("log-level")
.short('l')
.long("log-level")
.help("Set the log level")
.conflicts_with("verbose")
.value_name("level")
.value_parser(["trace", "debug", "info", "warn", "error"]),
)
.arg(
Arg::new("log-dir")
.long("log-dir")
.help("Set the log output directory")
.value_name("dir")
.value_hint(clap::ValueHint::DirPath)
.default_value(OsStr::new(&DEFAULT_ARGS.log_dir)),
)
.get_matches();
let mut args = Self::default();
args.config_file = m
.get_one::<String>("config-file")
.map_or(args.config_file, PathBuf::from);
args.server_icon = m
.get_one::<String>("server-icon")
.map_or(args.server_icon, PathBuf::from);
args.log_dir = m
.get_one::<String>("log-dir")
.map_or(args.log_dir, PathBuf::from);
if m.get_flag("verbose") {
args.log_level = Some(tracing::Level::DEBUG);
} else {
args.log_level = m.get_one("log-level").map_or(args.log_level, |s: &String| {
Some(s.parse::<tracing::Level>().unwrap())
});
}
if m.get_flag("version") {
println!("{}", Config::default().server_version);
if m.get_flag("verbose") {
println!("release: {}", env!("CARGO_PKG_VERSION"));
println!("commit-hash: {}", env!("GIT_HASH"));
println!("commit-date: {}", &env!("GIT_DATE")[0..10]);
println!("license: {}", env!("CARGO_PKG_LICENSE"));
println!("authors: {}", env!("CARGO_PKG_AUTHORS"));
println!("build-target: {}", env!("BUILD_TARGET"));
}
std::process::exit(0);
}
args
}
}

39
src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
pub mod config;
pub mod net;
pub mod server;
use crate::config::Config;
use once_cell::sync::OnceCell;
use std::time::Instant;
pub static START_TIME: OnceCell<Instant> = OnceCell::new();
/// Start the server.
#[tracing::instrument]
pub async fn start_server(
start_time: Instant,
) -> (server::Server, tokio_util::sync::CancellationToken) {
START_TIME
.set(start_time)
.expect("could not set START_TIME");
server::Server::new(format!("0.0.0.0:{}", Config::instance().port)).await
}
pub mod prelude {
pub use crate::config::Config;
pub use crate::START_TIME;
pub use composition_protocol::{Chat, Json, Uuid};
pub use serde::{Deserialize, Serialize};
pub use serde_json::json;
pub use std::collections::VecDeque;
pub use std::io::{Read, Write};
pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub use tracing::{debug, error, info, trace, warn};
#[derive(Clone, Debug, PartialEq)]
pub enum ParseError {
NotEnoughData,
InvalidData,
VarIntTooBig,
}
pub type ParseResult<T> = Result<(T, usize), ParseError>;
}

View File

@ -1,32 +1,76 @@
use log::info;
use std::sync::mpsc::TryRecvError;
use std::time::Duration;
use std::time::Instant;
use tracing::{info, instrument, warn};
use tracing_subscriber::prelude::*;
#[tokio::main]
pub async fn main() {
let ctrlc_rx = composition_core::init();
info!(
"Starting {} on port {}",
composition_core::CONFIG.server_version,
composition_core::CONFIG.port
);
let mut server = composition_core::start_server().await;
info!(
"Done! Start took {:?}",
composition_core::START_TIME.elapsed()
);
#[instrument]
pub fn main() {
let start_time = Instant::now();
// Set up logging.
let file_writer =
tracing_appender::rolling::daily(&composition::config::Args::instance().log_dir, "log");
let (file_writer, _guard) = tracing_appender::non_blocking(file_writer);
tracing_subscriber::registry()
.with(tracing_subscriber::filter::LevelFilter::from_level(
composition::config::Args::instance()
.log_level
.unwrap_or(if cfg!(debug_assertions) {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
}),
))
.with(
tracing_subscriber::fmt::layer()
.compact()
.with_ansi(false)
.with_writer(file_writer),
)
.with(
tracing_subscriber::fmt::layer()
.compact()
.with_writer(std::io::stdout),
)
.init();
// Load the config.
let config = composition::config::Config::load();
match config.server_threads {
Some(1) => {
warn!("Running on only one thread");
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
}
Some(n) => {
info!("Running on {} threads", n);
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(n)
.build()
}
None => tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build(),
}
.unwrap()
.block_on(async move {
info!("Starting {} on port {}", config.server_version, config.port);
let (mut server, running) = composition::start_server(start_time).await;
info!("Done! Start took {:?}", start_time.elapsed());
// The main server loop.
loop {
match ctrlc_rx.try_recv() {
Ok(_) => {
let _ = server.shutdown().await;
break; // Exit the loop.
tokio::select! {
_ = running.cancelled() => {
break;
}
Err(TryRecvError::Empty) => {} // Doesn't matter if there's nothing for us.
Err(TryRecvError::Disconnected) => panic!("Ctrl-C sender disconnected"),
}
server.update().await.unwrap();
std::thread::sleep(Duration::from_millis(2));
_ = server.update() => {}
}
}
let _ = tokio::time::timeout(std::time::Duration::from_secs(10), server.shutdown()).await;
});
}

212
src/net.rs Normal file
View File

@ -0,0 +1,212 @@
use crate::prelude::*;
use composition_protocol::{packet::GenericPacket, ClientState, ProtocolError};
use std::sync::Arc;
use std::time::Instant;
use tokio::net::TcpStream;
use tokio::sync::RwLock;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum NetworkClientState {
Handshake,
Status {
received_request: bool,
received_ping: bool,
},
Login,
Play,
Disconnected,
}
impl From<NetworkClientState> for ClientState {
fn from(value: NetworkClientState) -> Self {
match value {
NetworkClientState::Handshake => ClientState::Handshake,
NetworkClientState::Status {
received_request: _,
received_ping: _,
} => ClientState::Status,
NetworkClientState::Login => ClientState::Login,
NetworkClientState::Play => ClientState::Play,
NetworkClientState::Disconnected => ClientState::Disconnected,
}
}
}
impl AsRef<ClientState> for NetworkClientState {
fn as_ref(&self) -> &ClientState {
match self {
NetworkClientState::Handshake => &ClientState::Handshake,
NetworkClientState::Status {
received_request: _,
received_ping: _,
} => &ClientState::Status,
NetworkClientState::Login => &ClientState::Login,
NetworkClientState::Play => &ClientState::Play,
NetworkClientState::Disconnected => &ClientState::Disconnected,
}
}
}
#[derive(Debug, Clone)]
pub struct NetworkClient {
pub id: u128,
pub state: NetworkClientState,
stream: Arc<RwLock<TcpStream>>,
incoming_data: VecDeque<u8>,
pub incoming_packet_queue: VecDeque<GenericPacket>,
pub last_received_data_time: Instant,
pub outgoing_packet_queue: VecDeque<GenericPacket>,
}
impl NetworkClient {
#[tracing::instrument]
pub fn new(id: u128, stream: TcpStream) -> NetworkClient {
NetworkClient {
id,
state: NetworkClientState::Handshake,
stream: Arc::new(RwLock::new(stream)),
incoming_data: VecDeque::new(),
incoming_packet_queue: VecDeque::new(),
last_received_data_time: Instant::now(),
outgoing_packet_queue: VecDeque::new(),
}
}
#[tracing::instrument]
async fn read_data(&mut self) -> tokio::io::Result<()> {
trace!("NetworkClient.read_data() id {}", self.id);
let stream = self.stream.read().await;
// Try to read 4kb at a time until there is no more data.
loop {
let mut buf = [0; 4096];
let num_bytes = match stream.try_read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
break;
}
Err(e) => return Err(e),
};
debug!("Read {} bytes from client {}", num_bytes, self.id);
self.last_received_data_time = Instant::now();
self.incoming_data.extend(&buf[..num_bytes]);
}
trace!("NetworkClient.read_data() end id {}", self.id);
Ok(())
}
// TODO: Stream compression/encryption.
#[tracing::instrument]
pub async fn read_packets(&mut self) -> composition_protocol::Result<()> {
trace!("NetworkClient.read_packet() id {}", self.id);
if self.read_data().await.is_err() {
self.disconnect(None).await;
return Err(ProtocolError::Disconnected);
}
self.incoming_data.make_contiguous();
let (mut data, &[..]) = self.incoming_data.as_slices();
let mut bytes_consumed = 0;
while !data.is_empty() {
match GenericPacket::parse_uncompressed(self.state.into(), true, data) {
Ok((d, packet)) => {
debug!("Got packet {:?} from client {}", packet, self.id);
bytes_consumed += data.len() - d.len();
data = d;
self.incoming_packet_queue.push_back(packet);
}
Err(ProtocolError::NotEnoughData) => break,
Err(e) => {
// Remove the valid bytes before this packet.
self.incoming_data = self.incoming_data.split_off(bytes_consumed);
return Err(e);
}
}
}
// Remove the bytes we just read.
self.incoming_data = self.incoming_data.split_off(bytes_consumed);
Ok(())
}
// None: There was no packet to read.
// Some(Err(())): The packet was the wrong type.
// Some(Ok(_)): The packet was successfully read.
#[tracing::instrument]
pub fn read_packet<P: std::fmt::Debug + TryFrom<GenericPacket>>(
&mut self,
) -> Option<Result<P, GenericPacket>> {
if let Some(generic_packet) = self.incoming_packet_queue.pop_back() {
if let Ok(packet) = TryInto::<P>::try_into(generic_packet.clone()) {
Some(Ok(packet))
} else {
self.incoming_packet_queue.push_back(generic_packet.clone());
Some(Err(generic_packet))
}
} else {
None
}
}
#[tracing::instrument]
pub fn queue_packet<P: std::fmt::Debug + Into<GenericPacket>>(&mut self, packet: P) {
self.outgoing_packet_queue.push_back(packet.into());
}
#[tracing::instrument]
pub async fn send_queued_packets(&mut self) -> composition_protocol::Result<()> {
let packets: Vec<_> = self.outgoing_packet_queue.drain(..).collect();
for packet in packets {
self.send_packet(packet)
.await
.map_err(|_| ProtocolError::Disconnected)?;
}
Ok(())
}
#[tracing::instrument]
pub async fn send_packet<P: std::fmt::Debug + Into<GenericPacket>>(
&self,
packet: P,
) -> tokio::io::Result<()> {
use composition_protocol::util::serialize_varint;
let packet: GenericPacket = packet.into();
debug!("Sending packet {:?} to client {}", packet, self.id);
let (packet_id, mut packet_body) = packet.serialize();
let mut packet_id = serialize_varint(packet_id);
// TODO: Stream compression/encryption.
let mut b = vec![];
b.append(&mut packet_id);
b.append(&mut packet_body);
// bytes: packet length as varint, packet id as varint, packet body
let mut bytes = serialize_varint(b.len() as i32);
bytes.append(&mut b);
self.stream.write().await.write_all(&bytes).await?;
Ok(())
}
#[tracing::instrument]
pub async fn disconnect(&mut self, reason: Option<composition_protocol::Chat>) {
use composition_protocol::packet::clientbound::{CL00Disconnect, CP17Disconnect};
let reason = reason.unwrap_or(json!({
"text": "You have been disconnected!"
}));
match self.state.as_ref() {
ClientState::Disconnected | ClientState::Handshake | ClientState::Status => {
// Impossible to send a disconnect in these states.
}
ClientState::Login => {
let _ = self.send_packet(CL00Disconnect { reason }).await;
}
ClientState::Play => {
let _ = self.send_packet(CP17Disconnect { reason }).await;
}
}
self.state = NetworkClientState::Disconnected;
}
}

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

270
src/server/mod.rs Normal file
View File

@ -0,0 +1,270 @@
use crate::net::{NetworkClient, NetworkClientState};
use crate::prelude::*;
use composition_protocol::ClientState;
use std::sync::Arc;
use tokio::net::{TcpListener, ToSocketAddrs};
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
#[derive(Clone, Debug, PartialEq)]
pub enum ServerError {
NotRunning,
}
pub type Result<T> = std::result::Result<T, ServerError>;
#[derive(Debug)]
pub struct Server {
clients: Arc<RwLock<Vec<NetworkClient>>>,
net_tasks_handle: JoinHandle<()>,
}
impl Server {
#[tracing::instrument]
pub async fn new<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
bind_address: A,
) -> (Server, CancellationToken) {
trace!("Server::new()");
let running = CancellationToken::new();
let clients = Arc::new(RwLock::new(vec![]));
let net_tasks_handle = tokio::spawn(Self::create_network_tasks(
bind_address,
clients.clone(),
running.clone(),
));
let server = Server {
clients,
net_tasks_handle,
};
// let (shutdown_tx, shutdown_rx) = oneshot::channel();
let r = running.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.unwrap();
info!("Ctrl-C received, shutting down");
r.cancel();
// shutdown_tx.send(()).unwrap();
});
(server, running)
}
#[tracing::instrument]
async fn create_network_tasks<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
bind_address: A,
network_clients: Arc<RwLock<Vec<NetworkClient>>>,
running: CancellationToken,
) {
// Start a task to receive new clients.
trace!("Creating listener task");
let nc = network_clients.clone();
let r = running.clone();
let listener_task = tokio::spawn(async move {
trace!("Listener task created");
let Ok(listener) = TcpListener::bind(bind_address).await else {
error!("Could not bind to given address, shutting down.");
std::process::exit(1);
};
let mut client_id = 0u128;
loop {
tokio::select! {
_ = r.cancelled() => {
trace!("Listener task received shutdown");
break;
}
result = listener.accept() => {
if let Ok((stream, _)) = result {
trace!("Listener task got client (id {})", client_id);
nc.write().await.push(NetworkClient::new(client_id, stream));
client_id += 1;
} else {
trace!("Listener task failed to accept client");
}
}
}
}
});
// Start a task to update existing clients' packet queues.
trace!("Creating network task");
let nc = network_clients.clone();
let r = running.clone();
let packet_task = tokio::spawn(async move {
trace!("Network task created");
loop {
// Start tasks to read/write to clients concurrently.
tokio::select! {
_ = r.cancelled() => {
trace!("Network task received shutdown");
break;
}
mut nc = nc.write() => {
trace!("Network task updating clients");
let tasks: Vec<JoinHandle<NetworkClient>> = nc
.drain(..)
.map(|mut client: NetworkClient| {
tokio::spawn(async move {
let _ = client.read_packets().await;
if client.send_queued_packets().await.is_err() {
client
.disconnect(Some(json!({ "text": "Error writing packets." })))
.await;
}
client
})
})
.collect();
*nc = Vec::with_capacity(tasks.len());
for task in tasks {
nc.push(task.await.unwrap());
}
trace!("Network task updated clients");
}
}
}
});
// Start a task to remove disconnected clients.
trace!("Creating disconnection task");
let nc = network_clients.clone();
let r = running.clone();
let disconnection_task = tokio::spawn(async move {
trace!("Disconnection task created");
loop {
tokio::select! {
_ = r.cancelled() => {
trace!("Disconnection task received shutdown");
break;
}
mut nc = nc.write() => {
let before = nc.len();
nc.retain(|client| client.state != NetworkClientState::Disconnected);
let after = nc.len();
trace!("Disconnection task removed {} clients", before - after);
}
}
}
});
// Join the tasks on shutdown.
listener_task.await.expect("Listener task crashed");
packet_task.await.expect("Packet task crashed");
disconnection_task
.await
.expect("Disconnection task crashed");
}
#[tracing::instrument]
pub async fn update(&mut self) -> Result<()> {
trace!("Server.update()");
let mut clients = self.clients.write().await;
// Handle packets from the clients.
let online_players = clients
.iter()
.filter(|client| matches!(client.state, NetworkClientState::Play))
.count();
'clients: for client in clients.iter_mut() {
use composition_protocol::packet::{clientbound::*, serverbound::*};
'packets: while !client.incoming_packet_queue.is_empty() {
// client.read_packet()
// None: The client doesn't have any more packets.
// Some(Err(_)): The client read an unexpected packet. TODO: Handle this error.
// Some(Ok(_)): The client read the expected packet.
match client.state {
NetworkClientState::Handshake => {
let handshake = match client.read_packet::<SH00Handshake>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(handshake)) => handshake,
};
if handshake.next_state == ClientState::Status {
client.state = NetworkClientState::Status {
received_request: false,
received_ping: false,
};
} else if handshake.next_state == ClientState::Login {
client.state = NetworkClientState::Login;
} else {
client
.disconnect(Some(
json!({ "text": "Received invalid SH00Handshake packet" }),
))
.await;
}
}
// Status !received_request: Read SS00StatusRequest and respond with CS00StatusResponse
NetworkClientState::Status {
received_request,
received_ping,
} if !received_request => {
let _status_request = match client.read_packet::<SS00StatusRequest>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(p)) => p,
};
client.state = NetworkClientState::Status {
received_request: true,
received_ping,
};
let config = Config::instance();
client.queue_packet(CS00StatusResponse {
response: json!({
"version": {
"name": config.game_version,
"protocol": config.protocol_version
},
"players": {
"max": config.max_players,
"online": online_players,
"sample": []
},
"description": {
"text": config.motd
}
}),
});
}
// Status !received_ping: Read SS00StatusRequest and respond with CS00StatusResponse
NetworkClientState::Status { received_ping, .. } if !received_ping => {
let ping = match client.read_packet::<SS01PingRequest>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(p)) => p,
};
client.queue_packet(CS01PingResponse {
payload: ping.payload,
});
client.state = NetworkClientState::Disconnected;
}
NetworkClientState::Status { .. } => unreachable!(),
NetworkClientState::Login => unimplemented!(),
NetworkClientState::Play => unimplemented!(),
NetworkClientState::Disconnected => unimplemented!(),
}
// If continue was not
break 'packets;
}
}
Ok(())
}
#[tracing::instrument]
pub async fn shutdown(self) {
trace!("Server.shutdown()");
// Close the concurrent tasks.
let _ = self.net_tasks_handle.await;
// Send disconnect messages to the clients.
for client in self.clients.write().await.iter_mut() {
client
.disconnect(Some(json!({ "text": "The server is shutting down." })))
.await;
}
}
}