Replace packets with a macro

This commit is contained in:
Garen Tyler 2024-12-05 16:04:40 -07:00
parent 6a58f58cc0
commit 28f1656c81
Signed by: garentyler
SSH Key Fingerprint: SHA256:G4ke7blZMdpWPbkescyZ7IQYE4JAtwpI85YoJdq+S7U
16 changed files with 291 additions and 1040 deletions

View File

@ -8,12 +8,8 @@ pub mod error;
pub mod inventory;
/// Network packets.
///
/// The packet naming convention used is "DSIDName" where
/// 'D' is either 'S' for serverbound or 'C' for clientbound,
/// 'S' is the current connection state (**H**andshake, **S**tatus, **L**ogin, or **P**lay),
/// "ID" is the packet id in uppercase hexadecimal (ex. 1B, 05, 3A),
/// and "Name" is the packet's name as found on [wiki.vg](https://wiki.vg/Protocol) in PascalCase.
/// Examples include "SH00Handshake", "CP00SpawnEntity", and "SP11KeepAlive".
/// Packet names are as found on [wiki.vg](https://wiki.vg/Protocol)
/// in PascalCase, with some exceptions for uniqueness.
pub mod packets;
/// Useful shared parsing functions.
pub mod parsing;
@ -27,13 +23,14 @@ use types::VarInt;
///
/// Parsing packets requires knowing which state the connection is in.
/// [Relevant wiki.vg page](https://wiki.vg/How_to_Write_a_Server#FSM_example_of_handling_new_TCP_connections)
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub enum ClientState {
/// The connection is freshly established.
///
/// The only packet in this state is `SH00Handshake`.
/// After this packet is sent, the connection immediately
/// transitions to `Status` or `Login`.
#[default]
Handshake,
/// The client is performing [server list ping](https://wiki.vg/Server_List_Ping).
Status,
@ -59,8 +56,8 @@ impl parsing::Parsable for ClientState {
}
fn serialize(&self) -> Vec<u8> {
let byte = match &self {
&ClientState::Status => 1,
&ClientState::Login => 2,
ClientState::Status => 1,
ClientState::Login => 2,
_ => 0,
};
vec![byte]

232
src/protocol/packets.rs Normal file
View File

@ -0,0 +1,232 @@
#![allow(dead_code)]
// Inspired by https://github.com/iceiix/stevenarella.
/// Enum representation of a packet's direction.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PacketDirection {
Serverbound,
Clientbound,
}
#[macro_export]
macro_rules! packets {
($($state:ident $state_name:ident {
$($dir:ident $dir_name:ident {
$(
$(#[$attr:meta])*
packet $name:ident $id:literal {
$($(#[$fattr:meta])* field $field:ident: $field_type:ty,)*
$($(#[$rattr:meta])* rest $rest:ident,)?
}
)*
})+
})+) => {
use $crate::protocol::{ClientState, parsing::{VarInt, Parsable, IResult}};
#[derive(Debug, Clone, PartialEq)]
pub enum Packet {
$($($(
$name($state::$dir::$name),
)*)+)+
}
$($($(
impl From<$state::$dir::$name> for Packet {
fn from(value: $state::$dir::$name) -> Packet {
Packet::$name(value)
}
}
)*)+)+
impl Packet {
fn parser(client_state: ClientState, direction: PacketDirection) -> impl Fn(&[u8]) -> IResult<&[u8], Self> {
move |input: &[u8]| {
use nom::{combinator::verify, bytes::streaming::take};
if client_state == ClientState::Disconnected {
return nom::combinator::fail(input);
}
let (input, packet_len) = VarInt::parse_usize(input)?;
let (packet_body, packet_id) = verify(VarInt::parse, |v| {
match client_state {
$(ClientState::$state_name => {
match direction {
$(PacketDirection::$dir_name => {
match **v {
$($id => true,)*
_ => false,
}
})*
}
})*
ClientState::Disconnected => false,
}
})(input)?;
let (input, packet_body) = take(packet_len)(packet_body)?;
let (_, packet) = Packet::body_parser(client_state, direction, packet_id)(packet_body)?;
Ok((input, packet))
}
}
fn body_parser(client_state: ClientState, direction: PacketDirection, packet_id: VarInt) -> impl Fn(&[u8]) -> IResult<&[u8], Self> {
move |input: &[u8]| {
match client_state {
$(ClientState::$state_name => {
match direction {
$(PacketDirection::$dir_name => {
match *packet_id {
$($id => {
let (rest, inner) = $state::$dir::$name::parse(input)?;
// The packet should have consumed all of the input specified by packet_len.
nom::combinator::eof(rest)?;
Ok((rest, Packet::$name(inner)))
},)*
// Invalid packet id.
_ => Ok(nom::combinator::fail(input)?),
}
})*
}
})*
// Invalid client state.
_ => Ok(nom::combinator::fail(input)?),
}
}
}
pub fn parse(client_state: ClientState, direction: PacketDirection, input: &[u8]) -> IResult<&[u8], Self> {
Packet::parser(client_state, direction)(input)
}
pub fn parse_as<T: TryFrom<Packet, Error = Packet>>(client_state: ClientState, direction: PacketDirection, input: &[u8]) -> IResult<&[u8], Result<T, Self>> {
nom::combinator::map(Self::parser(client_state, direction), T::try_from)(input)
}
pub fn serialize(&self) -> (VarInt, Vec<u8>) {
match &self {
$($($(
Packet::$name(inner) => (VarInt::from($id), inner.serialize()),
)*)*)*
}
}
}
$(pub mod $state {
$(pub mod $dir {
#![allow(unused_imports)]
use $crate::protocol::{ClientState, parsing::{VarInt, Parsable, IResult}, types::*};
use super::super::Packet;
$(
$(#[$attr])*
#[derive(Default, Debug, Clone, PartialEq)]
pub struct $name {
$($(#[$fattr])* pub $field: $field_type,)*
$($(#[$rattr])* pub $rest: Vec<u8>,)?
}
impl TryFrom<Packet> for $name {
type Error = Packet;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
match value {
Packet::$name(inner) => Ok(inner),
_ => Err(value),
}
}
}
impl Parsable for $name {
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
$(let (input, $field) = <$field_type>::parse(input)?;)*
$(let (input, $rest) = nom::combinator::rest(input)?;)?
Ok((input, $name {
$($field: $field,)*
$($rest: $rest.to_vec(),)?
}))
}
#[allow(unused_mut)]
fn serialize(&self) -> Vec<u8> {
let mut output = vec![];
$(output.extend(self.$field.serialize());)*
$(output.extend(&self.$rest);)?
output
}
}
)*
})+
})+
};
}
packets!(
handshake Handshake {
serverbound Serverbound {
packet Handshake 0x00 {
field protocol_version: VarInt,
field host: String,
field port: u16,
field next_state: ClientState,
}
}
clientbound Clientbound {}
}
status Status {
serverbound Serverbound {
packet StatusRequest 0x00 {}
packet PingRequest 0x01 {
field payload: i64,
}
}
clientbound Clientbound {
packet StatusResponse 0x00 {
field response: Json,
}
packet PingResponse 0x01 {
field payload: i64,
}
}
}
login Login {
serverbound Serverbound {
packet LoginStart 0x00 {
field name: String,
field uuid: Option<Uuid>,
}
packet EncryptionResponse 0x01 {
field shared_secret: Vec<u8>,
field verify_token: Vec<u8>,
}
packet LoginPluginResponse 0x02 {
field message_id: VarInt,
field successful: bool,
rest data,
}
}
clientbound Clientbound {
packet LoginDisconnect 0x00 {
field reason: Chat,
}
packet EncryptionRequest 0x01 {
field server_id: String,
field public_key: Vec<u8>,
field verify_token: Vec<u8>,
}
packet LoginSuccess 0x02 {
field uuid: Uuid,
field username: String,
// TODO: Re-implement CL02LoginSuccessProperty
rest properties,
}
packet SetCompression 0x03 {
field threshold: VarInt,
}
packet LoginPluginRequest 0x04 {
field message_id: VarInt,
field channel: String,
rest data,
}
}
}
play Play {
serverbound Serverbound {}
clientbound Clientbound {
packet PlayDisconnect 0x17 {
field reason: Chat,
}
}
}
);

View File

@ -1,164 +0,0 @@
use crate::protocol::parsing::Parsable;
use crate::protocol::types::{Chat, Json, Uuid, VarInt};
#[derive(Clone, Debug, PartialEq)]
pub struct CL00Disconnect {
pub reason: Chat,
}
crate::protocol::packets::packet!(
CL00Disconnect,
0x00,
crate::protocol::ClientState::Login,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CL00Disconnect> {
let (data, reason) = Json::parse(data)?;
Ok((data, CL00Disconnect { reason }))
},
|packet: &CL00Disconnect| -> Vec<u8> { packet.reason.serialize() }
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL01EncryptionRequest {
pub server_id: String,
pub public_key: Vec<u8>,
pub verify_token: Vec<u8>,
}
crate::protocol::packets::packet!(
CL01EncryptionRequest,
0x01,
crate::protocol::ClientState::Login,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CL01EncryptionRequest> {
let (data, server_id) = String::parse(data)?;
let (data, public_key) = u8::parse_vec(data)?;
let (data, verify_token) = u8::parse_vec(data)?;
Ok((
data,
CL01EncryptionRequest {
server_id,
public_key,
verify_token,
},
))
},
|packet: &CL01EncryptionRequest| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.server_id.serialize());
output.extend(packet.public_key.serialize());
output.extend(packet.verify_token.serialize());
output
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL02LoginSuccess {
pub uuid: Uuid,
pub username: String,
pub properties: Vec<CL02LoginSuccessProperty>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CL02LoginSuccessProperty {
pub name: String,
pub value: String,
pub signature: Option<String>,
}
impl Parsable for CL02LoginSuccessProperty {
#[tracing::instrument]
fn parse(data: &[u8]) -> crate::protocol::parsing::IResult<&[u8], Self> {
let (data, name) = String::parse(data)?;
let (data, value) = String::parse(data)?;
let (data, signature) = String::parse_optional(data)?;
Ok((
data,
CL02LoginSuccessProperty {
name,
value,
signature,
},
))
}
#[tracing::instrument]
fn serialize(&self) -> Vec<u8> {
let mut output = vec![];
output.extend(self.name.serialize());
output.extend(self.value.serialize());
output.extend(self.signature.serialize());
output
}
}
crate::protocol::packets::packet!(
CL02LoginSuccess,
0x02,
crate::protocol::ClientState::Login,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CL02LoginSuccess> {
let (data, uuid) = Uuid::parse(data)?;
let (data, username) = String::parse(data)?;
let (data, properties) = CL02LoginSuccessProperty::parse_vec(data)?;
Ok((
data,
CL02LoginSuccess {
uuid,
username,
properties,
},
))
},
|packet: &CL02LoginSuccess| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.uuid.serialize());
output.extend(packet.username.serialize());
output.extend(packet.properties.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CL03SetCompression {
pub threshold: VarInt,
}
crate::protocol::packets::packet!(
CL03SetCompression,
0x03,
crate::protocol::ClientState::Login,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CL03SetCompression> {
let (data, threshold) = VarInt::parse(data)?;
Ok((data, CL03SetCompression { threshold }))
},
|packet: &CL03SetCompression| -> Vec<u8> { packet.threshold.serialize() }
);
#[derive(Clone, Debug, PartialEq)]
pub struct CL04LoginPluginRequest {
pub message_id: VarInt,
pub channel: String,
pub data: Vec<u8>,
}
crate::protocol::packets::packet!(
CL04LoginPluginRequest,
0x04,
crate::protocol::ClientState::Login,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CL04LoginPluginRequest> {
let (data, message_id) = VarInt::parse(data)?;
let (data, channel) = String::parse(data)?;
Ok((
data,
CL04LoginPluginRequest {
message_id,
channel,
data: data.to_vec(),
},
))
},
|packet: &CL04LoginPluginRequest| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.message_id.serialize());
output.extend(packet.channel.serialize());
output.extend(&packet.data);
output
}
);

View File

@ -1,10 +0,0 @@
/// Packets for the `ClientState::Login` state.
pub mod login;
/// Packets for the `ClientState::Play` state.
pub mod play;
/// Packets for the `ClientState::Status` state.
pub mod status;
pub use login::*;
pub use play::*;
pub use status::*;

View File

@ -1,283 +0,0 @@
use crate::protocol::{
entities::{EntityPosition, EntityRotation, EntityVelocity},
types::{Chat, Difficulty, Position, Uuid, VarInt},
};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP00SpawnEntity {
pub id: VarInt,
pub uuid: Uuid,
pub kind: VarInt,
pub position: EntityPosition,
pub rotation: EntityRotation,
pub head_yaw: u8,
pub data: VarInt,
pub velocity: EntityVelocity,
}
crate::protocol::packets::packet!(
CP00SpawnEntity,
0x00,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP00SpawnEntity> {
let (data, id) = VarInt::parse(data)?;
let (data, uuid) = Uuid::parse(data)?;
let (data, kind) = VarInt::parse(data)?;
let (data, position) = EntityPosition::parse(data)?;
let (data, rotation) = EntityRotation::parse(data)?;
let (data, head_yaw) = u8::parse(data)?;
let (data, d) = VarInt::parse(data)?;
let (data, velocity) = EntityVelocity::parse(data)?;
Ok((
data,
CP00SpawnEntity {
id,
uuid,
kind,
position,
rotation,
head_yaw,
data: d,
velocity,
},
))
},
|packet: &CP00SpawnEntity| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.id.serialize());
output.extend(packet.uuid.serialize());
output.extend(packet.kind.serialize());
output.extend(packet.position.serialize());
output.extend(packet.rotation.serialize());
output.extend(packet.head_yaw.serialize());
output.extend(packet.data.serialize());
output.extend(packet.velocity.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP0BChangeDifficulty {
pub difficulty: Difficulty,
pub is_locked: bool,
}
crate::protocol::packets::packet!(
CP0BChangeDifficulty,
0x0b,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP0BChangeDifficulty> {
let (data, difficulty) = Difficulty::parse(data)?;
let (data, is_locked) = bool::parse(data)?;
Ok((
data,
CP0BChangeDifficulty {
difficulty,
is_locked,
},
))
},
|packet: &CP0BChangeDifficulty| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.difficulty.serialize());
output.extend(packet.is_locked.serialize());
output
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CP17Disconnect {
pub reason: Chat,
}
crate::protocol::packets::packet!(
CP17Disconnect,
0x17,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP17Disconnect> {
let (data, reason) = Chat::parse(data)?;
Ok((data, CP17Disconnect { reason }))
},
|packet: &CP17Disconnect| -> Vec<u8> { packet.reason.serialize() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP1FKeepAlive {
pub payload: i64,
}
crate::protocol::packets::packet!(
CP1FKeepAlive,
0x1f,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP1FKeepAlive> {
let (data, payload) = i64::parse(data)?;
Ok((data, CP1FKeepAlive { payload }))
},
|packet: &CP1FKeepAlive| -> Vec<u8> { packet.payload.serialize() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP21WorldEvent {
pub event: i32,
pub location: Position,
pub data: i32,
pub disable_relative_volume: bool,
}
crate::protocol::packets::packet!(
CP21WorldEvent,
0x21,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP21WorldEvent> {
let (data, event) = i32::parse(data)?;
let (data, location) = Position::parse(data)?;
let (data, d) = i32::parse(data)?;
let (data, disable_relative_volume) = bool::parse(data)?;
Ok((
data,
CP21WorldEvent {
event,
location,
data: d,
disable_relative_volume,
},
))
},
|packet: &CP21WorldEvent| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.event.serialize());
output.extend(packet.location.serialize());
output.extend(packet.data.serialize());
output.extend(packet.disable_relative_volume.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP50SetEntityVelocity {
pub entity_id: VarInt,
pub entity_velocity: EntityVelocity,
}
crate::protocol::packets::packet!(
CP50SetEntityVelocity,
0x50,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP50SetEntityVelocity> {
let (data, entity_id) = VarInt::parse(data)?;
let (data, entity_velocity) = EntityVelocity::parse(data)?;
Ok((
data,
CP50SetEntityVelocity {
entity_id,
entity_velocity,
},
))
},
|packet: &CP50SetEntityVelocity| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.entity_id.serialize());
output.extend(packet.entity_velocity.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CP52SetExperience {
pub experience_bar: f32,
pub total_experience: VarInt,
pub level: VarInt,
}
crate::protocol::packets::packet!(
CP52SetExperience,
0x52,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP52SetExperience> {
let (data, experience_bar) = f32::parse(data)?;
let (data, total_experience) = VarInt::parse(data)?;
let (data, level) = VarInt::parse(data)?;
Ok((
data,
CP52SetExperience {
experience_bar,
total_experience,
level,
},
))
},
|packet: &CP52SetExperience| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.experience_bar.serialize());
output.extend(packet.total_experience.serialize());
output.extend(packet.level.serialize());
output
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct CP68EntityEffect {
pub entity_id: VarInt,
pub effect_id: VarInt,
pub amplifier: i8,
pub duration: VarInt,
pub is_ambient: bool,
pub show_particles: bool,
pub show_icon: bool,
pub has_factor_data: bool,
// TODO: pub factor_codec: NBT
}
crate::protocol::packets::packet!(
CP68EntityEffect,
0x68,
crate::protocol::ClientState::Play,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CP68EntityEffect> {
let (data, entity_id) = VarInt::parse(data)?;
let (data, effect_id) = VarInt::parse(data)?;
let (data, amplifier) = i8::parse(data)?;
let (data, duration) = VarInt::parse(data)?;
let (data, flags) = u8::parse(data)?;
let is_ambient = flags & 0x01 > 0;
let show_particles = flags & 0x02 > 0;
let show_icon = flags & 0x04 > 0;
let (data, has_factor_data) = bool::parse(data)?;
// TODO: factor_codec
Ok((
data,
CP68EntityEffect {
entity_id,
effect_id,
amplifier,
duration,
is_ambient,
show_particles,
show_icon,
has_factor_data,
},
))
},
|packet: &CP68EntityEffect| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.entity_id.serialize());
output.extend(packet.effect_id.serialize());
output.extend(packet.amplifier.serialize());
output.extend(packet.duration.serialize());
let mut flags = 0x00u8;
if packet.is_ambient {
flags |= 0x01;
}
if packet.show_particles {
flags |= 0x02;
}
if packet.show_icon {
flags |= 0x04;
}
output.extend(flags.serialize());
// TODO: factor_codec
output
}
);

View File

@ -1,33 +0,0 @@
use crate::protocol::types::Json;
#[derive(Clone, Debug, PartialEq)]
pub struct CS00StatusResponse {
pub response: Json,
}
crate::protocol::packets::packet!(
CS00StatusResponse,
0x00,
crate::protocol::ClientState::Status,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CS00StatusResponse> {
let (data, response) = Json::parse(data)?;
Ok((data, CS00StatusResponse { response }))
},
|packet: &CS00StatusResponse| -> Vec<u8> { packet.response.serialize() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CS01PingResponse {
pub payload: i64,
}
crate::protocol::packets::packet!(
CS01PingResponse,
0x01,
crate::protocol::ClientState::Status,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], CS01PingResponse> {
let (data, payload) = i64::parse(data)?;
Ok((data, CS01PingResponse { payload }))
},
|packet: &CS01PingResponse| -> Vec<u8> { packet.payload.serialize() }
);

View File

@ -1,178 +0,0 @@
/// Packets that are heading to the client.
pub mod clientbound;
/// Packets that are heading to the server.
pub mod serverbound;
use crate::protocol::parsing::{Parsable, VarInt};
/// Alias for a `VarInt`.
pub type PacketId = VarInt;
pub trait Packet:
std::fmt::Debug + Clone + TryFrom<GenericPacket> + Into<GenericPacket> + Parsable
{
const ID: i32;
const CLIENT_STATE: crate::protocol::ClientState;
const IS_SERVERBOUND: bool;
}
macro_rules! generic_packet {
($($packet_type: ident),*) => {
#[derive(Clone, Debug, PartialEq)]
pub enum GenericPacket {
$(
$packet_type($packet_type),
)*
}
impl GenericPacket {
#[tracing::instrument]
pub fn parse_uncompressed<'data>(
client_state: crate::protocol::ClientState,
is_serverbound: bool,
data: &'data [u8]
) -> crate::protocol::parsing::IResult<&'data [u8], Self> {
use crate::protocol::parsing::Parsable;
tracing::trace!(
"GenericPacket::parse_uncompressed: {:?} {} {:?}",
client_state,
is_serverbound,
data
);
let (data, packet_length) = crate::protocol::types::VarInt::parse(data)?;
let (data, packet_data) = nom::bytes::streaming::take(*packet_length as usize)(data)?;
let (packet_data, packet_id) = PacketId::parse(packet_data)?;
let (_packet_data, packet_body) =
Self::parse_body(client_state, packet_id, is_serverbound, packet_data)?;
// if !packet_data.is_empty() {
// println!("Packet data not empty after parsing!");
// }
Ok((data, packet_body))
}
#[tracing::instrument]
pub fn parse_body<'data>(
client_state: crate::protocol::ClientState,
packet_id: crate::protocol::packets::PacketId,
is_serverbound: bool,
data: &'data [u8],
) -> crate::protocol::parsing::IResult<&'data [u8], Self> {
use crate::protocol::parsing::Parsable;
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(data).map(|(data, packet)| (data, Into::<GenericPacket>::into(packet))),
)*
_ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))),
}
}
#[tracing::instrument]
pub fn serialize(&self) -> (crate::protocol::packets::PacketId, Vec<u8>) {
use crate::protocol::parsing::Parsable;
tracing::trace!("GenericPacket::serialize: {:?}", self);
match self {
$(
Self::$packet_type(packet) => (PacketId::from($packet_type::ID), packet.serialize()),
)*
}
}
}
};
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UnimplementedPacket(VarInt);
packet!(
UnimplementedPacket,
0x00,
crate::protocol::ClientState::Disconnected,
false,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], UnimplementedPacket> {
Ok((data, UnimplementedPacket(0i32.into())))
},
|_packet: &UnimplementedPacket| -> Vec<u8> { vec![] }
);
use clientbound::*;
use serverbound::*;
generic_packet!(
UnimplementedPacket,
// Handshake
SH00Handshake,
// Status
SS00StatusRequest,
SS01PingRequest,
CS00StatusResponse,
CS01PingResponse,
// Login
SL00LoginStart,
SL01EncryptionResponse,
SL02LoginPluginResponse,
CL00Disconnect,
CL01EncryptionRequest,
CL02LoginSuccess,
CL03SetCompression,
CL04LoginPluginRequest,
// Play
SP08CommandSuggestionsRequest,
SP11KeepAlive,
SP13SetPlayerPosition,
SP14SetPlayerPositionAndRotation,
SP15SetPlayerRotation,
CP00SpawnEntity,
CP0BChangeDifficulty,
CP17Disconnect,
CP1FKeepAlive,
CP21WorldEvent,
CP50SetEntityVelocity,
CP52SetExperience,
CP68EntityEffect
);
macro_rules! packet {
($packet_type: ident, $id: literal, $client_state: expr, $serverbound: literal, $parse_body: expr, $serialize_body: expr) => {
impl crate::protocol::packets::Packet for $packet_type {
const ID: i32 = $id;
const CLIENT_STATE: crate::protocol::ClientState = $client_state;
const IS_SERVERBOUND: bool = $serverbound;
}
impl crate::protocol::parsing::Parsable for $packet_type {
#[tracing::instrument]
fn parse<'data>(
data: &'data [u8],
) -> crate::protocol::parsing::IResult<&'data [u8], Self> {
$parse_body(data)
}
#[tracing::instrument]
fn serialize(&self) -> Vec<u8> {
$serialize_body(self)
}
}
impl From<$packet_type> for crate::protocol::packets::GenericPacket {
fn from(value: $packet_type) -> Self {
crate::protocol::packets::GenericPacket::$packet_type(value)
}
}
impl TryFrom<crate::protocol::packets::GenericPacket> for $packet_type {
type Error = ();
fn try_from(
value: crate::protocol::packets::GenericPacket,
) -> Result<Self, Self::Error> {
match value {
crate::protocol::packets::GenericPacket::$packet_type(packet) => Ok(packet),
_ => Err(()),
}
}
}
};
}
pub(crate) use packet;

View File

@ -1,40 +0,0 @@
use crate::protocol::{types::VarInt, ClientState};
#[derive(Clone, Debug, PartialEq)]
pub struct SH00Handshake {
pub protocol_version: VarInt,
pub server_address: String,
pub server_port: u16,
pub next_state: ClientState,
}
crate::protocol::packets::packet!(
SH00Handshake,
0x00,
ClientState::Handshake,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SH00Handshake> {
let (data, protocol_version) = VarInt::parse(data)?;
let (data, server_address) = String::parse(data)?;
let (data, server_port) = u16::parse(data)?;
let (data, next_state) = ClientState::parse(data)?;
// let (data, next_state) = VarInt::parse(data)?;
Ok((
data,
SH00Handshake {
protocol_version,
server_address,
server_port,
next_state,
},
))
},
|packet: &SH00Handshake| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.protocol_version.serialize());
output.extend(packet.server_address.serialize());
output.extend(packet.server_port.serialize());
output.extend(packet.next_state.serialize());
output
}
);

View File

@ -1,118 +0,0 @@
use crate::protocol::types::{Uuid, VarInt};
use nom::bytes::streaming::take;
#[derive(Clone, Debug, PartialEq)]
pub struct SL00LoginStart {
pub name: String,
pub uuid: Option<Uuid>,
}
crate::protocol::packets::packet!(
SL00LoginStart,
0x00,
crate::protocol::ClientState::Login,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SL00LoginStart> {
let (data, name) = String::parse(data)?;
let (data, has_uuid) = bool::parse(data)?;
if has_uuid {
let (data, uuid) = Uuid::parse(data)?;
Ok((
data,
SL00LoginStart {
name,
uuid: Some(uuid),
},
))
} else {
Ok((data, SL00LoginStart { name, uuid: None }))
}
},
|packet: &SL00LoginStart| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.name.serialize());
output.extend(packet.uuid.is_some().serialize());
if let Some(uuid) = packet.uuid {
output.extend(uuid.serialize());
}
output
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct SL01EncryptionResponse {
pub shared_secret: Vec<u8>,
pub verify_token: Vec<u8>,
}
crate::protocol::packets::packet!(
SL01EncryptionResponse,
0x01,
crate::protocol::ClientState::Login,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SL01EncryptionResponse> {
let (data, shared_secret_len) = VarInt::parse(data)?;
let (data, shared_secret) = take(*shared_secret_len as usize)(data)?;
let (data, verify_token_len) = VarInt::parse(data)?;
let (data, verify_token) = take(*verify_token_len as usize)(data)?;
Ok((
data,
SL01EncryptionResponse {
shared_secret: shared_secret.to_vec(),
verify_token: verify_token.to_vec(),
},
))
},
|packet: &SL01EncryptionResponse| -> Vec<u8> {
let mut output = vec![];
output.extend(VarInt::from(packet.shared_secret.len() as i32).serialize());
output.extend(&packet.shared_secret);
output.extend(VarInt::from(packet.verify_token.len() as i32).serialize());
output.extend(&packet.verify_token);
output
}
);
#[derive(Clone, Debug, PartialEq)]
pub struct SL02LoginPluginResponse {
pub message_id: VarInt,
pub successful: bool,
pub data: Vec<u8>,
}
crate::protocol::packets::packet!(
SL02LoginPluginResponse,
0x02,
crate::protocol::ClientState::Login,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SL02LoginPluginResponse> {
let (data, message_id) = VarInt::parse(data)?;
let (data, successful) = bool::parse(data)?;
if successful {
Ok((
&[],
SL02LoginPluginResponse {
message_id,
successful,
data: data.to_vec(),
},
))
} else {
Ok((
data,
SL02LoginPluginResponse {
message_id,
successful,
data: vec![],
},
))
}
},
|packet: &SL02LoginPluginResponse| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.message_id.serialize());
output.extend(packet.successful.serialize());
if packet.successful {
output.extend(&packet.data);
}
output
}
);

View File

@ -1,13 +0,0 @@
/// Packets for the `ClientState::Handshake` state.
pub mod handshake;
/// Packets for the `ClientState::Login` state.
pub mod login;
/// Packets for the `ClientState::Play` state.
pub mod play;
/// Packets for the `ClientState::Status` state.
pub mod status;
pub use handshake::*;
pub use login::*;
pub use play::*;
pub use status::*;

View File

@ -1,134 +0,0 @@
use crate::protocol::{
entities::{EntityPosition, EntityRotation},
types::VarInt,
};
#[derive(Clone, Debug, PartialEq)]
pub struct SP08CommandSuggestionsRequest {
pub transaction_id: VarInt,
pub text: String,
}
crate::protocol::packets::packet!(
SP08CommandSuggestionsRequest,
0x08,
crate::protocol::ClientState::Play,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SP08CommandSuggestionsRequest> {
let (data, transaction_id) = VarInt::parse(data)?;
let (data, text) = String::parse(data)?;
Ok((data, SP08CommandSuggestionsRequest {
transaction_id,
text,
}))
},
|packet: &SP08CommandSuggestionsRequest| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.transaction_id.serialize());
output.extend(packet.text.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP11KeepAlive {
pub payload: i64,
}
crate::protocol::packets::packet!(
SP11KeepAlive,
0x11,
crate::protocol::ClientState::Play,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SP11KeepAlive> {
let (data, payload) = i64::parse(data)?;
Ok((data, SP11KeepAlive { payload }))
},
|packet: &SP11KeepAlive| -> Vec<u8> { packet.payload.serialize() }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP13SetPlayerPosition {
pub position: EntityPosition,
pub on_ground: bool,
}
crate::protocol::packets::packet!(
SP13SetPlayerPosition,
0x13,
crate::protocol::ClientState::Play,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SP13SetPlayerPosition> {
let (data, position) = EntityPosition::parse(data)?;
let (data, on_ground) = bool::parse(data)?;
Ok((
data,
SP13SetPlayerPosition {
position,
on_ground,
},
))
},
|packet: &SP13SetPlayerPosition| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.position.serialize());
output.extend(packet.on_ground.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP14SetPlayerPositionAndRotation {
pub position: EntityPosition,
pub rotation: EntityRotation,
pub on_ground: bool,
}
crate::protocol::packets::packet!(
SP14SetPlayerPositionAndRotation,
0x14,
crate::protocol::ClientState::Play,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SP14SetPlayerPositionAndRotation> {
let (data, position) = EntityPosition::parse(data)?;
let (data, rotation) = EntityRotation::parse(data)?;
let (data, on_ground) = bool::parse(data)?;
Ok((data, SP14SetPlayerPositionAndRotation {
position,
rotation,
on_ground,
}))
},
|packet: &SP14SetPlayerPositionAndRotation| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.position.serialize());
output.extend(packet.rotation.serialize());
output.extend(packet.on_ground.serialize());
output
}
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SP15SetPlayerRotation {
pub rotation: EntityRotation,
pub on_ground: bool,
}
crate::protocol::packets::packet!(
SP15SetPlayerRotation,
0x15,
crate::protocol::ClientState::Play,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SP15SetPlayerRotation> {
let (data, rotation) = EntityRotation::parse(data)?;
let (data, on_ground) = bool::parse(data)?;
Ok((
data,
SP15SetPlayerRotation {
rotation,
on_ground,
},
))
},
|packet: &SP15SetPlayerRotation| -> Vec<u8> {
let mut output = vec![];
output.extend(packet.rotation.serialize());
output.extend(packet.on_ground.serialize());
output
}
);

View File

@ -1,28 +0,0 @@
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SS00StatusRequest;
crate::protocol::packets::packet!(
SS00StatusRequest,
0x00,
crate::protocol::ClientState::Status,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SS00StatusRequest> {
Ok((data, SS00StatusRequest))
},
|_packet: &SS00StatusRequest| -> Vec<u8> { vec![] }
);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SS01PingRequest {
pub payload: i64,
}
crate::protocol::packets::packet!(
SS01PingRequest,
0x01,
crate::protocol::ClientState::Status,
true,
|data: &'data [u8]| -> crate::protocol::parsing::IResult<&'data [u8], SS01PingRequest> {
let (data, payload) = i64::parse(data)?;
Ok((data, SS01PingRequest { payload }))
},
|packet: &SS01PingRequest| -> Vec<u8> { packet.payload.serialize() }
);

View File

@ -2,7 +2,8 @@ pub use nom::IResult;
use nom::{
bytes::streaming::{take, take_while_m_n},
combinator::map_res,
number::streaming as nom_nums, Parser,
number::streaming as nom_nums,
Parser,
};
/// Implementation of the protocol's VarInt type.
@ -11,6 +12,11 @@ use nom::{
/// When the original i32 value is needed, simply `Deref` it.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct VarInt(i32);
impl VarInt {
pub fn parse_usize(data: &[u8]) -> IResult<&[u8], usize> {
nom::combinator::map_res(Self::parse, usize::try_from)(data)
}
}
impl std::ops::Deref for VarInt {
type Target = i32;
fn deref(&self) -> &Self::Target {
@ -37,6 +43,12 @@ impl From<usize> for VarInt {
(value as i32).into()
}
}
impl TryFrom<VarInt> for usize {
type Error = <usize as TryFrom<i32>>::Error;
fn try_from(value: VarInt) -> Result<Self, Self::Error> {
usize::try_from(*value)
}
}
impl std::fmt::Display for VarInt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)

View File

@ -37,17 +37,10 @@ impl ProxyConfig {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct ProxyArgs {
upstream: String,
}
impl Default for ProxyArgs {
fn default() -> Self {
ProxyArgs {
upstream: String::new(),
}
}
}
impl ProxyArgs {
pub fn instance() -> Option<&'static Self> {
Args::instance().proxy.as_ref()

View File

@ -199,7 +199,8 @@ impl Server {
.filter(|client| matches!(client.state, NetworkClientState::Play))
.count();
'clients: for client in clients.iter_mut() {
use crate::protocol::packets::{clientbound::*, serverbound::*};
use crate::protocol::packets;
'packets: while !client.incoming_packet_queue.is_empty() {
// client.read_packet()
// None: The client doesn't have any more packets.
@ -207,7 +208,9 @@ impl Server {
// Some(Ok(_)): The client read the expected packet.
match client.state.clone() {
NetworkClientState::Handshake => {
let handshake = match client.read_packet::<SH00Handshake>() {
use packets::handshake::serverbound::Handshake;
let handshake = match client.read_packet::<Handshake>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(handshake)) => handshake,
@ -235,7 +238,11 @@ impl Server {
received_request,
received_ping,
} if !received_request => {
let _status_request = match client.read_packet::<SS00StatusRequest>() {
use packets::status::{
clientbound::StatusResponse, serverbound::StatusRequest,
};
let _status_request = match client.read_packet::<StatusRequest>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(p)) => p,
@ -246,7 +253,7 @@ impl Server {
};
let config = Config::instance();
use base64::Engine;
client.queue_packet(CS00StatusResponse {
client.queue_packet(StatusResponse {
response: serde_json::json!({
"version": {
"name": config.global.game_version,
@ -267,19 +274,25 @@ impl Server {
}
// Status !received_ping: Read SS00StatusRequest and respond with CS00StatusResponse
NetworkClientState::Status { received_ping, .. } if !received_ping => {
let ping = match client.read_packet::<SS01PingRequest>() {
use packets::status::{
clientbound::PingResponse, serverbound::PingRequest,
};
let ping = match client.read_packet::<PingRequest>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(p)) => p,
};
client.queue_packet(CS01PingResponse {
client.queue_packet(PingResponse {
payload: ping.payload,
});
client.state = NetworkClientState::Disconnected;
}
NetworkClientState::Status { .. } => unreachable!(),
NetworkClientState::Login { received_start, .. } if !received_start.0 => {
let login_start = match client.read_packet::<SL00LoginStart>() {
use packets::login::{clientbound::*, serverbound::*};
let login_start = match client.read_packet::<LoginStart>() {
None => continue 'packets,
Some(Err(_)) => continue 'clients,
Some(Ok(p)) => p,
@ -287,7 +300,7 @@ impl Server {
// TODO: Authenticate the user.
// TODO: Get the user from the stored database.
// TODO: Encryption/compression.
client.queue_packet(CL02LoginSuccess {
client.queue_packet(LoginSuccess {
uuid: login_start.uuid.unwrap_or(0u128),
username: login_start.name.clone(),
properties: vec![],

View File

@ -1,5 +1,5 @@
use crate::protocol::{
packets::{serverbound::SL00LoginStart, GenericPacket},
packets::{self, Packet, PacketDirection},
parsing::Parsable,
ClientState,
};
@ -30,7 +30,7 @@ pub(crate) enum NetworkClientState {
/// The client sent `SH00Handshake` with `next_state = ClientState::Login`
/// and is attempting to join the server.
Login {
received_start: (bool, Option<SL00LoginStart>),
received_start: (bool, Option<packets::login::serverbound::LoginStart>),
},
/// The server sent `CL02LoginSuccess` and transitioned to `Play`.
#[allow(dead_code)]
@ -76,13 +76,13 @@ pub(crate) struct NetworkClient {
incoming_data: VecDeque<u8>,
/// Packets get appended to the back as they get read,
/// and popped from the front as they get handled.
pub incoming_packet_queue: VecDeque<GenericPacket>,
pub incoming_packet_queue: VecDeque<Packet>,
/// Keeps track of the last time the client sent data.
///
/// This is useful for removing clients that have timed out.
pub last_received_data_time: Instant,
/// Packets get appended to the back and get popped from the front as they get sent.
pub outgoing_packet_queue: VecDeque<GenericPacket>,
pub outgoing_packet_queue: VecDeque<Packet>,
}
impl NetworkClient {
#[tracing::instrument]
@ -139,7 +139,11 @@ impl NetworkClient {
let mut bytes_consumed = 0;
while !data.is_empty() {
let p = GenericPacket::parse_uncompressed(self.state.clone().into(), true, data);
let p = Packet::parse(
self.state.clone().into(),
PacketDirection::Serverbound,
data,
);
trace!("{} got {:?}", self.id, p);
match p {
Ok((d, packet)) => {
@ -166,9 +170,9 @@ impl NetworkClient {
// 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>>(
pub fn read_packet<P: std::fmt::Debug + TryFrom<Packet>>(
&mut self,
) -> Option<std::result::Result<P, GenericPacket>> {
) -> Option<std::result::Result<P, Packet>> {
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))
@ -181,7 +185,7 @@ impl NetworkClient {
}
}
#[tracing::instrument]
pub fn queue_packet<P: std::fmt::Debug + Into<GenericPacket>>(&mut self, packet: P) {
pub fn queue_packet<P: std::fmt::Debug + Into<Packet>>(&mut self, packet: P) {
self.outgoing_packet_queue.push_back(packet.into());
}
#[tracing::instrument]
@ -195,11 +199,11 @@ impl NetworkClient {
Ok(())
}
#[tracing::instrument]
pub async fn send_packet<P: std::fmt::Debug + Into<GenericPacket>>(
pub async fn send_packet<P: std::fmt::Debug + Into<Packet>>(
&self,
packet: P,
) -> tokio::io::Result<()> {
let packet: GenericPacket = packet.into();
let packet: Packet = packet.into();
debug!("Sending packet {:?} to client {}", packet, self.id);
let (packet_id, mut packet_body) = packet.serialize();
@ -219,7 +223,8 @@ impl NetworkClient {
}
#[tracing::instrument]
pub async fn disconnect(&mut self, reason: Option<crate::protocol::types::Chat>) {
use crate::protocol::packets::clientbound::{CL00Disconnect, CP17Disconnect};
use packets::{login::clientbound::LoginDisconnect, play::clientbound::PlayDisconnect};
let reason = reason.unwrap_or(serde_json::json!({
"text": "You have been disconnected!"
}));
@ -229,10 +234,10 @@ impl NetworkClient {
// Impossible to send a disconnect in these states.
}
ClientState::Login => {
let _ = self.send_packet(CL00Disconnect { reason }).await;
let _ = self.send_packet(LoginDisconnect { reason }).await;
}
ClientState::Play => {
let _ = self.send_packet(CP17Disconnect { reason }).await;
let _ = self.send_packet(PlayDisconnect { reason }).await;
}
}