Add proxy stub

This commit is contained in:
Garen Tyler 2024-12-04 21:39:58 -07:00
parent 514e48c6ad
commit 7b63542810
Signed by: garentyler
SSH Key Fingerprint: SHA256:G4ke7blZMdpWPbkescyZ7IQYE4JAtwpI85YoJdq+S7U
11 changed files with 215 additions and 46 deletions

7
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

View File

@ -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<Vec<u8>> {
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<tracing::Level>,
pub log_dir: PathBuf,
pub subcommand: Subcommand,
#[cfg(feature = "server")]
pub server: Option<ServerArgs>,
#[cfg(feature = "proxy")]
pub proxy: Option<ProxyArgs>,
}
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);

View File

@ -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<Instant> = 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!(),
}
}

View File

@ -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`.

46
src/proxy/config.rs Normal file
View File

@ -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()
}
}

71
src/proxy/mod.rs Normal file
View File

@ -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<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
_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
}
}

View File

@ -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<ServerArgs> = Lazy::new(ServerArgs::default);
@ -14,6 +14,8 @@ pub static DEFAULT_SERVER_ARGS: Lazy<ServerArgs> = 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() {

View File

@ -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<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
async fn new<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
bind_address: A,
) -> (Server, CancellationToken) {
trace!("Server::new()");

View File

@ -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`.

View File

@ -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};