diff --git a/Cargo.toml b/Cargo.toml index 761ea9b..e55fd89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,7 @@ name = "mcpl" [dependencies] byteorder = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "0.7", features = ["v4", "serde"] } +mc-varint = { git = "https://github.com/luojia65/mc-varint" } diff --git a/src/lib.rs b/src/lib.rs index 6753a1c..5949a38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,27 +1,111 @@ +use mc_varint::{VarIntRead, VarIntWrite}; use std::io; use std::io::{Read, Write}; +use std::string::FromUtf8Error; pub mod status; /// Current supported protocol version. pub const PROTOCOL_VERSION: usize = 498; -/// Possible errors while decoding packet. -pub enum DecodePacketError { - UnknownPacketType { type_id: u8 }, - IOError { io_error: io::Error }, +/// Possible errors while encoding packet. +pub enum EncodeError { + StringTooLong, + IOError { + io_error: io::Error, + }, + JsonError { + json_error: serde_json::error::Error, + }, } -impl From for DecodePacketError { +impl From for EncodeError { fn from(io_error: io::Error) -> Self { - DecodePacketError::IOError { io_error } + EncodeError::IOError { io_error } + } +} + +impl From for EncodeError { + fn from(json_error: serde_json::error::Error) -> Self { + EncodeError::JsonError { json_error } + } +} + +/// Possible errors while decoding packet. +pub enum DecodeError { + UnknownPacketType { + type_id: u8, + }, + StringTooLong, + IOError { + io_error: io::Error, + }, + JsonError { + json_error: serde_json::error::Error, + }, + Utf8Error { + utf8_error: FromUtf8Error, + }, +} + +impl From for DecodeError { + fn from(io_error: io::Error) -> Self { + DecodeError::IOError { io_error } + } +} + +impl From for DecodeError { + fn from(json_error: serde_json::error::Error) -> Self { + DecodeError::JsonError { json_error } + } +} + +impl From for DecodeError { + fn from(utf8_error: FromUtf8Error) -> Self { + DecodeError::Utf8Error { utf8_error } } } trait Packet { type Output; - fn encode(&self, writer: &mut W) -> Result<(), io::Error>; + fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; - fn decode(reader: &mut R) -> Result; + fn decode(reader: &mut R) -> Result; +} + +trait PacketRead { + fn read_string(&mut self) -> Result; +} + +trait PacketWrite { + fn write_string(&mut self, value: &str) -> Result<(), EncodeError>; +} + +impl PacketRead for R { + fn read_string(&mut self) -> Result { + let length = self.read_var_u32()?; + + if length > 32_767 { + return Err(DecodeError::StringTooLong); + } + + let mut buf = vec![0; length as usize]; + self.read_exact(&mut buf)?; + + Ok(String::from_utf8(buf)?) + } +} + +impl PacketWrite for W { + fn write_string(&mut self, value: &str) -> Result<(), EncodeError> { + if value.len() > 32_767 { + return Err(EncodeError::StringTooLong); + } + + self.write_var_u32(value.len() as u32)?; + self.write_all(value.as_bytes())?; + + Ok(()) + } } diff --git a/src/status.rs b/src/status.rs index 43ff8a6..1ec9dfc 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,7 +1,8 @@ -use crate::{DecodePacketError, Packet}; +use crate::{DecodeError, EncodeError, Packet, PacketWrite}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use std::io; +use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; +use uuid::Uuid; pub enum StatusServerBoundPacket { StatusRequest, @@ -9,7 +10,7 @@ pub enum StatusServerBoundPacket { } pub enum StatusClientBoundPacket { - StatusResponse, + StatusResponse(StatusResponse), PingResponse(PingResponse), } @@ -21,7 +22,7 @@ impl StatusServerBoundPacket { } } - pub fn decode(type_id: u8, reader: &mut R) -> Result { + pub fn decode(type_id: u8, reader: &mut R) -> Result { match type_id { 0x0 => Ok(StatusServerBoundPacket::StatusRequest), 0x1 => { @@ -29,7 +30,7 @@ impl StatusServerBoundPacket { Ok(StatusServerBoundPacket::PingRequest(ping_request)) } - _ => Err(DecodePacketError::UnknownPacketType { type_id }), + _ => Err(DecodeError::UnknownPacketType { type_id }), } } } @@ -37,7 +38,7 @@ impl StatusServerBoundPacket { impl StatusClientBoundPacket { pub fn get_type_id(&self) -> u8 { match self { - StatusClientBoundPacket::StatusResponse => 0x0, + StatusClientBoundPacket::StatusResponse(_) => 0x0, StatusClientBoundPacket::PingResponse(_) => 0x1, } } @@ -58,13 +59,13 @@ impl PingRequest { impl Packet for PingRequest { type Output = Self; - fn encode(&self, writer: &mut W) -> Result<(), io::Error> { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { writer.write_u64::(self.time)?; Ok(()) } - fn decode(reader: &mut R) -> Result { + fn decode(reader: &mut R) -> Result { let time = reader.read_u64::()?; Ok(PingRequest { time }) @@ -86,15 +87,71 @@ impl PingResponse { impl Packet for PingResponse { type Output = Self; - fn encode(&self, writer: &mut W) -> Result<(), io::Error> { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { writer.write_u64::(self.time)?; Ok(()) } - fn decode(reader: &mut R) -> Result { + fn decode(reader: &mut R) -> Result { let time = reader.read_u64::()?; Ok(PingResponse { time }) } } + +#[derive(Serialize, Deserialize)] +pub struct ServerStatus { + pub version: ServerVersion, + pub description: String, + pub players: OnlinePlayers, +} + +#[derive(Serialize, Deserialize)] +pub struct ServerVersion { + pub name: String, + pub protocol: u32, +} + +#[derive(Serialize, Deserialize)] +pub struct OnlinePlayers { + pub online: u32, + pub max: u32, + pub sample: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct OnlinePlayer { + pub id: Uuid, + pub name: String, +} + +pub struct StatusResponse { + pub server_status: ServerStatus, +} + +impl StatusResponse { + pub fn new(server_status: ServerStatus) -> StatusClientBoundPacket { + let status_response = StatusResponse { server_status }; + + StatusClientBoundPacket::StatusResponse(status_response) + } +} + +impl Packet for StatusResponse { + type Output = Self; + + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + let json = serde_json::to_string(&self.server_status)?; + writer.write_string(&json)?; + + Ok(()) + } + + fn decode(reader: &mut R) -> Result { + let server_status = serde_json::from_reader(reader)?; + let status_response = StatusResponse { server_status }; + + Ok(status_response) + } +}