From 4849a7903d6f9702be0ceabb692d18515e05da36 Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Tue, 19 Apr 2022 13:05:17 -0600 Subject: [PATCH] Improve configuration file parsing, add log_level to config, log server version, and move mctypes into crate::net --- Cargo.lock | 10 +++ Cargo.toml | 2 + build.rs | 9 ++ composition.toml | 1 + src/config.rs | 138 +++++++++++++++++++++++++++++++ src/lib.rs | 48 ++--------- src/main.rs | 2 +- src/{ => net}/mctypes/mod.rs | 0 src/{ => net}/mctypes/numbers.rs | 0 src/{ => net}/mctypes/other.rs | 0 src/net/mod.rs | 2 + src/net/packets.rs | 2 +- 12 files changed, 170 insertions(+), 44 deletions(-) create mode 100644 build.rs create mode 100644 src/config.rs rename src/{ => net}/mctypes/mod.rs (100%) rename src/{ => net}/mctypes/numbers.rs (100%) rename src/{ => net}/mctypes/other.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 1454396..167621e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ "radix64", "serde", "serde_json", + "substring", "tokio", "toml", "uuid", @@ -539,6 +540,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + [[package]] name = "syn" version = "1.0.91" diff --git a/Cargo.toml b/Cargo.toml index d5db07c..376e41a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" license = "MIT" name = "composition" version = "0.1.0" +build = "build.rs" [dependencies] async-trait = "0.1.48" @@ -21,6 +22,7 @@ serde_json = "1.0.59" tokio = {version = "1", features = ["full"]} toml = "0.5" uuid = "0.8.2" +substring = "1.4.5" # colorful = "0.2.1" # ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e0af124 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ +use std::process::Command; +fn main() { + if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() { + let git_hash = String::from_utf8_lossy(&output.stdout).to_string(); + println!("cargo:rustc-env=GIT_HASH={}", git_hash); + } else { + println!("cargo:rustc-env=GIT_HASH=00000000"); + } +} diff --git a/composition.toml b/composition.toml index 4e50547..36290ef 100644 --- a/composition.toml +++ b/composition.toml @@ -2,3 +2,4 @@ favicon = "server-icon.png" max_players = 20 motd = "Hello world!" port = 25565 +log_level = "debug" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2d6ec02 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,138 @@ +use crate::prelude::*; + +pub struct Config { + pub port: u16, + pub max_players: usize, + pub motd: String, + pub favicon: String, + pub server_string: String, + pub log_level: log::LevelFilter, + pub server_version: String, +} +impl Default for Config { + fn default() -> Self { + let server_version = format!( + "composition/{} ({})", + env!("CARGO_PKG_VERSION"), + env!("GIT_HASH").substring(0, 8) + ); + Config { + port: 25565, + max_players: 20, + motd: "Hello world!".to_owned(), + favicon: "server-icon.png".to_owned(), + server_string: server_version.clone(), + log_level: if cfg!(debug_assertions) { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + }, + server_version, + } + } +} +impl Config { + pub fn from_file(filename: &str) -> Config { + let read_file = |filename: &str| -> std::io::Result { + use std::{fs::File, io::prelude::*}; + let mut data = String::new(); + let mut file = File::open(filename)?; + file.read_to_string(&mut data)?; + Ok(data) + }; + if let Ok(config) = read_file(filename) { + Config::parse(&config) + } else { + Config::default() + } + } + pub fn parse(data: &str) -> Config { + let mut config = Config::default(); + + let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String { + if let Some(s) = cfg.get(field) { + if let Some(s) = s.as_str() { + return s.to_owned(); + } else { + warn!("{}", error); + } + } else { + warn!("{}", error); + } + default.to_owned() + }; + + if let Ok(cfg) = data.parse::() { + if let Some(&toml::Value::Integer(port)) = cfg.get("port") { + if port < u16::MIN as i64 || port > u16::MAX as i64 { + warn!("Config port must be an integer in the range of {}-{}, using default port: {}", u16::MIN, u16::MAX, config.port); + } else { + config.port = port as u16; + } + } else { + warn!( + "Config port must be an integer in the range of {}-{}, using default port: {}", + u16::MIN, + u16::MAX, + config.port + ); + } + + if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") { + if max_players < 0 { + warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); + } else { + config.max_players = max_players as usize; + } + } else { + warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); + } + + config.motd = get_string( + &cfg, + "motd", + &config.motd, + &format!( + "Config motd must be a string, using default motd: \"{}\"", + config.motd + ), + ); + config.favicon = get_string( + &cfg, + "favicon", + &config.favicon, + &format!( + "Config favicon must be a string, using default favicon: \"{}\"", + config.favicon + ), + ); + let default_log_level = format!("{}", config.log_level).to_ascii_lowercase(); + config.log_level = match &get_string( + &cfg, + "log_level", + &default_log_level, + &format!( + "Config log_level must be a string, using default log_level: {}", + default_log_level + ), + )[..] + { + "off" => log::LevelFilter::Off, + "error" => log::LevelFilter::Error, + "warn" => log::LevelFilter::Warn, + "info" => log::LevelFilter::Info, + "debug" => log::LevelFilter::Debug, + "trace" => log::LevelFilter::Trace, + _ => { + warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace"); + config.log_level + } + }; + + config + } else { + warn!("Could not parse configuration file, using default"); + config + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6729dce..6dc5b09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,50 +1,16 @@ #[macro_use] extern crate lazy_static; -pub mod mctypes; +pub mod config; pub mod net; pub mod server; use crate::prelude::*; use std::sync::mpsc::{self, Receiver}; -#[derive(Serialize, Deserialize)] -pub struct Config { - pub port: u16, - pub max_players: usize, - pub motd: String, - pub favicon: String, -} - pub static PROTOCOL_VERSION: i32 = 757; lazy_static! { - pub static ref CONFIG: Config = { - let config_from_file = || -> std::io::Result { - use std::{fs::File, io::prelude::*}; - let mut data = String::new(); - let mut file = File::open("composition.toml")?; - file.read_to_string(&mut data)?; - if let Ok(c) = toml::from_str::(&data) { - Ok(c) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Could not parse toml", - )) - } - }; - if let Ok(c) = config_from_file() { - c - } else { - warn!("Could not load config from file, using default"); - Config { - port: 25565, - max_players: 20, - motd: "Hello world!".to_owned(), - favicon: "server-icon.png".to_owned(), - } - } - }; + pub static ref CONFIG: Config = Config::from_file("composition.toml"); pub static ref FAVICON: std::io::Result> = { use std::{fs::File, io::prelude::*}; let mut data = vec![]; @@ -70,15 +36,12 @@ pub fn init() -> Receiver<()> { message = message, )) }) - .level(if cfg!(debug_assertions) { - log::LevelFilter::Debug - } else { - log::LevelFilter::Info - }) + .level(log::LevelFilter::Trace) .chain(std::io::stdout()) .chain(fern::log_file("output.log").unwrap()) .apply() .unwrap(); + log::set_max_level(CONFIG.log_level); // Set up the ctrl-c handler. let (ctrlc_tx, ctrlc_rx) = mpsc::channel(); ctrlc::set_handler(move || { @@ -94,7 +57,7 @@ pub async fn start_server() -> server::Server { } pub mod prelude { - pub use crate::{mctypes::*, CONFIG, FAVICON, PROTOCOL_VERSION, START_TIME}; + pub use crate::{config::Config, CONFIG, FAVICON, PROTOCOL_VERSION, START_TIME}; pub use log::*; pub use serde::{Deserialize, Serialize}; pub use serde_json::json; @@ -102,6 +65,7 @@ pub mod prelude { pub type JSON = serde_json::Value; pub type NBT = fastnbt::Value; pub use std::collections::VecDeque; + pub use substring::Substring; pub use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[derive(Clone, Debug, PartialEq)] pub enum ParseError { diff --git a/src/main.rs b/src/main.rs index e7b3b7c..b65af79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::time::Duration; #[tokio::main] pub async fn main() { let ctrlc_rx = composition::init(); - info!("Starting server..."); + info!("Starting {}", composition::CONFIG.server_version); let mut server = composition::start_server().await; info!("Done! Start took {:?}", composition::START_TIME.elapsed()); diff --git a/src/mctypes/mod.rs b/src/net/mctypes/mod.rs similarity index 100% rename from src/mctypes/mod.rs rename to src/net/mctypes/mod.rs diff --git a/src/mctypes/numbers.rs b/src/net/mctypes/numbers.rs similarity index 100% rename from src/mctypes/numbers.rs rename to src/net/mctypes/numbers.rs diff --git a/src/mctypes/other.rs b/src/net/mctypes/other.rs similarity index 100% rename from src/mctypes/other.rs rename to src/net/mctypes/other.rs diff --git a/src/net/mod.rs b/src/net/mod.rs index 7f1856c..66fba37 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,6 +1,8 @@ +pub mod mctypes; pub mod packets; use crate::prelude::*; +use mctypes::*; pub use packets::Packet; use std::time::Instant; use tokio::net::TcpStream; diff --git a/src/net/packets.rs b/src/net/packets.rs index 5cd382d..d6351e1 100644 --- a/src/net/packets.rs +++ b/src/net/packets.rs @@ -1,4 +1,4 @@ -use super::NetworkClientState; +use super::{mctypes::*, NetworkClientState}; use crate::prelude::*; #[derive(Clone, PartialEq, Debug)]