replace most .expects and .unwraps with a custom error type, closes #54

This commit is contained in:
Zoey 2024-04-25 00:01:28 -07:00
parent b749eb838b
commit 373fc5889d
No known key found for this signature in database
GPG key ID: 8611B896D1AAFAF2
8 changed files with 82 additions and 57 deletions

1
Cargo.lock generated
View file

@ -127,6 +127,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum",
"thiserror",
"tokio", "tokio",
] ]

View file

@ -18,4 +18,5 @@ safer-bytes = "0.2"
serde = {version = "1", features = ["derive"]} serde = {version = "1", features = ["derive"]}
serde_json = "1" serde_json = "1"
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
thiserror = "1"
tokio = {version = "1", features = ["full"]} tokio = {version = "1", features = ["full"]}

12
src/error.rs Normal file
View file

@ -0,0 +1,12 @@
/// error type for the server
#[derive(Debug, thiserror::Error)]
pub enum GeneralError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error("{0}")]
Custom(String),
#[error("{0}")]
CustomPrivate(String),
}

View file

@ -6,7 +6,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{packet::server::ServerPacket, util::neighbors}; use crate::{error::GeneralError, packet::server::ServerPacket, util::neighbors};
use self::block::BLOCK_INFO; use self::block::BLOCK_INFO;
@ -108,7 +108,7 @@ impl Level {
} }
/// saves the level /// saves the level
pub async fn save<P>(&self, path: P) -> std::io::Result<()> pub async fn save<P>(&self, path: P) -> Result<(), GeneralError>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@ -116,29 +116,22 @@ impl Level {
tokio::fs::create_dir_all(path).await?; tokio::fs::create_dir_all(path).await?;
tokio::fs::write( tokio::fs::write(
path.join(LEVEL_INFO_PATH), path.join(LEVEL_INFO_PATH),
serde_json::to_string_pretty(self).unwrap(), serde_json::to_string_pretty(self)?,
) )
.await?; .await?;
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best()); let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best());
encoder encoder.write_all(&self.blocks)?;
.write_all(&self.blocks) Ok(tokio::fs::write(path.join(LEVEL_DATA_PATH), encoder.finish()?).await?)
.expect("failed to write blocks");
tokio::fs::write(
path.join(LEVEL_DATA_PATH),
encoder.finish().expect("failed to encode blocks"),
)
.await
} }
/// loads the level /// loads the level
pub async fn load<P>(path: P) -> std::io::Result<Self> pub async fn load<P>(path: P) -> Result<Self, GeneralError>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let path = path.as_ref(); let path = path.as_ref();
let mut info: Self = let mut info: Self =
serde_json::from_str(&tokio::fs::read_to_string(path.join(LEVEL_INFO_PATH)).await?) serde_json::from_str(&tokio::fs::read_to_string(path.join(LEVEL_INFO_PATH)).await?)?;
.expect("failed to deserialize level info");
let blocks_data = tokio::fs::read(path.join(LEVEL_DATA_PATH)).await?; let blocks_data = tokio::fs::read(path.join(LEVEL_DATA_PATH)).await?;
let mut decoder = flate2::read::GzDecoder::new(blocks_data.as_slice()); let mut decoder = flate2::read::GzDecoder::new(blocks_data.as_slice());
decoder.read_to_end(&mut info.blocks)?; decoder.read_to_end(&mut info.blocks)?;

View file

@ -2,12 +2,14 @@
use std::path::PathBuf; use std::path::PathBuf;
use error::GeneralError;
use server::{ use server::{
config::{OptionalServerConfig, ServerConfig}, config::{OptionalServerConfig, ServerConfig},
Server, Server,
}; };
mod command; mod command;
mod error;
mod level; mod level;
mod packet; mod packet;
mod player; mod player;
@ -18,11 +20,10 @@ const SERVER_NAME: &str = "classics";
const CONFIG_FILE: &str = "./server-config.json"; const CONFIG_FILE: &str = "./server-config.json";
#[tokio::main] #[tokio::main]
async fn main() -> std::io::Result<()> { async fn main() -> Result<(), GeneralError> {
let config_path = PathBuf::from(CONFIG_FILE); let config_path = PathBuf::from(CONFIG_FILE);
let config = if config_path.exists() { let config = if config_path.exists() {
serde_json::from_str::<OptionalServerConfig>(&std::fs::read_to_string(&config_path)?) serde_json::from_str::<OptionalServerConfig>(&std::fs::read_to_string(&config_path)?)?
.expect("failed to deserialize config")
.build_default() .build_default()
} else { } else {
ServerConfig::default() ServerConfig::default()

View file

@ -6,6 +6,7 @@ use std::{path::PathBuf, sync::Arc};
use tokio::{net::TcpListener, sync::RwLock}; use tokio::{net::TcpListener, sync::RwLock};
use crate::{ use crate::{
error::GeneralError,
level::{ level::{
block::{BlockType, BLOCK_INFO}, block::{BlockType, BLOCK_INFO},
BlockUpdate, Level, BlockUpdate, Level,
@ -58,7 +59,7 @@ impl ServerData {
impl Server { impl Server {
/// creates a new server with a generated level /// creates a new server with a generated level
pub async fn new(config: ServerConfig) -> std::io::Result<Self> { pub async fn new(config: ServerConfig) -> Result<Self, GeneralError> {
let levels_path = PathBuf::from(LEVELS_PATH); let levels_path = PathBuf::from(LEVELS_PATH);
if !levels_path.exists() { if !levels_path.exists() {
std::fs::create_dir_all(&levels_path)?; std::fs::create_dir_all(&levels_path)?;
@ -84,7 +85,7 @@ impl Server {
} }
/// creates a new server with the given level /// creates a new server with the given level
pub async fn new_with_level(config: ServerConfig, level: Level) -> std::io::Result<Self> { pub async fn new_with_level(config: ServerConfig, level: Level) -> Result<Self, GeneralError> {
let listener = TcpListener::bind("0.0.0.0:25565").await?; let listener = TcpListener::bind("0.0.0.0:25565").await?;
Ok(Self { Ok(Self {
@ -101,11 +102,15 @@ impl Server {
} }
/// starts the server /// starts the server
pub async fn run(self) -> std::io::Result<()> { pub async fn run(self) -> Result<(), GeneralError> {
let data = self.data.clone(); let data = self.data.clone();
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
let (stream, addr) = self.listener.accept().await.unwrap(); let (stream, addr) = self
.listener
.accept()
.await
.expect("failed to accept listener!");
println!("connection from {addr}"); println!("connection from {addr}");
let data = data.clone(); let data = data.clone();
tokio::spawn(async move { tokio::spawn(async move {
@ -113,7 +118,7 @@ impl Server {
}); });
} }
}); });
handle_ticks(self.data.clone()).await; handle_ticks(self.data.clone()).await?;
tokio::time::sleep(std::time::Duration::from_millis(1)).await; tokio::time::sleep(std::time::Duration::from_millis(1)).await;
// TODO: cancel pending tasks/send out "Server is stopping" messages *here* instead of elsewhere // TODO: cancel pending tasks/send out "Server is stopping" messages *here* instead of elsewhere
@ -129,7 +134,7 @@ impl Server {
} }
/// function to tick the server /// function to tick the server
async fn handle_ticks(data: Arc<RwLock<ServerData>>) { async fn handle_ticks(data: Arc<RwLock<ServerData>>) -> Result<(), GeneralError> {
let mut current_tick = 0; let mut current_tick = 0;
let mut last_auto_save = std::time::Instant::now(); let mut last_auto_save = std::time::Instant::now();
loop { loop {
@ -138,12 +143,7 @@ async fn handle_ticks(data: Arc<RwLock<ServerData>>) {
tick(&mut data, current_tick); tick(&mut data, current_tick);
if data.config_needs_saving { if data.config_needs_saving {
std::fs::write( tokio::fs::write(CONFIG_FILE, serde_json::to_string_pretty(&data.config)?).await?;
CONFIG_FILE,
serde_json::to_string_pretty(&data.config)
.expect("failed to serialize default config"),
)
.expect("failed to save config file");
data.config_needs_saving = false; data.config_needs_saving = false;
} }
@ -164,8 +164,7 @@ async fn handle_ticks(data: Arc<RwLock<ServerData>>) {
data.level.save_now = false; data.level.save_now = false;
data.level data.level
.save(PathBuf::from(LEVELS_PATH).join(&data.config.level_name)) .save(PathBuf::from(LEVELS_PATH).join(&data.config.level_name))
.await .await?;
.expect("failed to autosave level");
last_auto_save = std::time::Instant::now(); last_auto_save = std::time::Instant::now();
let packet = ServerPacket::Message { let packet = ServerPacket::Message {
@ -181,6 +180,8 @@ async fn handle_ticks(data: Arc<RwLock<ServerData>>) {
current_tick = current_tick.wrapping_add(1); current_tick = current_tick.wrapping_add(1);
tokio::time::sleep(TICK_DURATION).await; tokio::time::sleep(TICK_DURATION).await;
} }
Ok(())
} }
/// function which ticks the server once /// function which ticks the server once

View file

@ -13,6 +13,7 @@ use tokio::{
use crate::{ use crate::{
command::Command, command::Command,
error::GeneralError,
level::{block::BLOCK_INFO, BlockUpdate, Level}, level::{block::BLOCK_INFO, BlockUpdate, Level},
packet::{ packet::{
client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH, client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH,
@ -24,7 +25,7 @@ use crate::{
use super::ServerData; use super::ServerData;
async fn next_packet(stream: &mut TcpStream) -> std::io::Result<Option<ClientPacket>> { async fn next_packet(stream: &mut TcpStream) -> Result<Option<ClientPacket>, GeneralError> {
let id = stream.read_u8().await?; let id = stream.read_u8().await?;
if let Some(size) = ClientPacket::get_size_from_id(id) { if let Some(size) = ClientPacket::get_size_from_id(id) {
@ -37,7 +38,7 @@ async fn next_packet(stream: &mut TcpStream) -> std::io::Result<Option<ClientPac
} }
} }
async fn write_packets<I>(stream: &mut TcpStream, packets: I) -> std::io::Result<()> async fn write_packets<I>(stream: &mut TcpStream, packets: I) -> Result<(), GeneralError>
where where
I: Iterator<Item = ServerPacket>, I: Iterator<Item = ServerPacket>,
{ {
@ -84,9 +85,12 @@ pub(super) async fn handle_stream(
let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await; let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await;
println!("{addr} is no longer connected"); println!("{addr} is no longer connected");
match r { if let Err(e) = r {
Ok(disconnect_reason) => { match e {
if let Some(disconnect_reason) = disconnect_reason { // unexpected eof is expected when clients disconnect
GeneralError::Io(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {}
GeneralError::Custom(disconnect_reason) => {
println!("disconnecting <{addr}> for reason: {disconnect_reason}");
let packet = ServerPacket::DisconnectPlayer { disconnect_reason }; let packet = ServerPacket::DisconnectPlayer { disconnect_reason };
let writer = PacketWriter::default().write_u8(packet.get_id()); let writer = PacketWriter::default().write_u8(packet.get_id());
let msg = packet.write(writer).into_raw_packet(); let msg = packet.write(writer).into_raw_packet();
@ -94,11 +98,8 @@ pub(super) async fn handle_stream(
eprintln!("Failed to write disconnect packet for <{addr}>: {e}"); eprintln!("Failed to write disconnect packet for <{addr}>: {e}");
} }
} }
} _ => {
Err(e) => { eprintln!("Error in stream handler for <{addr}>: {e:?}");
// unexpected eof is expected when clients disconnect
if e.kind() != std::io::ErrorKind::UnexpectedEof {
eprintln!("Error in stream handler for <{addr}>: {e}")
} }
} }
} }
@ -129,7 +130,7 @@ async fn handle_stream_inner(
addr: SocketAddr, addr: SocketAddr,
data: Arc<RwLock<ServerData>>, data: Arc<RwLock<ServerData>>,
own_id: &mut i8, own_id: &mut i8,
) -> std::io::Result<Option<String>> { ) -> Result<(), GeneralError> {
let mut reply_queue: Vec<ServerPacket> = Vec::new(); let mut reply_queue: Vec<ServerPacket> = Vec::new();
macro_rules! msg { macro_rules! msg {
@ -144,7 +145,7 @@ async fn handle_stream_inner(
loop { loop {
if let Some(player) = data.read().await.players.iter().find(|p| p.id == *own_id) { if let Some(player) = data.read().await.players.iter().find(|p| p.id == *own_id) {
if let Some(msg) = &player.should_be_kicked { if let Some(msg) = &player.should_be_kicked {
return Ok(Some(format!("Kicked: {msg}"))); return Err(GeneralError::Custom(msg.clone()));
} }
} }
@ -157,7 +158,7 @@ async fn handle_stream_inner(
magic_number, magic_number,
} => { } => {
if protocol_version != 0x07 { if protocol_version != 0x07 {
return Ok(Some("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string())); return Err(GeneralError::Custom("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string()));
} }
let zero = f16::from_f32(0.0); let zero = f16::from_f32(0.0);
@ -168,7 +169,9 @@ async fn handle_stream_inner(
ServerProtectionMode::None => {} ServerProtectionMode::None => {}
ServerProtectionMode::Password(password) => { ServerProtectionMode::Password(password) => {
if verification_key != *password { if verification_key != *password {
return Ok(Some("Incorrect password!".to_string())); return Err(GeneralError::Custom(
"Incorrect password!".to_string(),
));
} }
} }
ServerProtectionMode::PasswordsByUser(passwords) => { ServerProtectionMode::PasswordsByUser(passwords) => {
@ -177,14 +180,18 @@ async fn handle_stream_inner(
.map(|password| verification_key == *password) .map(|password| verification_key == *password)
.unwrap_or_default() .unwrap_or_default()
{ {
return Ok(Some("Incorrect password!".to_string())); return Err(GeneralError::Custom(
"Incorrect password!".to_string(),
));
} }
} }
} }
for player in &data.players { for player in &data.players {
if player.username == username { if player.username == username {
return Ok(Some("Player with username already connected!".to_string())); return Err(GeneralError::Custom(
"Player with username already connected!".to_string(),
));
} }
} }
@ -232,7 +239,7 @@ async fn handle_stream_inner(
println!("generating level packets"); println!("generating level packets");
reply_queue.extend( reply_queue.extend(
build_level_packets(&data.level, extensions, custom_blocks_support_level) build_level_packets(&data.level, extensions, custom_blocks_support_level)?
.into_iter(), .into_iter(),
); );
@ -321,7 +328,9 @@ async fn handle_stream_inner(
|| y.clamp(0, data.level.y_size as i16 - 1) != y || y.clamp(0, data.level.y_size as i16 - 1) != y
|| z.clamp(0, data.level.z_size as i16 - 1) != z || z.clamp(0, data.level.z_size as i16 - 1) != z
{ {
return Ok(Some("Attempt to place block out of bounds".to_string())); return Err(GeneralError::Custom(
"Attempt to place block out of bounds".to_string(),
));
} }
let new_block_info = BLOCK_INFO.get(&block_type); let new_block_info = BLOCK_INFO.get(&block_type);
@ -429,7 +438,7 @@ async fn handle_stream_inner(
ClientPacket::Extended(_packet) => { ClientPacket::Extended(_packet) => {
// extended packets! // extended packets!
return Ok(Some( return Err(GeneralError::Custom(
"Unexpected extension packet in this phase!".to_string(), "Unexpected extension packet in this phase!".to_string(),
)); ));
// match packet { // match packet {
@ -468,7 +477,7 @@ fn build_level_packets(
level: &Level, level: &Level,
extensions: ExtBitmask, extensions: ExtBitmask,
custom_blocks_support_level: u8, custom_blocks_support_level: u8,
) -> Vec<ServerPacket> { ) -> Result<Vec<ServerPacket>, GeneralError> {
let mut packets: Vec<ServerPacket> = vec![ServerPacket::LevelInitialize {}]; let mut packets: Vec<ServerPacket> = vec![ServerPacket::LevelInitialize {}];
let custom_blocks = let custom_blocks =
@ -490,8 +499,8 @@ fn build_level_packets(
})); }));
let mut e = GzEncoder::new(Vec::new(), Compression::best()); let mut e = GzEncoder::new(Vec::new(), Compression::best());
e.write_all(&data).expect("failed to gzip level data"); e.write_all(&data)?;
let data = e.finish().expect("failed to gzip level data"); let data = e.finish()?;
let data_len = data.len(); let data_len = data.len();
let mut total_bytes = 0; let mut total_bytes = 0;
@ -513,5 +522,5 @@ fn build_level_packets(
z_size: level.z_size as i16, z_size: level.z_size as i16,
}); });
packets Ok(packets)
} }

View file

@ -1,6 +1,7 @@
use tokio::net::TcpStream; use tokio::net::TcpStream;
use crate::{ use crate::{
error::GeneralError,
level::block::CUSTOM_BLOCKS_SUPPORT_LEVEL, level::block::CUSTOM_BLOCKS_SUPPORT_LEVEL,
packet::{ packet::{
client::ClientPacket, client_extended::ExtendedClientPacket, server::ServerPacket, client::ClientPacket, client_extended::ExtendedClientPacket, server::ServerPacket,
@ -10,7 +11,9 @@ use crate::{
use super::{next_packet, write_packets}; use super::{next_packet, write_packets};
pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result<(ExtBitmask, u8)> { pub async fn get_supported_extensions(
stream: &mut TcpStream,
) -> Result<(ExtBitmask, u8), GeneralError> {
let extensions = ExtBitmask::all().all_contained_info(); let extensions = ExtBitmask::all().all_contained_info();
write_packets( write_packets(
@ -39,7 +42,9 @@ pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result
{ {
client_extensions.push(ExtInfo::new(ext_name, version, ExtBitmask::none())); client_extensions.push(ExtInfo::new(ext_name, version, ExtBitmask::none()));
} else { } else {
panic!("expected ExtEntry packet!"); return Err(GeneralError::Custom(
"expected ExtEntry packet!".to_string(),
));
} }
} }
client_extensions.retain_mut(|cext| { client_extensions.retain_mut(|cext| {
@ -76,7 +81,9 @@ pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result
{ {
support_level.min(CUSTOM_BLOCKS_SUPPORT_LEVEL) support_level.min(CUSTOM_BLOCKS_SUPPORT_LEVEL)
} else { } else {
panic!("expected CustomBlockSupportLevel packet!"); return Err(GeneralError::Custom(
"expected CustomBlockSupportLevel packet!".to_string(),
));
} }
} else { } else {
0 0