From 7b63542810d2e703c808a393eee06108f5076dc9 Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Wed, 4 Dec 2024 21:39:58 -0700 Subject: [PATCH] Add proxy stub --- Cargo.lock | 7 ---- Cargo.toml | 12 ++++--- src/config.rs | 77 ++++++++++++++++++++++++++++++++----------- src/lib.rs | 10 +++++- src/protocol/error.rs | 4 --- src/proxy/config.rs | 46 ++++++++++++++++++++++++++ src/proxy/mod.rs | 71 +++++++++++++++++++++++++++++++++++++++ src/server/config.rs | 13 ++++++-- src/server/mod.rs | 13 +++++--- src/world/error.rs | 2 -- src/world/mod.rs | 6 ++-- 11 files changed, 215 insertions(+), 46 deletions(-) create mode 100644 src/proxy/config.rs create mode 100644 src/proxy/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c6c2435..d3c385f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,12 +66,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "anyhow" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" - [[package]] name = "async-trait" version = "0.1.83" @@ -178,7 +172,6 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" name = "composition" version = "0.1.0" dependencies = [ - "anyhow", "async-trait", "base64", "clap", diff --git a/Cargo.toml b/Cargo.toml index 72a8226..a13e2ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,20 +14,22 @@ name = "composition" path = "src/main.rs" [features] -default = [] +default = ["server", "proxy"] +server = ["world", "dep:tokio-util", "dep:base64"] +proxy = ["dep:tokio-util"] +world = ["dep:async-trait"] update_1_20 = [] [dependencies] -anyhow = "1.0.94" -async-trait = "0.1.68" -base64 = "0.22.1" +async-trait = { version = "0.1.68", optional = true } +base64 = { version = "0.22.1", optional = true } clap = { version = "4.5.22", features = ["derive"] } once_cell = "1.17.1" serde = { version = "1.0.160", features = ["serde_derive"] } serde_json = "1.0.96" thiserror = "2.0.4" tokio = { version = "1.42.0", features = ["full"] } -tokio-util = "0.7.13" +tokio-util = { version = "0.7.13", optional = true } toml = "0.8.19" tracing = { version = "0.1.37", features = ["log"] } tracing-subscriber = { version = "0.3.17", features = ["tracing-log"] } diff --git a/src/config.rs b/src/config.rs index b728e95..22a40f7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,15 @@ use clap::Arg; use once_cell::sync::{Lazy, OnceCell}; use serde::{Deserialize, Serialize}; +use std::ffi::OsStr; use std::io::{Read, Write}; use std::{fs::File, path::Path, path::PathBuf}; use tracing::{error, trace, warn}; -use std::ffi::OsStr; -use crate::server::config::{ServerConfig, ServerArgs, DEFAULT_SERVER_ARGS}; +#[cfg(feature = "proxy")] +use crate::proxy::config::{ProxyArgs, ProxyConfig}; +#[cfg(feature = "server")] +use crate::server::config::{ServerArgs, ServerConfig}; /// The globally-accessible static instance of Config. /// On program startup, Config::load() should be called to initialize it. @@ -35,9 +38,27 @@ pub fn read_file(path: &Path) -> std::io::Result> { pub struct Config { #[serde(rename = "composition")] pub global: GlobalConfig, + #[cfg(feature = "server")] pub server: ServerConfig, + #[cfg(feature = "proxy")] + pub proxy: ProxyConfig, } impl Config { + pub fn get_formatted_version(subcommand: Subcommand) -> String { + format!( + "composition{} {} ({} {})", + match subcommand { + Subcommand::None => "", + #[cfg(feature = "server")] + Subcommand::Server => "", + #[cfg(feature = "proxy")] + Subcommand::Proxy => "-proxy", + }, + env!("CARGO_PKG_VERSION"), + &env!("GIT_HASH")[0..9], + &env!("GIT_DATE")[0..10] + ) + } pub fn instance() -> &'static Self { match CONFIG.get() { Some(a) => a, @@ -69,13 +90,10 @@ impl Config { error!("Could not read configuration file, using default"); } - // Load the server icon - config.server.server_icon = args - .server - .as_ref() - .map(|s| s.server_icon.clone()) - .unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone()); - config.server.load_icon(); + #[cfg(feature = "server")] + { + config.server.load_icon(); + } CONFIG.set(config).expect("could not set CONFIG"); Self::instance() @@ -117,12 +135,7 @@ pub struct GlobalConfig { impl Default for GlobalConfig { fn default() -> Self { GlobalConfig { - version: format!( - "composition {} ({} {})", - env!("CARGO_PKG_VERSION"), - &env!("GIT_HASH")[0..9], - &env!("GIT_DATE")[0..10] - ), + version: Config::get_formatted_version(Subcommand::None), protocol_version: 762, game_version: "1.19.4".to_owned(), threads: None, @@ -135,10 +148,14 @@ impl GlobalConfig { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Default)] pub enum Subcommand { + #[default] None, + #[cfg(feature = "server")] Server, + #[cfg(feature = "proxy")] + Proxy, } /// All of the valid command line arguments for the composition binary. @@ -150,7 +167,10 @@ pub struct Args { pub log_level: Option, pub log_dir: PathBuf, pub subcommand: Subcommand, + #[cfg(feature = "server")] pub server: Option, + #[cfg(feature = "proxy")] + pub proxy: Option, } impl Default for Args { fn default() -> Self { @@ -159,7 +179,10 @@ impl Default for Args { log_level: None, log_dir: PathBuf::from("logs"), subcommand: Subcommand::None, + #[cfg(feature = "server")] server: None, + #[cfg(feature = "proxy")] + proxy: None, } } } @@ -174,8 +197,9 @@ impl Args { ARGS.set(Self::parse()).expect("could not set ARGS"); Self::instance() } + #[allow(unused_mut)] fn command() -> clap::Command { - clap::Command::new("composition") + let mut cmd = clap::Command::new("composition") .about(env!("CARGO_PKG_DESCRIPTION")) .disable_version_flag(true) .arg( @@ -221,9 +245,18 @@ impl Args { .value_name("dir") .value_hint(clap::ValueHint::DirPath) .default_value(OsStr::new(&DEFAULT_ARGS.log_dir)), - ) - .subcommand(ServerArgs::command()) + ); + #[cfg(feature = "server")] + { + cmd = cmd.subcommand(ServerArgs::command()); + } + #[cfg(feature = "proxy")] + { + cmd = cmd.subcommand(ProxyArgs::command()); + } + cmd } + #[allow(unreachable_code)] fn parse() -> Self { let mut args = Self::default(); let m = Self::command().get_matches(); @@ -257,10 +290,16 @@ impl Args { } match m.subcommand() { + #[cfg(feature = "server")] Some(("server", m)) => { args.subcommand = Subcommand::Server; args.server = Some(ServerArgs::parse(m.clone())) } + #[cfg(feature = "proxy")] + Some(("proxy", m)) => { + args.subcommand = Subcommand::Proxy; + args.proxy = Some(ProxyArgs::parse(m.clone())) + } None => { let _ = Self::command().print_help(); std::process::exit(0); diff --git a/src/lib.rs b/src/lib.rs index c0e57b3..88ae50d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,15 @@ pub mod config; /// The Minecraft protocol implemented in a network-agnostic way. pub mod protocol; +/// A proxy server. +#[cfg(feature = "proxy")] +pub(crate) mod proxy; /// The core server implementation. +#[cfg(feature = "server")] pub(crate) mod server; /// A Minecraft world generator implementation that allows for custom worlds. -pub mod world; +#[cfg(feature = "world")] +pub(crate) mod world; use config::Subcommand; use once_cell::sync::OnceCell; @@ -18,7 +23,10 @@ pub static START_TIME: OnceCell = OnceCell::new(); pub async fn run(command: Subcommand) { match command { + #[cfg(feature = "server")] Subcommand::Server => server::Server::run().await, + #[cfg(feature = "proxy")] + Subcommand::Proxy => proxy::Proxy::run().await, Subcommand::None => unreachable!(), } } diff --git a/src/protocol/error.rs b/src/protocol/error.rs index 1cb7414..c17f1a6 100644 --- a/src/protocol/error.rs +++ b/src/protocol/error.rs @@ -16,10 +16,6 @@ pub enum Error { /// The data was not able to be parsed. #[error("parsing")] Parsing, - /// This error is general purpose. - /// When possible, other error variants should be used. - #[error(transparent)] - Other(#[from] anyhow::Error), } /// Alias for a Result with the error type `composition_protocol::Error`. diff --git a/src/proxy/config.rs b/src/proxy/config.rs new file mode 100644 index 0000000..eb42244 --- /dev/null +++ b/src/proxy/config.rs @@ -0,0 +1,46 @@ +use crate::config::{Args, Config}; +use serde::{Deserialize, Serialize}; + +/// The main server configuration struct. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct ProxyConfig { + #[serde(rename = "version-string")] + pub version: String, + pub port: u16, + pub upstream_address: String, +} +impl Default for ProxyConfig { + fn default() -> Self { + ProxyConfig { + version: Config::get_formatted_version(crate::config::Subcommand::Proxy), + port: 25565, + upstream_address: String::new(), + } + } +} +impl ProxyConfig { + pub fn instance() -> &'static Self { + &Config::instance().proxy + } +} + +#[derive(Debug)] +pub struct ProxyArgs {} +impl Default for ProxyArgs { + fn default() -> Self { + ProxyArgs {} + } +} +impl ProxyArgs { + pub fn instance() -> Option<&'static Self> { + Args::instance().proxy.as_ref() + } + pub fn command() -> clap::Command { + clap::Command::new("proxy").about("Run composition in proxy mode") + } + pub fn parse(_: clap::ArgMatches) -> Self { + ProxyArgs::default() + } +} diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs new file mode 100644 index 0000000..2d0f09b --- /dev/null +++ b/src/proxy/mod.rs @@ -0,0 +1,71 @@ +pub mod config; + +use crate::config::Config; +use config::ProxyConfig; +use tokio::net::ToSocketAddrs; +use tokio_util::sync::CancellationToken; +use tracing::{info, trace}; + +#[derive(Debug)] +pub struct Proxy {} +impl Proxy { + /// Start the proxy. + #[tracing::instrument] + pub async fn run() { + let config = Config::instance(); + info!( + "Starting {} on port {}", + ProxyConfig::default().version, + config.proxy.port + ); + let (mut proxy, running) = Self::new(format!("0.0.0.0:{}", config.proxy.port)).await; + info!( + "Done! Start took {:?}", + crate::START_TIME.get().unwrap().elapsed() + ); + + // Spawn the ctrl-c task. + let r = running.clone(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.unwrap(); + info!("Ctrl-C received, shutting down"); + r.cancel(); + }); + + // The main loop. + loop { + tokio::select! { + _ = running.cancelled() => { + break; + } + _ = proxy.update() => {} + } + } + + match tokio::time::timeout(std::time::Duration::from_secs(10), proxy.shutdown()).await { + Ok(_) => std::process::exit(0), + Err(_) => std::process::exit(1), + } + } + #[tracing::instrument] + async fn new( + _bind_address: A, + ) -> (Proxy, CancellationToken) { + trace!("Proxy::new()"); + + let running = CancellationToken::new(); + let proxy = Proxy {}; + + (proxy, running) + } + #[tracing::instrument] + async fn update(&mut self) -> Result<(), ()> { + // TODO + Ok(()) + } + #[tracing::instrument] + async fn shutdown(self) { + trace!("Proxy.shutdown()"); + // TODO + } +} diff --git a/src/server/config.rs b/src/server/config.rs index adc0cb6..01949c6 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -1,11 +1,11 @@ +use crate::config::{read_file, Args, Config}; use clap::Arg; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use std::ffi::OsStr; use std::io::Write; use std::{fs::File, path::Path, path::PathBuf}; use tracing::{error, trace, warn}; -use crate::config::{Config, Args, read_file}; -use std::ffi::OsStr; pub static DEFAULT_SERVER_ARGS: Lazy = Lazy::new(ServerArgs::default); @@ -14,6 +14,8 @@ pub static DEFAULT_SERVER_ARGS: Lazy = Lazy::new(ServerArgs::default #[serde(rename_all = "kebab-case")] #[serde(default)] pub struct ServerConfig { + #[serde(rename = "version-string")] + pub version: String, pub port: u16, pub max_players: usize, pub motd: String, @@ -24,6 +26,7 @@ pub struct ServerConfig { impl Default for ServerConfig { fn default() -> Self { ServerConfig { + version: Config::get_formatted_version(crate::config::Subcommand::Server), port: 25565, max_players: 20, motd: "Hello world!".to_owned(), @@ -36,7 +39,13 @@ impl ServerConfig { pub fn instance() -> &'static Self { &Config::instance().server } + /// Load the server icon. pub fn load_icon(&mut self) { + self.server_icon = ServerArgs::instance() + .as_ref() + .map(|s| s.server_icon.clone()) + .unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone()); + let server_icon_path = Path::new(&self.server_icon); if server_icon_path.exists() { diff --git a/src/server/mod.rs b/src/server/mod.rs index 28b0332..2430831 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,12 +1,13 @@ +/// Server-specific configuration. +pub mod config; /// When managing the server encounters errors. pub mod error; /// Network operations. pub mod net; -/// Server-specific configuration. -pub mod config; use crate::config::Config; use crate::protocol::ClientState; +use config::ServerConfig; use error::Result; use net::{NetworkClient, NetworkClientState}; use std::sync::Arc; @@ -26,7 +27,11 @@ impl Server { #[tracing::instrument] pub async fn run() { let config = Config::instance(); - info!("Starting {} on port {}", config.global.version, config.server.port); + info!( + "Starting {} on port {}", + ServerConfig::default().version, + config.server.port + ); let (mut server, running) = Self::new(format!("0.0.0.0:{}", config.server.port)).await; info!( "Done! Start took {:?}", @@ -57,7 +62,7 @@ impl Server { } } #[tracing::instrument] - pub async fn new( + async fn new( bind_address: A, ) -> (Server, CancellationToken) { trace!("Server::new()"); diff --git a/src/world/error.rs b/src/world/error.rs index 57bec6e..880561f 100644 --- a/src/world/error.rs +++ b/src/world/error.rs @@ -5,8 +5,6 @@ pub enum Error { OutOfBounds, #[error(transparent)] Io(#[from] std::io::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), } /// Alias for a Result with the error type `composition_world::Error`. diff --git a/src/world/mod.rs b/src/world/mod.rs index 13ca47e..74db3e8 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + /// Worlds are divided into chunks. pub mod chunks; /// When managing a `World` encounters errors. @@ -6,11 +8,11 @@ pub mod error; pub mod generators; /// Useful re-exports. pub mod prelude { - pub use super::{chunks::Chunk, World}; + // pub use super::{chunks::Chunk, World}; } pub use crate::protocol::{blocks, entities}; -pub use error::{Error, Result}; +pub use error::Result; use crate::world::chunks::{Chunk, ChunkPosition}; use blocks::{Block, BlockPosition};