Extract all packet writing logic to single function

This commit is contained in:
timvisee 2021-11-16 17:57:34 +01:00
parent 7df3829e00
commit 47fe7d0387
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
6 changed files with 236 additions and 294 deletions

4
Cargo.lock generated
View File

@ -685,7 +685,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "minecraft-protocol"
version = "0.1.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=4e6a1f9#4e6a1f93807f35671943630c9bdc0d0c5da67eb8"
dependencies = [
"byteorder",
"minecraft-protocol-derive",
@ -698,7 +698,7 @@ dependencies = [
[[package]]
name = "minecraft-protocol-derive"
version = "0.0.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=4e6a1f9#4e6a1f93807f35671943630c9bdc0d0c5da67eb8"
dependencies = [
"proc-macro2",
"quote",

View File

@ -40,7 +40,7 @@ dotenv = "0.15"
flate2 = { version = "1.0", default-features = false, features = ["default"] }
futures = { version = "0.3", default-features = false }
log = "0.4"
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "d26a525" }
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "4e6a1f9" }
pretty_env_logger = "0.4"
rand = "0.8"
serde = "1.0"

View File

@ -8,15 +8,13 @@ use bytes::BytesMut;
use futures::FutureExt;
use minecraft_protocol::data::chat::{Message, Payload};
use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::login::{LoginStart, LoginSuccess, SetCompression};
use minecraft_protocol::version::v1_17_1::game::{
ClientBoundKeepAlive, JoinGame, NamedSoundEffect, PlayerPositionAndLook, PluginMessage,
Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
ClientBoundKeepAlive, ClientBoundPluginMessage, JoinGame, NamedSoundEffect,
PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
};
use nbt::CompoundTag;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream;
use tokio::select;
@ -28,8 +26,7 @@ use crate::mc;
use crate::net;
use crate::proto;
use crate::proto::client::{Client, ClientInfo, ClientState};
use crate::proto::packet::{self, RawPacket};
use crate::proto::packets;
use crate::proto::{packet, packets};
use crate::proxy;
use crate::server::{Server, State};
@ -185,15 +182,7 @@ async fn respond_set_compression(
writer: &mut WriteHalf<'_>,
threshold: i32,
) -> Result<(), ()> {
let packet = SetCompression { threshold };
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::login::CLIENT_SET_COMPRESSION, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
packet::write_packet(SetCompression { threshold }, client, writer).await
}
/// Respond to client with login success packet
@ -203,21 +192,18 @@ async fn respond_login_success(
writer: &mut WriteHalf<'_>,
login_start: &LoginStart,
) -> Result<(), ()> {
let packet = LoginSuccess {
packet::write_packet(
LoginSuccess {
uuid: Uuid::new_v3(
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
login_start.name.as_bytes(),
),
username: login_start.name.clone(),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::login::CLIENT_LOGIN_SUCCESS, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Play lobby ready sound effect if configured.
@ -271,7 +257,8 @@ async fn send_lobby_join_game(
server: &Server,
) -> Result<(), ()> {
// Send Minecrafts default states, slightly customised for lobby world
let packet = {
packet::write_packet(
{
let status = server.status().await;
JoinGame {
@ -301,37 +288,31 @@ async fn send_lobby_join_game(
is_debug: true,
is_flat: false,
}
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_JOIN_GAME, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send lobby brand to client.
async fn send_lobby_brand(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = PluginMessage {
packet::write_packet(
ClientBoundPluginMessage {
channel: "minecraft:brand".into(),
data: SERVER_BRAND.into(),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_PLUGIN_MESSAGE, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send lobby player position to client.
async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
// Send player location, disables download terrain screen
let packet = PlayerPositionAndLook {
packet::write_packet(
PlayerPositionAndLook {
x: 0.0,
y: 0.0,
z: 0.0,
@ -340,15 +321,11 @@ async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> R
flags: 0b00000000,
teleport_id: 0,
dismount_vehicle: true,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_PLAYER_POS_LOOK, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send lobby time update to client.
@ -356,38 +333,32 @@ async fn send_lobby_time_update(client: &Client, writer: &mut WriteHalf<'_>) ->
const MC_TIME_NOON: i64 = 6000;
// Send time update, required once for keep-alive packets
let packet = TimeUpdate {
packet::write_packet(
TimeUpdate {
world_age: MC_TIME_NOON,
time_of_day: MC_TIME_NOON,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_TIME_UPDATE, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send keep alive packet to client.
///
/// Required periodically in play mode to prevent client timeout.
async fn send_keep_alive(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = ClientBoundKeepAlive {
packet::write_packet(
ClientBoundKeepAlive {
// Keep sending new IDs
id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_KEEP_ALIVE, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
},
client,
writer,
)
.await
// TODO: verify we receive keep alive response with same ID from client
Ok(())
}
/// Send lobby title packets to client.
@ -405,29 +376,28 @@ async fn send_lobby_title(
let subtitle = text.lines().skip(1).collect::<Vec<_>>().join("\n");
// Set title
let packet = SetTitleText {
packet::write_packet(
SetTitleText {
text: Message::new(Payload::text(title)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_TEXT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
},
client,
writer,
)
.await?;
// Set subtitle
let packet = SetTitleSubtitle {
packet::write_packet(
SetTitleSubtitle {
text: Message::new(Payload::text(&subtitle)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_SUBTITLE, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
},
client,
writer,
)
.await?;
// Set title times
let packet = if title.is_empty() && subtitle.is_empty() {
packet::write_packet(
if title.is_empty() && subtitle.is_empty() {
// Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
SetTitleTimes {
fade_in: 10,
@ -440,15 +410,11 @@ async fn send_lobby_title(
stay: KEEP_ALIVE_INTERVAL.as_secs() as i32 * mc::TICKS_PER_SECOND as i32 * 2,
fade_out: 0,
}
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_TIMES, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send lobby ready sound effect to client.
@ -457,7 +423,8 @@ async fn send_lobby_sound_effect(
writer: &mut WriteHalf<'_>,
sound_name: &str,
) -> Result<(), ()> {
let packet = NamedSoundEffect {
packet::write_packet(
NamedSoundEffect {
sound_name: sound_name.into(),
sound_category: 0,
effect_pos_x: 0,
@ -465,15 +432,11 @@ async fn send_lobby_sound_effect(
effect_pos_z: 0,
volume: 1.0,
pitch: 1.0,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_NAMED_SOUND_EFFECT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// Send respawn packet to client to jump from lobby into now loaded server.
@ -484,7 +447,8 @@ async fn send_respawn_from_join(
writer: &mut WriteHalf<'_>,
join_game: JoinGame,
) -> Result<(), ()> {
let packet = Respawn {
packet::write_packet(
Respawn {
dimension: join_game.dimension,
world_name: join_game.world_name,
hashed_seed: join_game.hashed_seed,
@ -493,15 +457,11 @@ async fn send_respawn_from_join(
is_debug: join_game.is_debug,
is_flat: join_game.is_flat,
copy_metadata: false,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_RESPAWN, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
},
client,
writer,
)
.await
}
/// An infinite keep-alive loop.
@ -634,29 +594,27 @@ async fn connect_to_server_no_timeout(
tmp_client.set_state(ClientState::Login);
// Handshake packet
let packet = Handshake {
packet::write_packet(
Handshake {
protocol_version: client_info.protocol_version.unwrap(),
server_addr: config.server.address.ip().to_string(),
server_port: config.server.address.port(),
next_state: ClientState::Login.to_id(),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let request = RawPacket::new(packets::handshake::SERVER_HANDSHAKE, data).encode(&tmp_client)?;
writer.write_all(&request).await.map_err(|_| ())?;
},
&tmp_client,
&mut writer,
)
.await?;
// Request login start
let packet = LoginStart {
packet::write_packet(
LoginStart {
name: client_info.username.ok_or(())?,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let request = RawPacket::new(packets::login::SERVER_LOGIN_START, data).encode(&tmp_client)?;
writer.write_all(&request).await.map_err(|_| ())?;
},
&tmp_client,
&mut writer,
)
.await?;
// Incoming buffer
let mut buf = BytesMut::new();

View File

@ -7,18 +7,17 @@ use std::time::Duration;
use bytes::BytesMut;
use minecraft_protocol::data::server_status::ServerStatus;
use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse, StatusResponse};
use minecraft_protocol::version::v1_14_4::status::{
PingRequest, PingResponse, StatusRequest, StatusResponse,
};
use rand::Rng;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::time;
use crate::config::Config;
use crate::proto::client::{Client, ClientState};
use crate::proto::packet::{self, RawPacket};
use crate::proto::packets;
use crate::proto::{packet, packets};
use crate::server::{Server, State};
/// Monitor ping inverval in seconds.
@ -125,45 +124,28 @@ async fn send_handshake(
config: &Config,
addr: SocketAddr,
) -> Result<(), ()> {
let handshake = Handshake {
packet::write_packet(
Handshake {
protocol_version: config.public.protocol as i32,
server_addr: addr.ip().to_string(),
server_port: addr.port(),
next_state: ClientState::Status.to_id(),
};
let mut packet = Vec::new();
handshake.encode(&mut packet).map_err(|_| ())?;
let raw = RawPacket::new(packets::handshake::SERVER_HANDSHAKE, packet)
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
Ok(())
},
client,
&mut stream.split().1,
)
.await
}
/// Send status request.
async fn request_status(client: &Client, stream: &mut TcpStream) -> Result<(), ()> {
let raw = RawPacket::new(packets::status::SERVER_STATUS, vec![])
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
Ok(())
packet::write_packet(StatusRequest {}, client, &mut stream.split().1).await
}
/// Send status request.
async fn send_ping(client: &Client, stream: &mut TcpStream) -> Result<u64, ()> {
let token = rand::thread_rng().gen();
let ping = PingRequest { time: token };
let mut packet = Vec::new();
ping.encode(&mut packet).map_err(|_| ())?;
let raw = RawPacket::new(packets::status::SERVER_PING, packet)
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
packet::write_packet(PingRequest { time: token }, client, &mut stream.split().1).await?;
Ok(token)
}

View File

@ -1,51 +1,36 @@
use minecraft_protocol::data::chat::{Message, Payload};
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::game::GameDisconnect;
use minecraft_protocol::version::v1_14_4::login::LoginDisconnect;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::WriteHalf;
use crate::proto::client::{Client, ClientState};
use crate::proto::packet::RawPacket;
use crate::proto::packets;
use crate::proto::packet;
/// Kick client with a message.
///
/// Should close connection afterwards.
pub async fn kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
match client.state() {
ClientState::Login => login_kick(client, msg, writer).await,
ClientState::Play => play_kick(client, msg, writer).await,
ClientState::Login => {
packet::write_packet(
LoginDisconnect {
reason: Message::new(Payload::text(msg)),
},
client,
writer,
)
.await
}
ClientState::Play => {
packet::write_packet(
GameDisconnect {
reason: Message::new(Payload::text(msg)),
},
client,
writer,
)
.await
}
_ => Err(()),
}
}
/// Kick client with a message in login state.
///
/// Should close connection afterwards.
async fn login_kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = LoginDisconnect {
reason: Message::new(Payload::text(msg)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::login::CLIENT_DISCONNECT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())
}
/// Kick client with a message in play state.
///
/// Should close connection afterwards.
async fn play_kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = GameDisconnect {
reason: Message::new(Payload::text(msg)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_DISCONNECT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())
}

View File

@ -4,9 +4,11 @@ use bytes::BytesMut;
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::PacketId;
use tokio::io;
use tokio::io::AsyncReadExt;
use tokio::net::tcp::ReadHalf;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{ReadHalf, WriteHalf};
use crate::proto::client::Client;
use crate::proto::BUF_SIZE;
@ -197,3 +199,18 @@ pub async fn read_packet(
Ok(Some((packet, raw.to_vec())))
}
/// Write packet to stream writer.
pub async fn write_packet(
packet: impl PacketId + Encoder,
client: &Client,
writer: &mut WriteHalf<'_>,
) -> Result<(), ()> {
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packet.packet_id(), data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
}