Refactored status.rs to use traits

This commit is contained in:
Kyler 2023-12-20 23:41:01 -07:00
parent 1d717ab8da
commit e9a3770c1e
3 changed files with 395 additions and 234 deletions

View File

@ -8,6 +8,7 @@ mod mc_types;
mod handshake;
mod status;
mod login;
mod status_handle;
use mc_types::Packet;
@ -50,7 +51,7 @@ async fn handle_client(client_socket: TcpStream) {
let packet_id: u8 = buffer[0];
if packet_id == 0xFE {
status::respond_legacy_status(&mut client_writer)
status_handle::respond_legacy_status(&mut client_writer)
.await.expect("Error handling legacy status request");
return;
} else {
@ -60,7 +61,7 @@ async fn handle_client(client_socket: TcpStream) {
println!("Next state: {}", handshake_packet.next_state);
if handshake_packet.next_state == 1 {
println!("Receiving Status Request");
status::respond_status(
status_handle::respond_status(
&mut client_reader,
&mut client_writer,
&mut server_reader,

View File

@ -1,249 +1,194 @@
// Yeahbut December 2023
use std::fs::{self, File};
use std::io::Read;
pub mod clientbound {
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::io::AsyncWriteExt;
use serde::{Serialize, Deserialize};
use serde_json::Value;
use base64::{Engine as _, engine::general_purpose};
use rand::Rng;
use tokio::net::tcp::OwnedReadHalf;
use serde::{Serialize, Deserialize};
use crate::mc_types::{self, Result, Packet};
use crate::handshake;
use crate::mc_types::{self, Result, Packet, PacketError};
#[derive(Serialize, Deserialize)]
pub struct StatusVersion {
pub name: String,
pub protocol: i32,
}
#[derive(Serialize, Deserialize)]
pub struct StatusPlayerInfo {
pub name: String,
pub id: String,
}
#[derive(Serialize, Deserialize)]
pub struct StatusPlayers {
pub max: i32,
pub online: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample: Option<Vec<StatusPlayerInfo>>
}
#[derive(Serialize, Deserialize)]
pub struct StatusResponseData {
pub version: StatusVersion,
pub description: mc_types::Chat,
pub players: StatusPlayers,
#[serde(skip_serializing_if = "Option::is_none")]
pub favicon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforcesSecureChat: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previewsChat: Option<bool>,
}
async fn online_players(
server_reader: &mut OwnedReadHalf,
server_writer: &mut OwnedWriteHalf,
) -> Result<StatusPlayers> {
Ok(get_upstream_status(server_reader, server_writer).await?.players)
}
fn motd() -> String {
let default = "A Minecraft Server Proxy".to_string();
let file_path = "./motd.json";
let data = match fs::read_to_string(file_path) {
Ok(data) => data,
Err(_) => return default,
};
let motd_data: Value = match serde_json::from_str(&data) {
Ok(value) => value,
Err(_) => return default,
};
let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len());
let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len());
if length1 == 0 || length2 == 0 {
return default;
#[derive(Serialize, Deserialize)]
pub struct StatusVersion {
pub name: String,
pub protocol: i32,
}
let mut rng = rand::thread_rng();
let rand1 = rng.gen_range(0..length1) as usize;
let rand2 = rng.gen_range(0..length2) as usize;
#[derive(Serialize, Deserialize)]
pub struct StatusPlayerInfo {
pub name: String,
pub id: String,
}
let line1: &str = match motd_data["line1"][rand1].as_str() {
Some(s) => s,
None => return default,
};
#[derive(Serialize, Deserialize)]
pub struct StatusPlayers {
pub max: i32,
pub online: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample: Option<Vec<StatusPlayerInfo>>
}
// TODO: Birthdays, Holidays, and Announcements
#[derive(Serialize, Deserialize)]
pub struct StatusResponseData {
pub version: StatusVersion,
pub description: mc_types::Chat,
pub players: StatusPlayers,
#[serde(skip_serializing_if = "Option::is_none")]
pub favicon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforcesSecureChat: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previewsChat: Option<bool>,
}
let line2: &str = match motd_data["line2"][rand2].as_str() {
Some(s) => s,
None => return default,
};
pub enum StatusPackets {
Status(Status),
Ping(Ping),
}
let line: String = format!("{}\n{}", line1, line2);
line
}
fn favicon() -> Option<String> {
let file_path = "./main_icon.png";
let mut file = match File::open(file_path) {
Ok(file) => file,
Err(_) => return None,
};
let mut buffer = Vec::new();
if let Err(_) = file.read_to_end(&mut buffer) {
return None
};
let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer);
let full_string: String =
format!("data:image/png;base64,{}", base64_string);
Some(full_string)
}
pub async fn respond_status(
client_reader: &mut OwnedReadHalf,
client_writer: &mut OwnedWriteHalf,
server_reader: &mut Option<OwnedReadHalf>,
server_writer: &mut Option<OwnedWriteHalf>,
)-> Result<()> {
loop {
println!("Status Handling");
let mut data = mc_types::read_data(client_reader).await?;
let packet_id = mc_types::get_var_int(&mut data)?;
println!("Status Packet ID: {}", packet_id);
if packet_id == 0x00 {
println!("Handling Status");
let favicon = favicon();
let online_players = match server_reader {
Some(server_reader) => match server_writer {
Some(server_writer) => match online_players(
server_reader,
server_writer,
).await {
Ok(value) => Some(value),
Err(_) => None,
},
None => None,
},
None => None,
};
let status_response =
match online_players {
Some(online_players) => StatusResponseData {
version: StatusVersion {
name: mc_types::VERSION_NAME.to_string(),
protocol: mc_types::VERSION_PROTOCOL,
},
description: mc_types::Chat {
text: motd(),
},
players: StatusPlayers {
max: -13,
online: online_players.online,
sample: online_players.sample,
},
favicon: favicon,
enforcesSecureChat: Some(false),
previewsChat: Some(false),
},
None => StatusResponseData {
version: StatusVersion {
name: "Old".to_string(),
protocol: 0,
},
description: mc_types::Chat {
text: "Server Error (Server may be starting)"
.to_string() + "\nPurple Cello Server",
},
players: StatusPlayers {
max: 0,
online: 0,
sample: None,
},
favicon: favicon,
enforcesSecureChat: Some(false),
previewsChat: Some(false),
},
};
let json = serde_json::to_string(&status_response)?;
let mut out_data: Vec<u8> = vec![0];
out_data.append(&mut mc_types::convert_string(&json));
mc_types::write_data(client_writer, &mut out_data).await?;
} else if packet_id == 0x01 {
println!("Handling Ping");
let mut out_data: Vec<u8> = vec![1];
out_data.append(&mut data);
mc_types::write_data(client_writer, &mut out_data).await?;
break;
} else {
break;
impl StatusPackets {
pub async fn read(stream: &mut OwnedReadHalf) -> Result<Self> {
let mut data = mc_types::read_data(stream).await?;
let packet_id = mc_types::get_var_int(&mut data)?;
if packet_id == Status::packet_id() {
return Ok(Self::Status(Status::get(&mut data)?))
} else if packet_id == Ping::packet_id() {
return Ok(Self::Ping(Ping::get(&mut data)?))
} else {
return Err(Box::new(PacketError::InvalidPacketId))
}
}
}
Ok(())
}
pub async fn get_upstream_status(
server_reader: &mut OwnedReadHalf,
server_writer: &mut OwnedWriteHalf,
) -> Result<StatusResponseData> {
handshake::serverbound::Handshake{
protocol_version: mc_types::VERSION_PROTOCOL,
server_address: "localhost".to_string(),
server_port: 25565,
next_state: 1,
}.write(server_writer).await?;
mc_types::write_data(server_writer, &mut vec![0]).await?;
let mut data = mc_types::read_data(server_reader).await?;
mc_types::get_u8(&mut data);
let json = mc_types::get_string(&mut data)?;
let status_response: StatusResponseData = serde_json::from_str(&json)?;
// let mut out_data: Vec<u8> = vec![1];
// out_data.append(&mut mc_types::convert_i64(0));
// mc_types::write_packet(server_writer, &mut out_data).await?;
Ok(status_response)
}
pub async fn respond_legacy_status(
client_writer: &mut OwnedWriteHalf,
) -> Result<()> {
println!("Old Style Status");
client_writer.write_u8(0xFF).await?;
let s = "§1\0127\0".to_string() +
mc_types::VERSION_NAME +
"\0YTD Proxy§0§10";
let utf16_vec: Vec<u16> = s
.encode_utf16()
.flat_map(|c| std::iter::once(c).chain(std::iter::once(0)))
.collect();
client_writer.write_u16((utf16_vec.len() / 2) as u16).await?;
for utf16_char in utf16_vec {
client_writer.write_u16(utf16_char).await?;
pub struct Status {
pub response: String
}
impl Status {
pub fn from_json(data: StatusResponseData) -> Result<Self> {
Ok(Self {
response: serde_json::to_string(&data)?
})
}
pub fn get_json(&self) -> Result<StatusResponseData> {
Ok(serde_json::from_str(&self.response)?)
}
}
impl Packet for Status {
fn packet_id() -> i32 {0}
fn get(mut data: &mut Vec<u8>) -> Result<Self> {
Ok(Self {
response: mc_types::get_string(&mut data)?
})
}
fn convert(&self) -> Vec<u8> {
let mut data: Vec<u8> = vec![];
data.append(&mut mc_types::convert_var_int(Self::packet_id()));
data.append(&mut mc_types::convert_string(&self.response));
data
}
}
pub struct Ping {
pub payload: i64
}
impl Packet for Ping {
fn packet_id() -> i32 {1}
fn get(mut data: &mut Vec<u8>) -> Result<Self> {
Ok(Self {
payload: mc_types::get_i64(&mut data)
})
}
fn convert(&self) -> Vec<u8> {
let mut data: Vec<u8> = vec![];
data.append(&mut mc_types::convert_var_int(Self::packet_id()));
data.append(&mut mc_types::convert_i64(self.payload));
data
}
}
}
pub mod serverbound {
use tokio::net::tcp::OwnedReadHalf;
use crate::mc_types::{self, Result, Packet, PacketError};
pub enum StatusPackets {
Status(Status),
Ping(Ping),
}
impl StatusPackets {
pub async fn read(stream: &mut OwnedReadHalf) -> Result<Self> {
let mut data = mc_types::read_data(stream).await?;
let packet_id = mc_types::get_var_int(&mut data)?;
if packet_id == Status::packet_id() {
return Ok(Self::Status(Status::get(&mut data)?))
} else if packet_id == Ping::packet_id() {
return Ok(Self::Ping(Ping::get(&mut data)?))
} else {
return Err(Box::new(PacketError::InvalidPacketId))
}
}
}
pub struct Status {}
impl Packet for Status {
fn packet_id() -> i32 {0}
fn get(mut data: &mut Vec<u8>) -> Result<Self> {
Ok(Self {})
}
fn convert(&self) -> Vec<u8> {
let mut data: Vec<u8> = vec![];
data.append(&mut mc_types::convert_var_int(Self::packet_id()));
data
}
}
pub struct Ping {
pub payload: i64
}
impl Packet for Ping {
fn packet_id() -> i32 {1}
fn get(mut data: &mut Vec<u8>) -> Result<Self> {
Ok(Self {
payload: mc_types::get_i64(&mut data)
})
}
fn convert(&self) -> Vec<u8> {
let mut data: Vec<u8> = vec![];
data.append(&mut mc_types::convert_var_int(Self::packet_id()));
data.append(&mut mc_types::convert_i64(self.payload));
data
}
}
Ok(())
}

215
src/status_handle.rs Normal file
View File

@ -0,0 +1,215 @@
// Yeahbut December 2023
use std::fs::{self, File};
use std::io::Read;
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::io::AsyncWriteExt;
use serde_json::Value;
use base64::{Engine as _, engine::general_purpose};
use rand::Rng;
use crate::mc_types::{self, Result, Packet};
use crate::status;
use crate::handshake;
async fn online_players(
server_reader: &mut OwnedReadHalf,
server_writer: &mut OwnedWriteHalf,
) -> Result<status::clientbound::StatusPlayers> {
Ok(get_upstream_status(server_reader, server_writer).await?.players)
}
fn motd() -> String {
let default = "A Minecraft Server Proxy".to_string();
let file_path = "./motd.json";
let data = match fs::read_to_string(file_path) {
Ok(data) => data,
Err(_) => return default,
};
let motd_data: Value = match serde_json::from_str(&data) {
Ok(value) => value,
Err(_) => return default,
};
let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len());
let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len());
if length1 == 0 || length2 == 0 {
return default;
}
let mut rng = rand::thread_rng();
let rand1 = rng.gen_range(0..length1) as usize;
let rand2 = rng.gen_range(0..length2) as usize;
let line1: &str = match motd_data["line1"][rand1].as_str() {
Some(s) => s,
None => return default,
};
// TODO: Birthdays, Holidays, and Announcements
let line2: &str = match motd_data["line2"][rand2].as_str() {
Some(s) => s,
None => return default,
};
let line: String = format!("{}\n{}", line1, line2);
line
}
fn favicon() -> Option<String> {
let file_path = "./main_icon.png";
let mut file = match File::open(file_path) {
Ok(file) => file,
Err(_) => return None,
};
let mut buffer = Vec::new();
if let Err(_) = file.read_to_end(&mut buffer) {
return None
};
let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer);
let full_string: String =
format!("data:image/png;base64,{}", base64_string);
Some(full_string)
}
pub async fn respond_status(
client_reader: &mut OwnedReadHalf,
client_writer: &mut OwnedWriteHalf,
server_reader: &mut Option<OwnedReadHalf>,
server_writer: &mut Option<OwnedWriteHalf>,
)-> Result<()> {
loop {
println!("Status Handling");
let packet =
status::serverbound::StatusPackets::read(client_reader).await?;
match packet {
status::serverbound::StatusPackets::Status(_) => {
println!("Handling Status");
let favicon = favicon();
let online_players = match server_reader {
Some(server_reader) => match server_writer {
Some(server_writer) => match online_players(
server_reader,
server_writer,
).await {
Ok(value) => Some(value),
Err(_) => None,
},
None => None,
},
None => None,
};
let status_response =
match online_players {
Some(online_players) =>
status::clientbound::StatusResponseData {
version: status::clientbound::StatusVersion {
name: mc_types::VERSION_NAME.to_string(),
protocol: mc_types::VERSION_PROTOCOL,
},
description: mc_types::Chat {
text: motd(),
},
players: status::clientbound::StatusPlayers {
max: -13,
online: online_players.online,
sample: online_players.sample,
},
favicon: favicon,
enforcesSecureChat: Some(false),
previewsChat: Some(false),
},
None => status::clientbound::StatusResponseData {
version: status::clientbound::StatusVersion {
name: "Old".to_string(),
protocol: 0,
},
description: mc_types::Chat {
text: "Server Error (Server may be starting)"
.to_string() + "\nPurple Cello Server",
},
players: status::clientbound::StatusPlayers {
max: 0,
online: 0,
sample: None,
},
favicon: favicon,
enforcesSecureChat: Some(false),
previewsChat: Some(false),
},
};
let packet =
status::clientbound::Status::from_json(status_response)?;
packet.write(client_writer).await?;
},
status::serverbound::StatusPackets::Ping(packet) => {
println!("Handling Ping");
let new_packet = status::clientbound::Ping{
payload: packet.payload,
};
new_packet.write(client_writer).await?;
break;
}
}
}
Ok(())
}
pub async fn get_upstream_status(
server_reader: &mut OwnedReadHalf,
server_writer: &mut OwnedWriteHalf,
) -> Result<status::clientbound::StatusResponseData> {
handshake::serverbound::Handshake{
protocol_version: mc_types::VERSION_PROTOCOL,
server_address: "localhost".to_string(),
server_port: 25565,
next_state: 1,
}.write(server_writer).await?;
mc_types::write_data(server_writer, &mut vec![0]).await?;
let mut data = mc_types::read_data(server_reader).await?;
mc_types::get_u8(&mut data);
let json = mc_types::get_string(&mut data)?;
let status_response: status::clientbound::StatusResponseData =
serde_json::from_str(&json)?;
// let mut out_data: Vec<u8> = vec![1];
// out_data.append(&mut mc_types::convert_i64(0));
// mc_types::write_packet(server_writer, &mut out_data).await?;
Ok(status_response)
}
pub async fn respond_legacy_status(
client_writer: &mut OwnedWriteHalf,
) -> Result<()> {
println!("Old Style Status");
client_writer.write_u8(0xFF).await?;
let s = "§1\0127\0".to_string() +
mc_types::VERSION_NAME +
"\0YTD Proxy§0§10";
let utf16_vec: Vec<u16> = s
.encode_utf16()
.flat_map(|c| std::iter::once(c).chain(std::iter::once(0)))
.collect();
client_writer.write_u16((utf16_vec.len() / 2) as u16).await?;
for utf16_char in utf16_vec {
client_writer.write_u16(utf16_char).await?;
}
Ok(())
}