From 9dde53c9ae03f1679fdf7541a69889204af17496 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Sat, 27 Dec 2025 00:29:20 -0600 Subject: [PATCH 01/23] feat: server cli up and running --- crates/longboy-cli/Cargo.toml | 14 ++++ crates/longboy-cli/src/main.rs | 119 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 crates/longboy-cli/Cargo.toml create mode 100644 crates/longboy-cli/src/main.rs diff --git a/crates/longboy-cli/Cargo.toml b/crates/longboy-cli/Cargo.toml new file mode 100644 index 0000000..9b2f70b --- /dev/null +++ b/crates/longboy-cli/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "longboy-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +config = "0.15.19" +longboy = { version = "1.0.0", path = "../longboy" } +quinn = "0.11.9" +rustls = "0.23.35" +serde = { version = "1.0.228", features = ["derive"] } +tokio = "1.48.0" +tokio-util = "0.7.17" diff --git a/crates/longboy-cli/src/main.rs b/crates/longboy-cli/src/main.rs new file mode 100644 index 0000000..c89d206 --- /dev/null +++ b/crates/longboy-cli/src/main.rs @@ -0,0 +1,119 @@ +use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; +use config::Config; +use quinn::{Endpoint, crypto::rustls::QuicServerConfig}; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}; +use std::{net::SocketAddr, sync::Arc}; +use anyhow::{Context, Result}; +use tokio_util::sync::CancellationToken; + +// This really could be a feature in the toml configuration crate. +#[derive(serde::Deserialize)] +enum LongboyRuntimeType { + Tokio, + Thread +} + +#[derive(serde::Deserialize)] +struct LongboyServerConfig +{ + // Define configuration fields here + // e.g., session_capacity: usize, + // runtime: enum of tokio or async-std, + session_capacity: usize, + runtime_type: LongboyRuntimeType, + public_certificate_path: String, + private_key_path: String, + listen_address: Option, +} + +/// Entry point of the application. +/// +/// This function collects configuration settings and starts the server. +fn main() +{ + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); + // Setup the configuration builder for the server. Let environment variables take the highest precedence. + let settings = Config::builder() + .add_source(config::File::with_name(&format!("{}/longboy", base_config_dir)).required(false)) + .add_source(config::Environment::with_prefix("LONGBOY")) + .build() + .unwrap(); + + let config: LongboyServerConfig = settings.try_deserialize::().expect("Failed to deserialize configuration"); + + // Create a cancellation token for the runtime. + let cancellation_token = CancellationToken::new(); + let _ = run_server_from_config(config, cancellation_token); +} + +// Runs the server based on the provided configuration. It uses the tokio runtime by default. +#[tokio::main] +async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: CancellationToken) -> Result<()> +{ + let server_runtime = match config.runtime_type { + LongboyRuntimeType::Tokio => Box::new(TokioRuntime::new(cancellation_token.clone())) as Box, + LongboyRuntimeType::Thread => Box::new(ThreadRuntime::new(cancellation_token.clone())) as Box, + }; + let server_builder = Server::builder(config.session_capacity, server_runtime); + + // Load TLS certificates. TLS is required. + let (cert_chain, key) = { + let cert_path = std::path::Path::new(&config.public_certificate_path); + let key_path = std::path::Path::new(&config.private_key_path); + let key = if key_path.extension().is_some_and(|x| x == "der") { + PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( + std::fs::read(key_path).context("failed to read private key file")?, + )) + } else { + PrivateKeyDer::from_pem_file(key_path) + .context("failed to read PEM from private key file")? + }; + + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") { + vec![CertificateDer::from( + std::fs::read(cert_path).context("failed to read certificate chain file")?, + )] + } else { + CertificateDer::pem_file_iter(cert_path) + .context("failed to read PEM from certificate chain file")? + .collect::>() + .context("invalid PEM-encoded certificate")? + }; + + (cert_chain, key) + }; + + // setup listen address + let listen_addr = config.listen_address.unwrap_or_else(|| "[::1]:4433".parse().unwrap()); + + // Now use Quinn (HTTP/3 (QUIP)) to start a listening endpoint. + let server_crypto = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key)?; + let server_config = quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(server_crypto)?)); + let server_endpoint = Endpoint::server( + server_config, + listen_addr, + ).context("failed to build endpoint")?; + + // create a new logical server instance + let mut server_instance = server_builder.build(); + + // start accepting connections + while let Some(conn) = server_endpoint.accept().await { + handle_connection(conn, &mut server_instance).await?; + }; + + Ok(()) +} + +async fn handle_connection(conn: quinn::Incoming, server: &mut Server) -> Result<()> { + let connection = conn.await?; + println!("New connection from {}", connection.remote_address()); + + let session_id = 0; + let cipher_key = 0xdeadbeef; + let server_session = ServerSession::new(session_id, cipher_key, connection).await?; + server.register(server_session); + Ok(()) +} From bc5c6109fb71b84ecd07802c909f7b0cb9351fd0 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Sun, 28 Dec 2025 22:48:12 -0600 Subject: [PATCH 02/23] feat: working networked example using a client ans server registration flow --- .gitignore | 6 + .vscode/launch.json | 27 +++++ crates/longboy-client-cli/Cargo.toml | 15 +++ crates/longboy-client-cli/src/main.rs | 96 +++++++++++++++ .../Cargo.toml | 4 +- .../src/main.rs | 111 +++++++++++++----- 6 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 crates/longboy-client-cli/Cargo.toml create mode 100644 crates/longboy-client-cli/src/main.rs rename crates/{longboy-cli => longboy-server-cli}/Cargo.toml (75%) rename crates/{longboy-cli => longboy-server-cli}/src/main.rs (54%) diff --git a/.gitignore b/.gitignore index 6985cf1..e5db288 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,9 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# dotenv environment variable files +.env + +# A bunch of local certificates files for testing +crates/longboy-server-cli/test/data \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..22437d0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "type": "cppdbg", + "request": "launch", + "name": "Launch longboy-client-cli", + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/target/debug/longboy-client-cli", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "/Users/martinhollstein/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "args": [], + "sourceFileMap": {}, + "osx": { + "MIMode": "lldb" + } + } + ] +} \ No newline at end of file diff --git a/crates/longboy-client-cli/Cargo.toml b/crates/longboy-client-cli/Cargo.toml new file mode 100644 index 0000000..d05b010 --- /dev/null +++ b/crates/longboy-client-cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "longboy-client-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +config = "0.15.19" +longboy = { version = "1.0.0", path = "../longboy" } +quinn = "0.11.9" +rustls = { version = "0.23.35", features = ["aws-lc-rs"] } +rustls-native-certs = "0.8.2" +serde = { version = "1.0.228", features = ["derive"] } +tokio = "1.48.0" +tokio-util = "0.7.17" diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs new file mode 100644 index 0000000..45680aa --- /dev/null +++ b/crates/longboy-client-cli/src/main.rs @@ -0,0 +1,96 @@ +use std::{net::SocketAddr, sync::Arc}; + +use config::Config; +use anyhow::{Context, Result}; +use quinn::{Endpoint, ClientConfig}; +use rustls::{crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, pem::PemObject}}; +use rustls_native_certs::load_native_certs; +use longboy::{Client, ClientSession}; + +#[derive(serde::Deserialize)] +struct LongboyClientConfig +{ + certificate_trust_store: Option, + server_address: SocketAddr, + server_name: String, +} + +fn main() +{ + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); + // Setup the configuration builder for the server. Let environment variables take the highest precedence. + let settings = Config::builder() + .add_source(config::File::with_name(&format!("{}/longboy", base_config_dir)).required(false)) + .add_source(config::Environment::with_prefix("LONGBOY_CLIENT")) + .build() + .unwrap(); + let config: LongboyClientConfig = settings + .try_deserialize::() + .expect("Failed to deserialize configuration"); + if let Err(err) = run_client_from_config(config) + { + eprintln!("Error running longboy client: {err:#}"); + } +} + +#[tokio::main] +async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<()> +{ + // load rustls default crypto provider with aws-lc-rs backend + let provider = aws_lc_rs::default_provider(); + CryptoProvider::install_default(provider).expect("Failed to install default crypto provider"); + + // load the default trust store if provided + let mut root_store = rustls::RootCertStore::empty(); + for cert in load_native_certs().certs + { + root_store.add(cert).context("failed to add native certificate to root store")?; + } + + if let Some(trust_store_path) = config.certificate_trust_store + { + let cert_path = std::path::Path::new(&trust_store_path); + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") + { + vec![CertificateDer::from( + std::fs::read(cert_path).context("failed to read certificate chain file")?, + )] + } + else + { + CertificateDer::pem_file_iter(cert_path) + .context("failed to read PEM from certificate chain file")? + .collect::>() + .context("invalid PEM-encoded certificate")? + }; + for cert in cert_chain + { + root_store.add(cert).context("failed to add certificate to root store")?; + } + } + + // Create client endpoint with the given server address and tls configuration + let client_config = ClientConfig::with_root_certificates(Arc::new(root_store))?; + let client_endpoint = Endpoint::client(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + + // Connect to the server + let connection = client_endpoint + .connect_with(client_config, config.server_address, config.server_name.as_str()) + .context("failed to connect to server")? + .await + .context("failed to establish connection to server")?; + + println!("Connected to server at {}", config.server_address); + + // Create longboy client sessions + let client_session = ClientSession::new(connection).await?; + + // Session established, create the longboy client using tokyo runtime. + let runtime = longboy::TokioRuntime::new(tokio_util::sync::CancellationToken::new()); + let _longboy_client = Client::builder(client_session, Box::new(runtime)).build(); + + // Client was created, for now just log and exit. + println!("Longboy client session established."); + + Ok(()) +} diff --git a/crates/longboy-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml similarity index 75% rename from crates/longboy-cli/Cargo.toml rename to crates/longboy-server-cli/Cargo.toml index 9b2f70b..46d2252 100644 --- a/crates/longboy-cli/Cargo.toml +++ b/crates/longboy-server-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "longboy-cli" +name = "longboy-server-cli" version = "0.1.0" edition = "2024" @@ -8,7 +8,7 @@ anyhow = "1.0.100" config = "0.15.19" longboy = { version = "1.0.0", path = "../longboy" } quinn = "0.11.9" -rustls = "0.23.35" +rustls = { version = "0.23.35", features = ["aws-lc-rs"] } serde = { version = "1.0.228", features = ["derive"] } tokio = "1.48.0" tokio-util = "0.7.17" diff --git a/crates/longboy-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs similarity index 54% rename from crates/longboy-cli/src/main.rs rename to crates/longboy-server-cli/src/main.rs index c89d206..ef32f2c 100644 --- a/crates/longboy-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -1,16 +1,20 @@ -use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; +use anyhow::{Context, Result}; use config::Config; -use quinn::{Endpoint, crypto::rustls::QuicServerConfig}; -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}; +use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; +use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; +use rustls::{ + crypto::{CryptoProvider, aws_lc_rs}, + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, +}; use std::{net::SocketAddr, sync::Arc}; -use anyhow::{Context, Result}; use tokio_util::sync::CancellationToken; // This really could be a feature in the toml configuration crate. #[derive(serde::Deserialize)] -enum LongboyRuntimeType { +enum LongboyRuntimeType +{ Tokio, - Thread + Thread, } #[derive(serde::Deserialize)] @@ -35,11 +39,13 @@ fn main() // Setup the configuration builder for the server. Let environment variables take the highest precedence. let settings = Config::builder() .add_source(config::File::with_name(&format!("{}/longboy", base_config_dir)).required(false)) - .add_source(config::Environment::with_prefix("LONGBOY")) + .add_source(config::Environment::with_prefix("LONGBOY_SERVER")) .build() .unwrap(); - let config: LongboyServerConfig = settings.try_deserialize::().expect("Failed to deserialize configuration"); + let config: LongboyServerConfig = settings + .try_deserialize::() + .expect("Failed to deserialize configuration"); // Create a cancellation token for the runtime. let cancellation_token = CancellationToken::new(); @@ -50,30 +56,45 @@ fn main() #[tokio::main] async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: CancellationToken) -> Result<()> { - let server_runtime = match config.runtime_type { - LongboyRuntimeType::Tokio => Box::new(TokioRuntime::new(cancellation_token.clone())) as Box, - LongboyRuntimeType::Thread => Box::new(ThreadRuntime::new(cancellation_token.clone())) as Box, + let server_runtime = match config.runtime_type + { + LongboyRuntimeType::Tokio => + { + Box::new(TokioRuntime::new(cancellation_token.clone())) as Box + } + LongboyRuntimeType::Thread => + { + Box::new(ThreadRuntime::new(cancellation_token.clone())) as Box + } }; let server_builder = Server::builder(config.session_capacity, server_runtime); // Load TLS certificates. TLS is required. + let provider = aws_lc_rs::default_provider(); + CryptoProvider::install_default(provider).expect("Failed to install default crypto provider"); + let (cert_chain, key) = { let cert_path = std::path::Path::new(&config.public_certificate_path); let key_path = std::path::Path::new(&config.private_key_path); - let key = if key_path.extension().is_some_and(|x| x == "der") { + let key = if key_path.extension().is_some_and(|x| x == "der") + { PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( std::fs::read(key_path).context("failed to read private key file")?, )) - } else { - PrivateKeyDer::from_pem_file(key_path) - .context("failed to read PEM from private key file")? + } + else + { + PrivateKeyDer::from_pem_file(key_path).context("failed to read PEM from private key file")? }; - let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") { + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") + { vec![CertificateDer::from( std::fs::read(cert_path).context("failed to read certificate chain file")?, )] - } else { + } + else + { CertificateDer::pem_file_iter(cert_path) .context("failed to read PEM from certificate chain file")? .collect::>() @@ -91,29 +112,61 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: .with_no_client_auth() .with_single_cert(cert_chain, key)?; let server_config = quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(server_crypto)?)); - let server_endpoint = Endpoint::server( - server_config, - listen_addr, - ).context("failed to build endpoint")?; + let server_endpoint = Endpoint::server(server_config, listen_addr).context("failed to build endpoint")?; // create a new logical server instance let mut server_instance = server_builder.build(); + // log out some info on server startup + println!("Longboy server listening on {}", listen_addr); + // print stats from server endpoint + println!("Server endpoint stats: {:?}", server_endpoint.stats()); + // start accepting connections - while let Some(conn) = server_endpoint.accept().await { - handle_connection(conn, &mut server_instance).await?; - }; + while let Some(conn) = server_endpoint.accept().await + { + // print it + println!("Incoming connection: {:?}", conn.remote_address()); + + // try to accept and handle errors gracefully + match conn.accept() + { + Err(e) => { + println!("Failed to accept connection: {:?}", e); + continue; + } + Ok(connecting) => { + match connecting.await + { + Err(e) => { + println!("Failed to establish connection: {:?}", e); + continue; + } + Ok(connection) => { + // handle the connection + if let Err(e) = server_handle_connection(connection, &mut server_instance).await + { + println!("Error handling connection: {:?}", e); + } + } + } + }, + } + } Ok(()) } -async fn handle_connection(conn: quinn::Incoming, server: &mut Server) -> Result<()> { - let connection = conn.await?; - println!("New connection from {}", connection.remote_address()); - +async fn server_handle_connection(conn: Connection, server: &mut Server) -> Result<()> +{ + let remote_address = conn.remote_address(); + println!("New connection from {}", remote_address); + let session_id = 0; let cipher_key = 0xdeadbeef; - let server_session = ServerSession::new(session_id, cipher_key, connection).await?; + let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); + + print!("Server session registered\t\nSession Id: {}\t\nRemote Address: {}\n", session_id, remote_address); Ok(()) } From 5c2cfc1af116efc8fcaa563d1e4b023034c7e5c6 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Tue, 6 Jan 2026 23:04:21 -0600 Subject: [PATCH 03/23] fix: compiles again --- crates/longboy-server-cli/Cargo.toml | 2 + crates/longboy-server-cli/src/main.rs | 180 +++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 4 deletions(-) diff --git a/crates/longboy-server-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml index 46d2252..086bad2 100644 --- a/crates/longboy-server-cli/Cargo.toml +++ b/crates/longboy-server-cli/Cargo.toml @@ -6,6 +6,8 @@ edition = "2024" [dependencies] anyhow = "1.0.100" config = "0.15.19" +enum-map = "2.7.3" +flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } quinn = "0.11.9" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index ef32f2c..d7d8cba 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -1,12 +1,20 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +#![feature(generic_const_items)] +#![feature(map_try_insert)] +#![feature(try_blocks)] + +use enum_map::enum_map; use anyhow::{Context, Result}; use config::Config; -use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; +use flume::{Receiver, Sender}; +use longboy::{ClientToServerSchema, Factory, Mirroring, Server, ServerSession, ServerToClientSchema, Source, Sink, ThreadRuntime, TokioRuntime}; use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use std::{net::SocketAddr, sync::Arc}; +use std::{net::{SocketAddr, UdpSocket}, sync::Arc}; use tokio_util::sync::CancellationToken; // This really could be a feature in the toml configuration crate. @@ -30,6 +38,95 @@ struct LongboyServerConfig listen_address: Option, } +// Server runtime sender and receiver behaviors. +struct ServerToClientSourceFactory +{ + channels: [Receiver<(u32, [u64; 2])>; 32 /* max players per instance */], +} + +struct ClientToServerSinkFactory +{ + channel: Sender<(u32, u8, u64)>, +} + +struct ClientToServerSink +{ + player_index: u8, + channel: Sender<(u32, u8, u64)>, +} + +struct ServerToClientSource +{ + channel: Receiver<(u32, [u64; 2])>, +} + +impl Factory for ServerToClientSourceFactory +{ + type Type = ServerToClientSource; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let player_index = match session_id + { + 1 => 0, + 2 => 1, + _ => unreachable!(), + }; + + ServerToClientSource { + channel: self.channels[player_index].clone(), + } + } +} + +impl Factory for ClientToServerSinkFactory +{ + type Type = ClientToServerSink; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let player_index = match session_id + { + 1 => 0, + 2 => 1, + _ => unreachable!(), + }; + + ClientToServerSink { + player_index, + channel: self.channel.clone(), + } + } +} + +impl Sink<16> for ClientToServerSink +{ + fn handle(&mut self, buffer: &[u8; 16]) + { + let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); + let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); + self.channel.send((frame, self.player_index, player_input)).unwrap(); + } +} + +impl Source<32> for ServerToClientSource +{ + fn poll(&mut self, buffer: &mut [u8; 32]) -> bool + { + match self.channel.try_recv() + { + Ok((frame, player_inputs)) => + { + *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); + true + } + Err(_) => false, + } + } +} + /// Entry point of the application. /// /// This function collects configuration settings and starts the server. @@ -67,7 +164,76 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Box::new(ThreadRuntime::new(cancellation_token.clone())) as Box } }; - let server_builder = Server::builder(config.session_capacity, server_runtime); + + // Setup the receivers and senders for client-server communication. + let client_to_server_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let client_to_server_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let server_to_client_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let client_to_server_schema = ClientToServerSchema { + name: "Input", + mapper_port: client_to_server_mapper_socket.local_addr()?.port(), + heartbeat_period: 2000, + port: client_to_server_socket.local_addr()?.port(), + }; + let server_to_client_schema = ServerToClientSchema { + name: "State", + mapper_port: server_to_client_mapper_socket.local_addr()?.port(), + heartbeat_period: 2000, + }; + let server_builder = Server::builder(config.session_capacity, server_runtime) + .sender_with_sockets::<_, 32, 3>( + &server_to_client_schema, + server_to_client_mapper_socket, + enum_map! { + Mirroring::AudioVideo => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, + Mirroring::Background => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, + Mirroring::Voice => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, + }, + ServerToClientSourceFactory { + channels: [ + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1, + flume::unbounded().1 + ], + }, + )? + .receiver_with_socket::<_, 16, 3>( + &client_to_server_schema, + client_to_server_mapper_socket, + client_to_server_socket, + ClientToServerSinkFactory { + channel: flume::unbounded().0, + }, + )?; // Load TLS certificates. TLS is required. let provider = aws_lc_rs::default_provider(); @@ -165,7 +331,13 @@ async fn server_handle_connection(conn: Connection, server: &mut Server) -> Resu let session_id = 0; let cipher_key = 0xdeadbeef; let server_session = ServerSession::new(session_id, cipher_key, conn).await?; - server.register(server_session); + server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? + + loop { + // Here you would handle incoming requests, manage sessions, etc. + // For demonstration, we will just break the loop. + break; + } print!("Server session registered\t\nSession Id: {}\t\nRemote Address: {}\n", session_id, remote_address); Ok(()) From bd585b98e84de43d46832da5b4e0676f13f7be73 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Wed, 7 Jan 2026 01:47:21 -0600 Subject: [PATCH 04/23] chore: it is doing ... something? --- .vscode/launch.json | 11 +++ crates/longboy-client-cli/Cargo.toml | 2 + crates/longboy-client-cli/src/main.rs | 108 +++++++++++++++++++++++--- crates/longboy-server-cli/src/main.rs | 54 ++++++++----- 4 files changed, 145 insertions(+), 30 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 22437d0..4eed3c7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,4 +1,5 @@ { + "version": "0.2.0", "configurations": [ { "type": "cppdbg", @@ -22,6 +23,16 @@ "osx": { "MIMode": "lldb" } + }, + { + "type": "cppdbg", // Use "type": "cppdbg" for Windows C/C++ extension + "request": "attach", + "name": "Attach to Process by PID", + "program": "${workspaceFolder}/target/debug/longboy-server-cli", + "processId": "${command:pickProcess}", // Provides a filterable list of running processes + "osx": { + "MIMode": "lldb" + } } ] } \ No newline at end of file diff --git a/crates/longboy-client-cli/Cargo.toml b/crates/longboy-client-cli/Cargo.toml index d05b010..0475844 100644 --- a/crates/longboy-client-cli/Cargo.toml +++ b/crates/longboy-client-cli/Cargo.toml @@ -6,6 +6,8 @@ edition = "2024" [dependencies] anyhow = "1.0.100" config = "0.15.19" +crossterm = "0.29.0" +flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } quinn = "0.11.9" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 45680aa..091f7e5 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -1,11 +1,21 @@ -use std::{net::SocketAddr, sync::Arc}; +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +#![feature(generic_const_items)] +#![feature(unboxed_closures)] + +use std::{net::{SocketAddr, UdpSocket}, sync::Arc, thread::yield_now}; -use config::Config; use anyhow::{Context, Result}; -use quinn::{Endpoint, ClientConfig}; -use rustls::{crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, pem::PemObject}}; +use config::Config; +use longboy::{Client, ClientSession, ClientToServerSchema, ServerToClientSchema, Sink, Source}; +use quinn::{ClientConfig, Endpoint}; +use rustls::{ + crypto::{CryptoProvider, aws_lc_rs}, + pki_types::{CertificateDer, pem::PemObject}, +}; use rustls_native_certs::load_native_certs; -use longboy::{Client, ClientSession}; +use crossterm::event::{self, Event, KeyCode}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; #[derive(serde::Deserialize)] struct LongboyClientConfig @@ -15,8 +25,52 @@ struct LongboyClientConfig server_name: String, } +struct ServerToClientSink +{ + channel: flume::Sender<(u32, [u64; 2])>, +} + +struct ClientToServerSource +{ +} + +impl Source<16> for ClientToServerSource +{ + fn poll(&mut self, buffer: &mut [u8; 16]) -> bool + { + if let Event::Key(key_event) = event::read().unwrap() { + let frame: i32 = 0; + match key_event.code { + KeyCode::Esc => false, + KeyCode::Char(val) => { + print!("Sending {}", val); + *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = u64::from(val).to_le_bytes(); + true + } + _ => false, + } + } else { + false + } + } +} + +impl Sink<32> for ServerToClientSink +{ + fn handle(&mut self, buffer: &[u8; 32]) + { + let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); + let player_input_1 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); + let player_input_2 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[12..20]).unwrap())); + self.channel.send((frame, [player_input_1, player_input_2])).unwrap(); + } +} + fn main() { + enable_raw_mode(); // Enters raw mode + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); // Setup the configuration builder for the server. Let environment variables take the highest precedence. let settings = Config::builder() @@ -31,6 +85,8 @@ fn main() { eprintln!("Error running longboy client: {err:#}"); } + + disable_raw_mode(); // Exits raw mode } #[tokio::main] @@ -44,7 +100,9 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( let mut root_store = rustls::RootCertStore::empty(); for cert in load_native_certs().certs { - root_store.add(cert).context("failed to add native certificate to root store")?; + root_store + .add(cert) + .context("failed to add native certificate to root store")?; } if let Some(trust_store_path) = config.certificate_trust_store @@ -65,13 +123,16 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( }; for cert in cert_chain { - root_store.add(cert).context("failed to add certificate to root store")?; + root_store + .add(cert) + .context("failed to add certificate to root store")?; } } // Create client endpoint with the given server address and tls configuration let client_config = ClientConfig::with_root_certificates(Arc::new(root_store))?; - let client_endpoint = Endpoint::client(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let socket = SocketAddr::from(([0, 0, 0, 0], 0)); + let client_endpoint = Endpoint::client(socket).unwrap(); // Connect to the server let connection = client_endpoint @@ -85,12 +146,39 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( // Create longboy client sessions let client_session = ClientSession::new(connection).await?; + let client_to_server_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let client_to_server_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + let server_to_client_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + // Session established, create the longboy client using tokyo runtime. let runtime = longboy::TokioRuntime::new(tokio_util::sync::CancellationToken::new()); - let _longboy_client = Client::builder(client_session, Box::new(runtime)).build(); + let client_to_server_schema = ClientToServerSchema { + name: "Input", + mapper_port: client_to_server_mapper_socket.local_addr().unwrap().port(), + heartbeat_period: 2000, + port: client_to_server_socket.local_addr().unwrap().port(), + }; + + let server_to_client_schema = ServerToClientSchema { + name: "State", + mapper_port: server_to_client_mapper_socket.local_addr().unwrap().port(), + heartbeat_period: 2000, + }; + + let receiver_channel = flume::unbounded(); + let _longboy_client = Client::builder(client_session, Box::new(runtime)) + .receiver::<_, 32, 3>(&server_to_client_schema, ServerToClientSink{ + channel: receiver_channel.0 + })? + .sender::<_, 16, 3>(&client_to_server_schema, ClientToServerSource { + })? + .build(); // Client was created, for now just log and exit. println!("Longboy client session established."); - Ok(()) + + loop { + yield_now(); + } } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index d7d8cba..d44dbb1 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -1,20 +1,26 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs)] #![feature(generic_const_items)] -#![feature(map_try_insert)] -#![feature(try_blocks)] -use enum_map::enum_map; use anyhow::{Context, Result}; use config::Config; +use enum_map::enum_map; use flume::{Receiver, Sender}; -use longboy::{ClientToServerSchema, Factory, Mirroring, Server, ServerSession, ServerToClientSchema, Source, Sink, ThreadRuntime, TokioRuntime}; +use longboy::{ + ClientToServerSchema, Factory, Mirroring, Server, ServerSession, ServerToClientSchema, Sink, Source, ThreadRuntime, + TokioRuntime, +}; use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use std::{net::{SocketAddr, UdpSocket}, sync::Arc}; +use std::{ + net::{SocketAddr, UdpSocket}, + sync::Arc, + thread::yield_now, +}; +use tokio::task; use tokio_util::sync::CancellationToken; // This really could be a feature in the toml configuration crate. @@ -41,7 +47,7 @@ struct LongboyServerConfig // Server runtime sender and receiver behaviors. struct ServerToClientSourceFactory { - channels: [Receiver<(u32, [u64; 2])>; 32 /* max players per instance */], + channels: [Receiver<(u32, [u64; 2])>; 32], } struct ClientToServerSinkFactory @@ -105,6 +111,7 @@ impl Sink<16> for ClientToServerSink { let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); + println!("Recv'd Frame({}) PlayerInput({})", frame, player_input); self.channel.send((frame, self.player_index, player_input)).unwrap(); } } @@ -117,9 +124,11 @@ impl Source<32> for ServerToClientSource { Ok((frame, player_inputs)) => { + *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); + println!("SendFrame({}) PlayerInput0({}) PlayerInput1({})", frame, player_inputs[0], player_inputs[1]); true } Err(_) => false, @@ -180,6 +189,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: mapper_port: server_to_client_mapper_socket.local_addr()?.port(), heartbeat_period: 2000, }; + let receiver_channel = flume::unbounded(); let server_builder = Server::builder(config.session_capacity, server_runtime) .sender_with_sockets::<_, 32, 3>( &server_to_client_schema, @@ -222,7 +232,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: flume::unbounded().1, flume::unbounded().1, flume::unbounded().1, - flume::unbounded().1 + flume::unbounded().1, ], }, )? @@ -231,7 +241,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: client_to_server_mapper_socket, client_to_server_socket, ClientToServerSinkFactory { - channel: flume::unbounded().0, + channel: receiver_channel.0, }, )?; @@ -297,18 +307,22 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: // try to accept and handle errors gracefully match conn.accept() { - Err(e) => { + Err(e) => + { println!("Failed to accept connection: {:?}", e); continue; } - Ok(connecting) => { + Ok(connecting) => + { match connecting.await { - Err(e) => { + Err(e) => + { println!("Failed to establish connection: {:?}", e); continue; } - Ok(connection) => { + Ok(connection) => + { // handle the connection if let Err(e) = server_handle_connection(connection, &mut server_instance).await { @@ -316,7 +330,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: } } } - }, + } } } @@ -328,17 +342,17 @@ async fn server_handle_connection(conn: Connection, server: &mut Server) -> Resu let remote_address = conn.remote_address(); println!("New connection from {}", remote_address); - let session_id = 0; + let session_id = 1; let cipher_key = 0xdeadbeef; let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? - loop { - // Here you would handle incoming requests, manage sessions, etc. - // For demonstration, we will just break the loop. - break; + loop + { + // read stuff + yield_now(); } - print!("Server session registered\t\nSession Id: {}\t\nRemote Address: {}\n", session_id, remote_address); - Ok(()) + //print!("Server session registered\t\nSession Id: {}\t\nRemote Address: {}\n", session_id, remote_address); + //Ok(()) } From a794f700e2ee8c77eb313d7ce53694904bb04409 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Mon, 12 Jan 2026 01:36:07 -0600 Subject: [PATCH 05/23] chore(connection): update debug configurations. Appropriately internal streaming --- .vscode/launch.json | 37 +++++++++- crates/longboy-client-cli/src/main.rs | 101 +++++++++++++++++++------- crates/longboy-server-cli/src/main.rs | 66 +++++------------ 3 files changed, 131 insertions(+), 73 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4eed3c7..8845dbf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,30 @@ }, { "name": "RUSTC_TOOLCHAIN", - "value": "/Users/martinhollstein/.rustup/toolchains/nightly-aarch64-apple-darwin" + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "args": [], + "sourceFileMap": {}, + "osx": { + "MIMode": "lldb" + } + }, + { + "type": "cppdbg", + "request": "launch", + "name": "Launch longboy-server-cli", + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/target/debug/longboy-server-cli", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" } ], "args": [], @@ -27,12 +50,22 @@ { "type": "cppdbg", // Use "type": "cppdbg" for Windows C/C++ extension "request": "attach", - "name": "Attach to Process by PID", + "name": "longboy-server-cli by PID", "program": "${workspaceFolder}/target/debug/longboy-server-cli", "processId": "${command:pickProcess}", // Provides a filterable list of running processes "osx": { "MIMode": "lldb" } + }, + { + "type": "cppdbg", // Use "type": "cppdbg" for Windows C/C++ extension + "request": "attach", + "name": "longboy-client-cli by PID", + "program": "${workspaceFolder}/target/debug/longboy-client-cli", + "processId": "${command:pickProcess}", // Provides a filterable list of running processes + "osx": { + "MIMode": "lldb" + } } ] } \ No newline at end of file diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 091f7e5..d9fab0e 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -3,10 +3,20 @@ #![feature(generic_const_items)] #![feature(unboxed_closures)] -use std::{net::{SocketAddr, UdpSocket}, sync::Arc, thread::yield_now}; +use std::{ + net::{SocketAddr, UdpSocket}, + process, + sync::Arc, + thread::{self}, + time::Duration, +}; use anyhow::{Context, Result}; use config::Config; +use crossterm::{ + event::{self, Event, KeyCode}, + terminal::{disable_raw_mode, enable_raw_mode}, +}; use longboy::{Client, ClientSession, ClientToServerSchema, ServerToClientSchema, Sink, Source}; use quinn::{ClientConfig, Endpoint}; use rustls::{ @@ -14,8 +24,6 @@ use rustls::{ pki_types::{CertificateDer, pem::PemObject}, }; use rustls_native_certs::load_native_certs; -use crossterm::event::{self, Event, KeyCode}; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; #[derive(serde::Deserialize)] struct LongboyClientConfig @@ -32,26 +40,24 @@ struct ServerToClientSink struct ClientToServerSource { + channel: flume::Receiver<(u32, u64)>, } impl Source<16> for ClientToServerSource { fn poll(&mut self, buffer: &mut [u8; 16]) -> bool { - if let Event::Key(key_event) = event::read().unwrap() { - let frame: i32 = 0; - match key_event.code { - KeyCode::Esc => false, - KeyCode::Char(val) => { - print!("Sending {}", val); - *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = u64::from(val).to_le_bytes(); - true - } - _ => false, + let msg = self.channel.recv(); + match msg + { + Ok((frame, val)) => + { + print!("Sending {}", val); + *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = u64::from(val).to_le_bytes(); + true } - } else { - false + _ => false, } } } @@ -69,7 +75,7 @@ impl Sink<32> for ServerToClientSink fn main() { - enable_raw_mode(); // Enters raw mode + let _ = enable_raw_mode(); // Enters raw mode let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); // Setup the configuration builder for the server. Let environment variables take the highest precedence. @@ -86,7 +92,7 @@ fn main() eprintln!("Error running longboy client: {err:#}"); } - disable_raw_mode(); // Exits raw mode + let _ = disable_raw_mode(); // Exits raw mode } #[tokio::main] @@ -166,19 +172,64 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( }; let receiver_channel = flume::unbounded(); + let sender_channel = flume::unbounded(); let _longboy_client = Client::builder(client_session, Box::new(runtime)) - .receiver::<_, 32, 3>(&server_to_client_schema, ServerToClientSink{ - channel: receiver_channel.0 - })? - .sender::<_, 16, 3>(&client_to_server_schema, ClientToServerSource { - })? + .receiver::<_, 32, 3>( + &server_to_client_schema, + ServerToClientSink { + channel: receiver_channel.0, + }, + )? + .sender::<_, 16, 3>( + &client_to_server_schema, + ClientToServerSource { + channel: sender_channel.1, + }, + )? .build(); // Client was created, for now just log and exit. println!("Longboy client session established."); + // dump server events + tokio::spawn(async move { + let recv = receiver_channel.1.clone(); + loop + { + let incoming = recv.recv_async().await; + + match incoming + { + Ok((frame, [first, second])) => + { + println!("Recv'd Frame({}) ({} ; {})", frame, first, second) + } + Err(e) => println!("{}", e), + } + } + }); - loop { - yield_now(); + let mut frame: u32 = 0; + // init with some garbage + sender_channel.0.send((frame, u64::from('z')))?; + loop + { + // send keys + if let Event::Key(key_event) = event::read().unwrap() + { + match key_event.code + { + KeyCode::Esc => process::exit(0), + KeyCode::Char(val) => + { + println!("Sending Frame({}) {}", frame, val); + let as64 = u64::from(val); + sender_channel.0.send((frame, as64)).unwrap(); + } + _ => (), + } + } + frame += 1; + thread::sleep(Duration::from_millis(1)); } } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index d44dbb1..a566ca6 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -18,9 +18,7 @@ use rustls::{ use std::{ net::{SocketAddr, UdpSocket}, sync::Arc, - thread::yield_now, }; -use tokio::task; use tokio_util::sync::CancellationToken; // This really could be a feature in the toml configuration crate. @@ -47,7 +45,7 @@ struct LongboyServerConfig // Server runtime sender and receiver behaviors. struct ServerToClientSourceFactory { - channels: [Receiver<(u32, [u64; 2])>; 32], + channels: [Receiver<(u32, [u64; 2])>; 16], } struct ClientToServerSinkFactory @@ -124,7 +122,6 @@ impl Source<32> for ServerToClientSource { Ok((frame, player_inputs)) => { - *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); @@ -190,6 +187,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: heartbeat_period: 2000, }; let receiver_channel = flume::unbounded(); + let broadcast_channel = flume::unbounded(); let server_builder = Server::builder(config.session_capacity, server_runtime) .sender_with_sockets::<_, 32, 3>( &server_to_client_schema, @@ -201,38 +199,22 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: }, ServerToClientSourceFactory { channels: [ - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, - flume::unbounded().1, + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), + broadcast_channel.1.clone(), ], }, )? @@ -342,17 +324,9 @@ async fn server_handle_connection(conn: Connection, server: &mut Server) -> Resu let remote_address = conn.remote_address(); println!("New connection from {}", remote_address); - let session_id = 1; + let session_id = 1; // Should have an accessible server session manager (with player index mapping) let cipher_key = 0xdeadbeef; let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? - - loop - { - // read stuff - yield_now(); - } - - //print!("Server session registered\t\nSession Id: {}\t\nRemote Address: {}\n", session_id, remote_address); - //Ok(()) + Ok(()) } From 95ccec5eabe1671e75e2e8fcfb83fc32be90fa1b Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Wed, 14 Jan 2026 15:32:04 -0600 Subject: [PATCH 06/23] fix: finally understanding of the socket exchange... but clients will not pump to --- crates/longboy-client-cli/src/main.rs | 10 +++------- crates/longboy-server-cli/src/main.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index d9fab0e..1dbf035 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -152,22 +152,18 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( // Create longboy client sessions let client_session = ClientSession::new(connection).await?; - let client_to_server_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - let client_to_server_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - let server_to_client_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - // Session established, create the longboy client using tokyo runtime. let runtime = longboy::TokioRuntime::new(tokio_util::sync::CancellationToken::new()); let client_to_server_schema = ClientToServerSchema { name: "Input", - mapper_port: client_to_server_mapper_socket.local_addr().unwrap().port(), + mapper_port: 8081, heartbeat_period: 2000, - port: client_to_server_socket.local_addr().unwrap().port(), + port: 8082, }; let server_to_client_schema = ServerToClientSchema { name: "State", - mapper_port: server_to_client_mapper_socket.local_addr().unwrap().port(), + mapper_port: 8080, heartbeat_period: 2000, }; diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index a566ca6..59111c4 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -171,10 +171,11 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: } }; - // Setup the receivers and senders for client-server communication. + // Setup the receivers for client-server communication. let client_to_server_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); let client_to_server_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - let server_to_client_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); + + println!("Ports {}, {}", client_to_server_mapper_socket.local_addr()?.port(), client_to_server_socket.local_addr()?.port()); let client_to_server_schema = ClientToServerSchema { name: "Input", mapper_port: client_to_server_mapper_socket.local_addr()?.port(), @@ -183,20 +184,14 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: }; let server_to_client_schema = ServerToClientSchema { name: "State", - mapper_port: server_to_client_mapper_socket.local_addr()?.port(), + mapper_port: 8080, heartbeat_period: 2000, }; let receiver_channel = flume::unbounded(); let broadcast_channel = flume::unbounded(); let server_builder = Server::builder(config.session_capacity, server_runtime) - .sender_with_sockets::<_, 32, 3>( + .sender::<_, 32, 3>( &server_to_client_schema, - server_to_client_mapper_socket, - enum_map! { - Mirroring::AudioVideo => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, - Mirroring::Background => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, - Mirroring::Voice => UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0)))?, - }, ServerToClientSourceFactory { channels: [ broadcast_channel.1.clone(), From 64f779e65725f2cc5e532340a808106b368c7891 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 16 Jan 2026 00:21:23 -0600 Subject: [PATCH 07/23] feat: use docker to run longboys on same host for testing. --- .gitignore | 2 +- crates/longboy-client-cli/Cargo.toml | 1 + crates/longboy-client-cli/src/main.rs | 20 ++++--------- crates/longboy-schema/Cargo.toml | 7 +++++ crates/longboy-schema/src/lib.rs | 19 ++++++++++++ crates/longboy-server-cli/Cargo.toml | 1 + crates/longboy-server-cli/src/main.rs | 43 ++++++++------------------- docker/.docker-compose.yml | 38 +++++++++++++++++++++++ docker/client.dockerfile | 23 ++++++++++++++ docker/server.dockerfile | 25 ++++++++++++++++ 10 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 crates/longboy-schema/Cargo.toml create mode 100644 crates/longboy-schema/src/lib.rs create mode 100644 docker/.docker-compose.yml create mode 100644 docker/client.dockerfile create mode 100644 docker/server.dockerfile diff --git a/.gitignore b/.gitignore index e5db288..1747ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ Cargo.lock .env # A bunch of local certificates files for testing -crates/longboy-server-cli/test/data \ No newline at end of file +crates/longboy-server-cli/test/data diff --git a/crates/longboy-client-cli/Cargo.toml b/crates/longboy-client-cli/Cargo.toml index 0475844..362ba12 100644 --- a/crates/longboy-client-cli/Cargo.toml +++ b/crates/longboy-client-cli/Cargo.toml @@ -9,6 +9,7 @@ config = "0.15.19" crossterm = "0.29.0" flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } +longboy_schema = { version = "1.0.0", path = "../longboy-schema" } quinn = "0.11.9" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } rustls-native-certs = "0.8.2" diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 1dbf035..8346a58 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -4,7 +4,7 @@ #![feature(unboxed_closures)] use std::{ - net::{SocketAddr, UdpSocket}, + net::{SocketAddr}, process, sync::Arc, thread::{self}, @@ -17,13 +17,14 @@ use crossterm::{ event::{self, Event, KeyCode}, terminal::{disable_raw_mode, enable_raw_mode}, }; -use longboy::{Client, ClientSession, ClientToServerSchema, ServerToClientSchema, Sink, Source}; +use longboy::{Client, ClientSession, Sink, Source}; use quinn::{ClientConfig, Endpoint}; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, pem::PemObject}, }; use rustls_native_certs::load_native_certs; +use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; #[derive(serde::Deserialize)] struct LongboyClientConfig @@ -154,19 +155,8 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( // Session established, create the longboy client using tokyo runtime. let runtime = longboy::TokioRuntime::new(tokio_util::sync::CancellationToken::new()); - let client_to_server_schema = ClientToServerSchema { - name: "Input", - mapper_port: 8081, - heartbeat_period: 2000, - port: 8082, - }; - - let server_to_client_schema = ServerToClientSchema { - name: "State", - mapper_port: 8080, - heartbeat_period: 2000, - }; - + let client_to_server_schema = new_client_to_server_schema(); + let server_to_client_schema = new_server_to_client_schema(); let receiver_channel = flume::unbounded(); let sender_channel = flume::unbounded(); let _longboy_client = Client::builder(client_session, Box::new(runtime)) diff --git a/crates/longboy-schema/Cargo.toml b/crates/longboy-schema/Cargo.toml new file mode 100644 index 0000000..43a100e --- /dev/null +++ b/crates/longboy-schema/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "longboy_schema" +version = "1.0.0" +edition = "2024" + +[dependencies] +longboy = { version = "1.0.0", path = "../longboy" } \ No newline at end of file diff --git a/crates/longboy-schema/src/lib.rs b/crates/longboy-schema/src/lib.rs new file mode 100644 index 0000000..5dd8570 --- /dev/null +++ b/crates/longboy-schema/src/lib.rs @@ -0,0 +1,19 @@ +use longboy::{ClientToServerSchema, ServerToClientSchema}; + + +pub fn new_client_to_server_schema() -> ClientToServerSchema{ + ClientToServerSchema { + name: "Input", + mapper_port: 8081, + heartbeat_period: 2000, + port: 8082, + } +} + +pub fn new_server_to_client_schema() -> ServerToClientSchema{ + ServerToClientSchema { + name: "State", + mapper_port: 8080, + heartbeat_period: 2000, + } +} \ No newline at end of file diff --git a/crates/longboy-server-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml index 086bad2..b46288c 100644 --- a/crates/longboy-server-cli/Cargo.toml +++ b/crates/longboy-server-cli/Cargo.toml @@ -9,6 +9,7 @@ config = "0.15.19" enum-map = "2.7.3" flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } +longboy_schema = { version = "1.0.0", path = "../longboy-schema" } quinn = "0.11.9" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 59111c4..27faa1b 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -4,21 +4,15 @@ use anyhow::{Context, Result}; use config::Config; -use enum_map::enum_map; use flume::{Receiver, Sender}; -use longboy::{ - ClientToServerSchema, Factory, Mirroring, Server, ServerSession, ServerToClientSchema, Sink, Source, ThreadRuntime, - TokioRuntime, -}; +use longboy::{Factory, Server, ServerSession, Sink, Source, ThreadRuntime, TokioRuntime}; +use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use std::{ - net::{SocketAddr, UdpSocket}, - sync::Arc, -}; +use std::{net::SocketAddr, sync::Arc}; use tokio_util::sync::CancellationToken; // This really could be a feature in the toml configuration crate. @@ -125,7 +119,10 @@ impl Source<32> for ServerToClientSource *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); - println!("SendFrame({}) PlayerInput0({}) PlayerInput1({})", frame, player_inputs[0], player_inputs[1]); + println!( + "SendFrame({}) PlayerInput0({}) PlayerInput1({})", + frame, player_inputs[0], player_inputs[1] + ); true } Err(_) => false, @@ -172,21 +169,9 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: }; // Setup the receivers for client-server communication. - let client_to_server_mapper_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - let client_to_server_socket = UdpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).unwrap(); - - println!("Ports {}, {}", client_to_server_mapper_socket.local_addr()?.port(), client_to_server_socket.local_addr()?.port()); - let client_to_server_schema = ClientToServerSchema { - name: "Input", - mapper_port: client_to_server_mapper_socket.local_addr()?.port(), - heartbeat_period: 2000, - port: client_to_server_socket.local_addr()?.port(), - }; - let server_to_client_schema = ServerToClientSchema { - name: "State", - mapper_port: 8080, - heartbeat_period: 2000, - }; + let server_to_client_schema = new_server_to_client_schema(); + let client_to_server_schema = new_client_to_server_schema(); + let receiver_channel = flume::unbounded(); let broadcast_channel = flume::unbounded(); let server_builder = Server::builder(config.session_capacity, server_runtime) @@ -212,15 +197,13 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: broadcast_channel.1.clone(), ], }, - )? - .receiver_with_socket::<_, 16, 3>( + ).unwrap() + .receiver::<_, 16, 3>( &client_to_server_schema, - client_to_server_mapper_socket, - client_to_server_socket, ClientToServerSinkFactory { channel: receiver_channel.0, }, - )?; + ).unwrap(); // Load TLS certificates. TLS is required. let provider = aws_lc_rs::default_provider(); diff --git a/docker/.docker-compose.yml b/docker/.docker-compose.yml new file mode 100644 index 0000000..fa76654 --- /dev/null +++ b/docker/.docker-compose.yml @@ -0,0 +1,38 @@ +services: + server: + build: + context: .. + dockerfile: docker/server.dockerfile + environment: + - LONGBOY_SERVER_RUNTIME_TYPE=Tokio + - LONGBOY_SERVER_SESSION_CAPACITY=1000 + - LONGBOY_SERVER_PUBLIC_CERTIFICATE_PATH=./localhost.crt + - LONGBOY_SERVER_PRIVATE_KEY_PATH=./localhost.key + - LONGBOY_SERVER_LISTEN_ADDRESS=0.0.0.0:4433 + networks: + longboy-network: + ipv4_address: 192.168.1.10 + + client: + build: + context: .. + dockerfile: docker/client.dockerfile + environment: + - LONGBOY_CLIENT_CERTIFICATE_TRUST_STORE=./rootCA.pem + - LONGBOY_CLIENT_SERVER_ADDRESS=192.168.1.10:4433 + - LONGBOY_CLIENT_SERVER_NAME=localhost + networks: + - longboy-network + depends_on: + - server + stdin_open: true + tty: true + command: /bin/bash + +networks: + longboy-network: + driver: bridge + ipam: + config: + - subnet: "192.168.1.0/24" + gateway: "192.168.1.1" \ No newline at end of file diff --git a/docker/client.dockerfile b/docker/client.dockerfile new file mode 100644 index 0000000..bab1cc7 --- /dev/null +++ b/docker/client.dockerfile @@ -0,0 +1,23 @@ +FROM rust:latest AS base + +FROM base AS sources + +WORKDIR /tmp/app + +COPY ./crates ./crates +COPY .rustfmt.toml . +COPY Cargo.toml . +COPY rust-toolchain.toml . +COPY crates/longboy-server-cli/test/data/ ./certs + +FROM sources AS build + +WORKDIR /tmp/app +RUN cargo build + +FROM rust:latest AS release + +WORKDIR /tmp/app + +COPY --from=sources /tmp/app/certs . +COPY --from=build /tmp/app/target/debug/longboy-client* . diff --git a/docker/server.dockerfile b/docker/server.dockerfile new file mode 100644 index 0000000..c1eec5b --- /dev/null +++ b/docker/server.dockerfile @@ -0,0 +1,25 @@ +FROM rust:latest AS base + +FROM base AS sources + +WORKDIR /tmp/app + +COPY ./crates ./crates +COPY .rustfmt.toml . +COPY Cargo.toml . +COPY rust-toolchain.toml . +COPY crates/longboy-server-cli/test/data/ ./certs + +FROM sources AS build + +WORKDIR /tmp/app +RUN cargo build + +FROM rust:latest AS release + +WORKDIR /tmp/app + +COPY --from=sources /tmp/app/certs . +COPY --from=build /tmp/app/target/debug/longboy-server* . + +ENTRYPOINT ["/tmp/app/longboy-server-cli"] \ No newline at end of file From ae6063209d1876a63100b644246b9a68601e5bfc Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Tue, 27 Jan 2026 01:04:59 -0600 Subject: [PATCH 08/23] fix: overflow, add logging --- crates/longboy-client-cli/src/main.rs | 32 +-- crates/longboy-schema/src/lib.rs | 4 +- crates/longboy-server-cli/src/README.md | 16 ++ crates/longboy-server-cli/src/broker.rs | 197 ++++++++++++++++++ crates/longboy-server-cli/src/main.rs | 140 ++----------- .../src/client/client_to_server_sender.rs | 4 + .../src/client/server_to_client_receiver.rs | 1 + crates/longboy/src/proto/receiver.rs | 4 +- .../src/server/client_to_server_receiver.rs | 9 + .../src/server/server_to_client_sender.rs | 2 + docker/.docker-compose.yml | 2 + 11 files changed, 268 insertions(+), 143 deletions(-) create mode 100644 crates/longboy-server-cli/src/README.md create mode 100644 crates/longboy-server-cli/src/broker.rs diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 8346a58..9eeeab6 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -4,27 +4,25 @@ #![feature(unboxed_closures)] use std::{ - net::{SocketAddr}, - process, + net::SocketAddr, sync::Arc, - thread::{self}, time::Duration, }; use anyhow::{Context, Result}; use config::Config; use crossterm::{ - event::{self, Event, KeyCode}, + event::{self, Event, KeyCode, poll}, terminal::{disable_raw_mode, enable_raw_mode}, }; use longboy::{Client, ClientSession, Sink, Source}; +use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; use quinn::{ClientConfig, Endpoint}; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, pem::PemObject}, }; use rustls_native_certs::load_native_certs; -use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; #[derive(serde::Deserialize)] struct LongboyClientConfig @@ -196,26 +194,28 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( }); let mut frame: u32 = 0; - // init with some garbage - sender_channel.0.send((frame, u64::from('z')))?; loop { // send keys - if let Event::Key(key_event) = event::read().unwrap() + if poll(Duration::from_millis(10))? { - match key_event.code + if let Event::Key(key_event) = event::read().unwrap() { - KeyCode::Esc => process::exit(0), - KeyCode::Char(val) => + match key_event.code { - println!("Sending Frame({}) {}", frame, val); - let as64 = u64::from(val); - sender_channel.0.send((frame, as64)).unwrap(); + KeyCode::Esc => break, + KeyCode::Char(val) => + { + print!("Sending Frame({}) {}", frame, val); + let as64 = u64::from(val); + sender_channel.0.send((frame, as64)).unwrap(); + } + _ => (), } - _ => (), } } frame += 1; - thread::sleep(Duration::from_millis(1)); } + + Ok(()) } diff --git a/crates/longboy-schema/src/lib.rs b/crates/longboy-schema/src/lib.rs index 5dd8570..9ea4744 100644 --- a/crates/longboy-schema/src/lib.rs +++ b/crates/longboy-schema/src/lib.rs @@ -5,7 +5,7 @@ pub fn new_client_to_server_schema() -> ClientToServerSchema{ ClientToServerSchema { name: "Input", mapper_port: 8081, - heartbeat_period: 2000, + heartbeat_period: 100, port: 8082, } } @@ -14,6 +14,6 @@ pub fn new_server_to_client_schema() -> ServerToClientSchema{ ServerToClientSchema { name: "State", mapper_port: 8080, - heartbeat_period: 2000, + heartbeat_period: 100, } } \ No newline at end of file diff --git a/crates/longboy-server-cli/src/README.md b/crates/longboy-server-cli/src/README.md new file mode 100644 index 0000000..18540bd --- /dev/null +++ b/crates/longboy-server-cli/src/README.md @@ -0,0 +1,16 @@ + +# Longboy Server CLI Architecture + +## Broker Module Overview + +```mermaid +graph TD + A[SessionBroker] -->|Manages| B[ClientBroker] + A -->|Manages| C[ServerBroker] + B -->|Sends| D[ClientToServerSink] + C -->|Receives| E[ServerToClientSource] + A -->|Tracks| F[PlayerSessions] + A -->|Tracks| G[PlayerIndexFreeList] +``` + +This diagram illustrates how the broker module manages client connections, routes messages through a queue system, and maintains client state through a registry. diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs new file mode 100644 index 0000000..f5c29c1 --- /dev/null +++ b/crates/longboy-server-cli/src/broker.rs @@ -0,0 +1,197 @@ +use flume::{Receiver, Sender}; +use longboy::{Factory, Sink, Source}; +use std::{collections::HashMap, sync::{Arc, RwLock, atomic::{AtomicU64}}}; +use anyhow::{Result}; +use std::sync::atomic::Ordering::SeqCst; + +pub struct SessionBroker +{ + receiver_channels: [Receiver<(u32, [u64; 2])>; MAX_PLAYERS], + broadcast_channel: Sender<(u32, u8, u64)>, + // thread safe atomic always incrementing session id + next_session_id: AtomicU64, + // key is the session id, value is the player index + player_sessions: Arc>>, + // thread safe atomic free pointer to the next player index + player_index_free_list: Arc>>, +} + +impl SessionBroker +{ + pub fn new() -> Self + { + let receiver_channels: [Receiver<(u32, [u64; 2])>; MAX_PLAYERS] = + [(); MAX_PLAYERS].map(|_| { + let (_s, r) = flume::unbounded(); + r + }); + + let broadcast_channel = flume::unbounded().0; + + Self { + receiver_channels, + broadcast_channel, + next_session_id: AtomicU64::new(1), + player_sessions: Arc::new(RwLock::new(HashMap::new())), + player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())), + } + } + + pub fn allocate_session_id(&self, player_index: usize) -> u64 + { + let session_id = self.next_session_id.fetch_add(1, SeqCst); + let insert = self.player_sessions.write().unwrap().insert(session_id, player_index); + match insert + { + Some(old_player_index) => println!("Warning: Overwriting existing session {} for player index {} with {}", old_player_index, player_index, session_id), + None => println!("Allocated session id {} for player index {}", session_id, player_index), + } + session_id + } + + pub fn next_player_index(&self) -> Result + { + let mut free_list = self.player_index_free_list.write().unwrap(); + let player_index = free_list.iter().position(|&is_free| is_free); + match player_index + { + Some(index) => + { + free_list[index] = false; + Ok(index) + } + None => anyhow::bail!("Maximum number of players reached"), + } + } + + // pub fn free_player_index(&self, player_index: usize) + // { + // let mut free_list = self.player_index_free_list.write().unwrap(); + // free_list[player_index] = true; + // let mut sessions = self.player_sessions.write().unwrap(); + // let session_id = sessions.iter().find_map(|(&s_id, &p_index)| if p_index == player_index { Some(s_id) } else { None }); + // if let Some(s_id) = session_id + // { + // sessions.remove(&s_id); + // println!("Freed session id {} for player index {}", s_id, player_index); + // } + // } +} + + +pub struct ClientToServerSink +{ + player_index: u8, + channel: Sender<(u32, u8, u64)>, +} + +pub struct ServerToClientSource +{ + channel: Receiver<(u32, [u64; 2])>, +} + +pub struct ServerBroker +{ + inner: Arc>, +} + +impl ServerBroker +{ + pub fn new(session_broker: Arc>) -> Self + { + Self { + inner: session_broker, + } + } +} + +impl Factory for ServerBroker +{ + type Type = ServerToClientSource; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let binding = self + .inner + .player_sessions + .read() + .unwrap(); + let player_index = binding + .get(&session_id) + .expect("Unknown Session ID"); + + ServerToClientSource { + channel: self.inner.receiver_channels[*player_index].clone(), + } + } +} + +pub struct ClientBroker +{ + inner: Arc>, +} + +impl ClientBroker +{ + pub fn new(session_broker: Arc>) -> Self + { + Self { + inner: session_broker, + } + } +} + +impl Factory for ClientBroker +{ + type Type = ClientToServerSink; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let binding = self + .inner + .player_sessions + .read() + .unwrap(); + let player_index = binding + .get(&session_id) + .expect("Unknown Session ID"); + + ClientToServerSink { + player_index: *player_index as u8, + channel: self.inner.broadcast_channel.clone(), + } + } +} + +impl Sink<16> for ClientToServerSink +{ + fn handle(&mut self, buffer: &[u8; 16]) + { + let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); + let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); + println!("Recv'd Frame({}) PlayerInput({})", frame, player_input); + self.channel.send((frame, self.player_index, player_input)).unwrap(); + } +} + +impl Source<32> for ServerToClientSource +{ + fn poll(&mut self, buffer: &mut [u8; 32]) -> bool + { + match self.channel.try_recv() + { + Ok((frame, player_inputs)) => + { + *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); + println!( + "SendFrame({}) PlayerInput0({}) PlayerInput1({})", + frame, player_inputs[0], player_inputs[1] + ); + true + } + Err(_) => false, + } + } +} \ No newline at end of file diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 27faa1b..efdf7b3 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -2,10 +2,11 @@ #![feature(generic_const_exprs)] #![feature(generic_const_items)] +mod broker; + use anyhow::{Context, Result}; use config::Config; -use flume::{Receiver, Sender}; -use longboy::{Factory, Server, ServerSession, Sink, Source, ThreadRuntime, TokioRuntime}; +use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; use rustls::{ @@ -15,6 +16,8 @@ use rustls::{ use std::{net::SocketAddr, sync::Arc}; use tokio_util::sync::CancellationToken; +use crate::broker::{ClientBroker, ServerBroker, SessionBroker}; + // This really could be a feature in the toml configuration crate. #[derive(serde::Deserialize)] enum LongboyRuntimeType @@ -36,100 +39,6 @@ struct LongboyServerConfig listen_address: Option, } -// Server runtime sender and receiver behaviors. -struct ServerToClientSourceFactory -{ - channels: [Receiver<(u32, [u64; 2])>; 16], -} - -struct ClientToServerSinkFactory -{ - channel: Sender<(u32, u8, u64)>, -} - -struct ClientToServerSink -{ - player_index: u8, - channel: Sender<(u32, u8, u64)>, -} - -struct ServerToClientSource -{ - channel: Receiver<(u32, [u64; 2])>, -} - -impl Factory for ServerToClientSourceFactory -{ - type Type = ServerToClientSource; - - fn invoke(&mut self, session_id: u64) -> Self::Type - { - let player_index = match session_id - { - 1 => 0, - 2 => 1, - _ => unreachable!(), - }; - - ServerToClientSource { - channel: self.channels[player_index].clone(), - } - } -} - -impl Factory for ClientToServerSinkFactory -{ - type Type = ClientToServerSink; - - fn invoke(&mut self, session_id: u64) -> Self::Type - { - let player_index = match session_id - { - 1 => 0, - 2 => 1, - _ => unreachable!(), - }; - - ClientToServerSink { - player_index, - channel: self.channel.clone(), - } - } -} - -impl Sink<16> for ClientToServerSink -{ - fn handle(&mut self, buffer: &[u8; 16]) - { - let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); - let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); - println!("Recv'd Frame({}) PlayerInput({})", frame, player_input); - self.channel.send((frame, self.player_index, player_input)).unwrap(); - } -} - -impl Source<32> for ServerToClientSource -{ - fn poll(&mut self, buffer: &mut [u8; 32]) -> bool - { - match self.channel.try_recv() - { - Ok((frame, player_inputs)) => - { - *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); - println!( - "SendFrame({}) PlayerInput0({}) PlayerInput1({})", - frame, player_inputs[0], player_inputs[1] - ); - true - } - Err(_) => false, - } - } -} - /// Entry point of the application. /// /// This function collects configuration settings and starts the server. @@ -168,41 +77,22 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: } }; + let broker = std::sync::Arc::new(SessionBroker::<32>::new()); + let client_broker = ClientBroker::new(broker.clone().into()); + let server_broker = ServerBroker::new(broker.clone().into()); + // Setup the receivers for client-server communication. let server_to_client_schema = new_server_to_client_schema(); let client_to_server_schema = new_client_to_server_schema(); - let receiver_channel = flume::unbounded(); - let broadcast_channel = flume::unbounded(); let server_builder = Server::builder(config.session_capacity, server_runtime) .sender::<_, 32, 3>( &server_to_client_schema, - ServerToClientSourceFactory { - channels: [ - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - broadcast_channel.1.clone(), - ], - }, + server_broker ).unwrap() .receiver::<_, 16, 3>( &client_to_server_schema, - ClientToServerSinkFactory { - channel: receiver_channel.0, - }, + client_broker ).unwrap(); // Load TLS certificates. TLS is required. @@ -258,6 +148,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: // print stats from server endpoint println!("Server endpoint stats: {:?}", server_endpoint.stats()); + // start accepting connections while let Some(conn) = server_endpoint.accept().await { @@ -284,7 +175,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Ok(connection) => { // handle the connection - if let Err(e) = server_handle_connection(connection, &mut server_instance).await + if let Err(e) = server_handle_connection(connection, &mut server_instance, &broker).await { println!("Error handling connection: {:?}", e); } @@ -297,12 +188,13 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Ok(()) } -async fn server_handle_connection(conn: Connection, server: &mut Server) -> Result<()> +async fn server_handle_connection(conn: Connection, server: &mut Server, broker: &std::sync::Arc>) -> Result<()> { let remote_address = conn.remote_address(); println!("New connection from {}", remote_address); - let session_id = 1; // Should have an accessible server session manager (with player index mapping) + let player_index = broker.next_player_index()?; + let session_id = broker.allocate_session_id(player_index); let cipher_key = 0xdeadbeef; let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? diff --git a/crates/longboy/src/client/client_to_server_sender.rs b/crates/longboy/src/client/client_to_server_sender.rs index 00f2f72..13f8961 100644 --- a/crates/longboy/src/client/client_to_server_sender.rs +++ b/crates/longboy/src/client/client_to_server_sender.rs @@ -86,6 +86,8 @@ where for (mirroring, socket) in self.sockets.iter() { buffer[8] = Mirroring::into_usize(mirroring) as u8; + // debug sent buffer + print!("Sending heartbeat buffer: {:?}", buffer); socket .send_to(&buffer, self.mapper_socket_addr) .expect("send_to failure"); @@ -97,8 +99,10 @@ where // Poll Session if let Some(datagram) = self.sender.poll_datagram(timestamp) { + print!("Sending datagram: {:?}", datagram); for socket in self.sockets.values() { + // debug sent datagram socket.send_to(datagram, self.socket_addr).expect("send_to failure"); } } diff --git a/crates/longboy/src/client/server_to_client_receiver.rs b/crates/longboy/src/client/server_to_client_receiver.rs index 173b2a0..4092e78 100644 --- a/crates/longboy/src/client/server_to_client_receiver.rs +++ b/crates/longboy/src/client/server_to_client_receiver.rs @@ -90,6 +90,7 @@ where let mut buffer = [0; 512]; while let Ok((len, _)) = self.socket.recv_from(&mut buffer) { + println!("Received datagram of length {}", len); if len != DATAGRAM_SIZE { continue; diff --git a/crates/longboy/src/proto/receiver.rs b/crates/longboy/src/proto/receiver.rs index c56876f..3bf46b4 100644 --- a/crates/longboy/src/proto/receiver.rs +++ b/crates/longboy/src/proto/receiver.rs @@ -60,9 +60,11 @@ where let datagram_cycle = u16::from_le_bytes(*<&[u8; 2]>::try_from(&datagram[0..2]).unwrap()) as usize; let datagram_timestamp = u16::from_le_bytes(*<&[u8; 2]>::try_from(&datagram[2..4]).unwrap()); + println!("Received datagram with cycle {} and timestamp {}, my cycle is {} and my timestamp is {}", datagram_cycle, datagram_timestamp, self.cycle, timestamp); + // Calculate diff for cycle and timestamp. let cycle_diff = ((datagram_cycle + MAX_CYCLE) - self.cycle) % MAX_CYCLE; - let timestamp_diff = ((datagram_timestamp + u16::MAX) - timestamp) % u16::MAX; + let timestamp_diff = (((datagram_timestamp as u32 + u16::MAX as u32) - timestamp as u32) % (u16::MAX as u32)) as u16; // Check for bad datagrams or late datagrams that are already processed. Because // we ensure only a positive diff, this is done by checking for any values greater diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index 06147e6..77162cc 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -128,6 +128,7 @@ where let mut buffer = [0; 64]; while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { + println!("Received mapper datagram from {:?}, length {}", socket_addr, len); if len != std::mem::size_of::() + std::mem::size_of::() { continue; @@ -136,6 +137,13 @@ where let session_id = u64::from_le_bytes(*<&[u8; 8]>::try_from(&buffer[0..8]).unwrap()); let mirroring = buffer[8] as usize; + + println!( + "Mapping session_id {} mirroring {} to socket_addr {:?}", + session_id, + mirroring, + socket_addr + ); if mirroring >= Mirroring::LENGTH { continue; @@ -160,6 +168,7 @@ where let mut buffer = [0; 512]; while let Ok((len, socket_addr)) = self.socket.recv_from(&mut buffer) { + println!("Received datagram from {:?}, length {}", socket_addr, len); if len != DATAGRAM_SIZE { continue; diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 4c4cd7c..1f0e22d 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -124,6 +124,7 @@ where } let session_id = u64::from_le_bytes(*<&[u8; 8]>::try_from(&buffer[0..8]).unwrap()); + println!("Received heartbeat from session_id {}, socket_addr {:?}", session_id, socket_addr); if let Some(index) = self.session_id_to_session_map.get(&session_id) { @@ -139,6 +140,7 @@ where { for socket in self.sockets.values() { + println!("Sending datagram to {:?}: {:?}", socket_addr, datagram); socket.send_to(datagram, socket_addr).expect("send_to failure"); } } diff --git a/docker/.docker-compose.yml b/docker/.docker-compose.yml index fa76654..3852c02 100644 --- a/docker/.docker-compose.yml +++ b/docker/.docker-compose.yml @@ -28,6 +28,8 @@ services: stdin_open: true tty: true command: /bin/bash + deploy: + replicas: 2 networks: longboy-network: From 52b4f87c7093b44705a6f36a729ada119d7ae86f Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Wed, 28 Jan 2026 06:22:39 -0600 Subject: [PATCH 09/23] feat: nailed down proto and flumage, scale to 16 players for testing --- crates/longboy-client-cli/Cargo.toml | 1 + crates/longboy-client-cli/src/main.rs | 58 +++++++++++-------------- crates/longboy-server-cli/src/broker.rs | 22 +++++----- docker/.docker-compose.yml | 3 +- docker/client.dockerfile | 2 + 5 files changed, 41 insertions(+), 45 deletions(-) diff --git a/crates/longboy-client-cli/Cargo.toml b/crates/longboy-client-cli/Cargo.toml index 362ba12..3d59f2e 100644 --- a/crates/longboy-client-cli/Cargo.toml +++ b/crates/longboy-client-cli/Cargo.toml @@ -11,6 +11,7 @@ flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } longboy_schema = { version = "1.0.0", path = "../longboy-schema" } quinn = "0.11.9" +rand = "0.9.2" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } rustls-native-certs = "0.8.2" serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 9eeeab6..1f3373a 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -11,10 +11,6 @@ use std::{ use anyhow::{Context, Result}; use config::Config; -use crossterm::{ - event::{self, Event, KeyCode, poll}, - terminal::{disable_raw_mode, enable_raw_mode}, -}; use longboy::{Client, ClientSession, Sink, Source}; use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; use quinn::{ClientConfig, Endpoint}; @@ -23,6 +19,8 @@ use rustls::{ pki_types::{CertificateDer, pem::PemObject}, }; use rustls_native_certs::load_native_certs; +use tokio::{time::sleep}; +use tokio_util::sync::CancellationToken; #[derive(serde::Deserialize)] struct LongboyClientConfig @@ -34,7 +32,7 @@ struct LongboyClientConfig struct ServerToClientSink { - channel: flume::Sender<(u32, [u64; 2])>, + channel: flume::Sender<(u32, u8, u64)>, } struct ClientToServerSource @@ -66,16 +64,14 @@ impl Sink<32> for ServerToClientSink fn handle(&mut self, buffer: &[u8; 32]) { let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); - let player_input_1 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); - let player_input_2 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[12..20]).unwrap())); - self.channel.send((frame, [player_input_1, player_input_2])).unwrap(); + let player_id = u8::from_le_bytes(*(<&[u8; 1]>::try_from(&buffer[4..5]).unwrap())); + let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[5..13]).unwrap())); + self.channel.send((frame, player_id, player_input)).unwrap(); } } fn main() { - let _ = enable_raw_mode(); // Enters raw mode - let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); // Setup the configuration builder for the server. Let environment variables take the highest precedence. let settings = Config::builder() @@ -90,8 +86,6 @@ fn main() { eprintln!("Error running longboy client: {err:#}"); } - - let _ = disable_raw_mode(); // Exits raw mode } #[tokio::main] @@ -151,8 +145,11 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( // Create longboy client sessions let client_session = ClientSession::new(connection).await?; + // Bind the cancellation token to sigterm handler + let cancellation_token = CancellationToken::new(); + // Session established, create the longboy client using tokyo runtime. - let runtime = longboy::TokioRuntime::new(tokio_util::sync::CancellationToken::new()); + let runtime = longboy::TokioRuntime::new(cancellation_token.child_token()); let client_to_server_schema = new_client_to_server_schema(); let server_to_client_schema = new_server_to_client_schema(); let receiver_channel = flume::unbounded(); @@ -184,11 +181,15 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( match incoming { - Ok((frame, [first, second])) => + Ok((frame, player_id, player_input)) => + { + println!("Recv'd Frame({}) PlayerID({}) PlayerInput({})", frame, player_id, player_input); + } + Err(e) => { - println!("Recv'd Frame({}) ({} ; {})", frame, first, second) + println!("{}", e); + break } - Err(e) => println!("{}", e), } } }); @@ -196,25 +197,18 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( let mut frame: u32 = 0; loop { - // send keys - if poll(Duration::from_millis(10))? + if cancellation_token.is_cancelled() { - if let Event::Key(key_event) = event::read().unwrap() - { - match key_event.code - { - KeyCode::Esc => break, - KeyCode::Char(val) => - { - print!("Sending Frame({}) {}", frame, val); - let as64 = u64::from(val); - sender_channel.0.send((frame, as64)).unwrap(); - } - _ => (), - } - } + break; } + // send keys + sleep(Duration::from_millis(100)).await; + // random ascii between 32 and 126 + let player_input: u64 = rand::random::() as u64 % (126 - 32) + 32; + println!("Sending Frame({}) PlayerInput({})", frame, player_input); + sender_channel.0.send((frame, player_input)).unwrap(); frame += 1; + println!("Frame {}", frame); } Ok(()) diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index f5c29c1..655698f 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -6,7 +6,7 @@ use std::sync::atomic::Ordering::SeqCst; pub struct SessionBroker { - receiver_channels: [Receiver<(u32, [u64; 2])>; MAX_PLAYERS], + receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS], broadcast_channel: Sender<(u32, u8, u64)>, // thread safe atomic always incrementing session id next_session_id: AtomicU64, @@ -20,13 +20,14 @@ impl SessionBroker { pub fn new() -> Self { - let receiver_channels: [Receiver<(u32, [u64; 2])>; MAX_PLAYERS] = + let pipe = flume::unbounded(); + let receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS] = [(); MAX_PLAYERS].map(|_| { - let (_s, r) = flume::unbounded(); + let (_s, r) = pipe.clone(); r }); - let broadcast_channel = flume::unbounded().0; + let broadcast_channel = pipe.0; Self { receiver_channels, @@ -87,7 +88,7 @@ pub struct ClientToServerSink pub struct ServerToClientSource { - channel: Receiver<(u32, [u64; 2])>, + channel: Receiver<(u32, u8, u64)>, } pub struct ServerBroker @@ -180,15 +181,12 @@ impl Source<32> for ServerToClientSource { match self.channel.try_recv() { - Ok((frame, player_inputs)) => + Ok((frame, player_id, player_input)) => { *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = player_inputs[0].to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[12..20]).unwrap()) = player_inputs[1].to_le_bytes(); - println!( - "SendFrame({}) PlayerInput0({}) PlayerInput1({})", - frame, player_inputs[0], player_inputs[1] - ); + *(<&mut [u8; 1]>::try_from(&mut buffer[4..5]).unwrap()) = player_id.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[5..13]).unwrap()) = player_input.to_le_bytes(); + println!("Sending Frame({}) PlayerID({}) PlayerInput({})", frame, player_id, player_input); true } Err(_) => false, diff --git a/docker/.docker-compose.yml b/docker/.docker-compose.yml index 3852c02..8c8fed5 100644 --- a/docker/.docker-compose.yml +++ b/docker/.docker-compose.yml @@ -1,5 +1,6 @@ services: server: + container_name: longboy_server build: context: .. dockerfile: docker/server.dockerfile @@ -29,7 +30,7 @@ services: tty: true command: /bin/bash deploy: - replicas: 2 + replicas: 16 networks: longboy-network: diff --git a/docker/client.dockerfile b/docker/client.dockerfile index bab1cc7..e8f12a8 100644 --- a/docker/client.dockerfile +++ b/docker/client.dockerfile @@ -21,3 +21,5 @@ WORKDIR /tmp/app COPY --from=sources /tmp/app/certs . COPY --from=build /tmp/app/target/debug/longboy-client* . + +ENTRYPOINT ["/tmp/app/longboy-client-cli"] \ No newline at end of file From ba472ff93fdd34eb372e5471791090950dc98d8d Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Wed, 4 Feb 2026 23:39:35 -0600 Subject: [PATCH 10/23] chore: add script to generate localhost certificates and chain on demand --- generate-certs.ps1 | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 generate-certs.ps1 diff --git a/generate-certs.ps1 b/generate-certs.ps1 new file mode 100644 index 0000000..cafbe68 --- /dev/null +++ b/generate-certs.ps1 @@ -0,0 +1,128 @@ +# PowerShell script to generate root CA, CSR, and signed localhost certificate in PEM format +# Usage: .\generate-certs.ps1 [-OutputPath ] +# If -OutputPath is not specified, certificates will be generated in the script's directory + +param( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$OutputPath = (Split-Path -Parent $MyInvocation.MyCommand.Path) +) + +# Ensure output directory exists +if (-not (Test-Path $OutputPath)) { + New-Item -ItemType Directory -Path $OutputPath | Out-Null +} + +Write-Host "Generating certificates in: $OutputPath" + +# Step 1: Create Root CA Certificate +Write-Host "`n[1/5] Creating Root CA certificate..." +$rootCA = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=Root CA,O=Test Organization,C=US" ` + -KeyUsage CertSign, CRLSign ` + -KeyLength 2048 ` + -NotAfter (Get-Date).AddYears(10) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Root CA Thumbprint: $($rootCA.Thumbprint)" + +# Step 2: Create a server certificate request +Write-Host "`n[2/5] Creating server certificate signing request..." +$serverCert = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=localhost,O=Test Organization,C=US" ` + -DnsName localhost, 127.0.0.1, "::1" ` + -KeyUsage DigitalSignature, KeyEncipherment ` + -KeyLength 2048 ` + -NotAfter (Get-Date).AddYears(1) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Server Cert Thumbprint: $($serverCert.Thumbprint)" + +# Step 3: Sign the server certificate with root CA +Write-Host "`n[3/5] Signing server certificate with Root CA..." +$signedCert = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=localhost,O=Test Organization,C=US" ` + -DnsName localhost, 127.0.0.1, "::1" ` + -KeyUsage DigitalSignature, KeyEncipherment ` + -KeyLength 2048 ` + -Signer $rootCA ` + -NotAfter (Get-Date).AddYears(1) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Signed Cert Thumbprint: $($signedCert.Thumbprint)" + +# Step 4: Export Root CA to PEM format +Write-Host "`n[4/5] Exporting Root CA to PEM format..." +$rootCAPath = Join-Path $OutputPath "rootCA.pem" +$rootCABytes = $rootCA.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) +$rootCABase64 = [Convert]::ToBase64String($rootCABytes, 'InsertLineBreaks') +$rootCAContent = "-----BEGIN CERTIFICATE-----`r`n$rootCABase64`r`n-----END CERTIFICATE-----" +Set-Content -Path $rootCAPath -Value $rootCAContent -Encoding UTF8 + +Write-Host "Root CA saved to: $rootCAPath" + +# Step 5: Export signed server certificate and private key to PEM format +Write-Host "`n[5/5] Exporting server certificate and private key to PEM format..." + +# Export certificate +$certPath = Join-Path $OutputPath "localhost.crt" +$certBytes = $signedCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) +$certBase64 = [Convert]::ToBase64String($certBytes, 'InsertLineBreaks') +$certContent = "-----BEGIN CERTIFICATE-----`r`n$certBase64`r`n-----END CERTIFICATE-----" +Set-Content -Path $certPath -Value $certContent -Encoding UTF8 + +Write-Host "Certificate saved to: $certPath" + +# Export private key +$keyPath = Join-Path $OutputPath "localhost.key" + +# Retrieve the certificate with private key from the store +$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) +$certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) +$certs = $certStore.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $signedCert.Thumbprint, $false) +$certWithKey = $certs[0] +$certStore.Close() + +# Get the RSA private key using the extension method +$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certWithKey) + +# Export as PKCS8 format +[System.Security.Cryptography.CngKey]$cngKey = $null +$keyObject = $rsaKey +if ($keyObject -is [System.Security.Cryptography.RSACng]) { + # RSACng has a Key property that gives us the CngKey + $cngKey = $keyObject.Key + # Export PKCS8 from CNG key + $keyBytes = $cngKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) + $keyContent = "-----BEGIN PRIVATE KEY-----`r`n$([Convert]::ToBase64String($keyBytes, 'InsertLineBreaks'))`r`n-----END PRIVATE KEY-----" +} else { + # Fallback for RSA implementations + Write-Host "Exporting key as PKCS1 RSA format..." + # For systems without ExportPkcs8PrivateKey, we'll use a base64 encoded export + # This is a workaround for systems without the newer methods + Write-Warning "Using fallback method for private key export" + $keyBytes = $rsaKey.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) + $keyContent = "-----BEGIN PRIVATE KEY-----`r`n$([Convert]::ToBase64String($keyBytes, 'InsertLineBreaks'))`r`n-----END PRIVATE KEY-----" +} + +$keyBase64 = [Convert]::ToBase64String($keyBytes, 'InsertLineBreaks') +$keyContent = "-----BEGIN PRIVATE KEY-----`r`n$keyBase64`r`n-----END PRIVATE KEY-----" +Set-Content -Path $keyPath -Value $keyContent -Encoding UTF8 + +Write-Host "Private key saved to: $keyPath" + +# Verify files were created +Write-Host "" +Write-Host "[OK] Certificate generation complete!" +Write-Host "Generated files:" +Get-Item -Path @($rootCAPath, $certPath, $keyPath) -ErrorAction SilentlyContinue | ForEach-Object { + $size = $_.Length + Write-Host " - $($_.Name) ($size bytes)" +} + +Write-Host "" +Write-Host "Trust relationship: localhost.crt is signed by rootCA.pem" +Write-Host "A client with rootCA.pem can verify the localhost.crt certificate." From eabd0492a33711415af782721442572997a578cf Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Thu, 5 Feb 2026 00:11:52 -0600 Subject: [PATCH 11/23] fix: add windows debug configuration for server-cli --- .vscode/launch.json | 33 +++++++++++++++++++++++++++++++++ generate-certs.ps1 | 10 ++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8845dbf..dff919c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,27 @@ { "version": "0.2.0", "configurations": [ + { + "name": "(Windows) Launch longboy-server-cli", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "externalConsole": true + }, { "type": "cppdbg", "request": "launch", @@ -45,6 +66,10 @@ "sourceFileMap": {}, "osx": { "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe" } }, { @@ -55,6 +80,10 @@ "processId": "${command:pickProcess}", // Provides a filterable list of running processes "osx": { "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe" } }, { @@ -65,6 +94,10 @@ "processId": "${command:pickProcess}", // Provides a filterable list of running processes "osx": { "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-client-cli.exe" } } ] diff --git a/generate-certs.ps1 b/generate-certs.ps1 index cafbe68..be13827 100644 --- a/generate-certs.ps1 +++ b/generate-certs.ps1 @@ -89,7 +89,7 @@ $certStore.Close() # Get the RSA private key using the extension method $rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certWithKey) -# Export as PKCS8 format +# Export as PKCS8 format (required by Rust's PrivateKeyDer::from_pem_file()) [System.Security.Cryptography.CngKey]$cngKey = $null $keyObject = $rsaKey if ($keyObject -is [System.Security.Cryptography.RSACng]) { @@ -97,15 +97,9 @@ if ($keyObject -is [System.Security.Cryptography.RSACng]) { $cngKey = $keyObject.Key # Export PKCS8 from CNG key $keyBytes = $cngKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) - $keyContent = "-----BEGIN PRIVATE KEY-----`r`n$([Convert]::ToBase64String($keyBytes, 'InsertLineBreaks'))`r`n-----END PRIVATE KEY-----" } else { - # Fallback for RSA implementations - Write-Host "Exporting key as PKCS1 RSA format..." - # For systems without ExportPkcs8PrivateKey, we'll use a base64 encoded export - # This is a workaround for systems without the newer methods - Write-Warning "Using fallback method for private key export" + # Fallback for other RSA implementations $keyBytes = $rsaKey.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) - $keyContent = "-----BEGIN PRIVATE KEY-----`r`n$([Convert]::ToBase64String($keyBytes, 'InsertLineBreaks'))`r`n-----END PRIVATE KEY-----" } $keyBase64 = [Convert]::ToBase64String($keyBytes, 'InsertLineBreaks') From 020fad6cfe9fbeefd5284a7ae8702c71bb9f3b64 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Thu, 5 Feb 2026 00:17:32 -0600 Subject: [PATCH 12/23] fix: no BOM, UTF8 --- crates/longboy-server-cli/src/main.rs | 8 ++++---- generate-certs.ps1 | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index efdf7b3..50d9b72 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -105,18 +105,18 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let key = if key_path.extension().is_some_and(|x| x == "der") { PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - std::fs::read(key_path).context("failed to read private key file")?, + std::fs::read(key_path).context("failed to read private key file").unwrap(), )) } else { - PrivateKeyDer::from_pem_file(key_path).context("failed to read PEM from private key file")? + PrivateKeyDer::from_pem_file(key_path).context("failed to read PEM from private key file").unwrap() }; let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") { vec![CertificateDer::from( - std::fs::read(cert_path).context("failed to read certificate chain file")?, + std::fs::read(cert_path).context("failed to read certificate chain file").unwrap(), )] } else @@ -124,7 +124,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: CertificateDer::pem_file_iter(cert_path) .context("failed to read PEM from certificate chain file")? .collect::>() - .context("invalid PEM-encoded certificate")? + .context("invalid PEM-encoded certificate").unwrap() }; (cert_chain, key) diff --git a/generate-certs.ps1 b/generate-certs.ps1 index be13827..bdd5d1d 100644 --- a/generate-certs.ps1 +++ b/generate-certs.ps1 @@ -59,8 +59,10 @@ Write-Host "`n[4/5] Exporting Root CA to PEM format..." $rootCAPath = Join-Path $OutputPath "rootCA.pem" $rootCABytes = $rootCA.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) $rootCABase64 = [Convert]::ToBase64String($rootCABytes, 'InsertLineBreaks') -$rootCAContent = "-----BEGIN CERTIFICATE-----`r`n$rootCABase64`r`n-----END CERTIFICATE-----" -Set-Content -Path $rootCAPath -Value $rootCAContent -Encoding UTF8 +$rootCAContent = "-----BEGIN CERTIFICATE-----`n$rootCABase64`n-----END CERTIFICATE-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($rootCAPath, $rootCAContent, $utf8NoBOM) Write-Host "Root CA saved to: $rootCAPath" @@ -71,8 +73,10 @@ Write-Host "`n[5/5] Exporting server certificate and private key to PEM format.. $certPath = Join-Path $OutputPath "localhost.crt" $certBytes = $signedCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) $certBase64 = [Convert]::ToBase64String($certBytes, 'InsertLineBreaks') -$certContent = "-----BEGIN CERTIFICATE-----`r`n$certBase64`r`n-----END CERTIFICATE-----" -Set-Content -Path $certPath -Value $certContent -Encoding UTF8 +$certContent = "-----BEGIN CERTIFICATE-----`n$certBase64`n-----END CERTIFICATE-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($certPath, $certContent, $utf8NoBOM) Write-Host "Certificate saved to: $certPath" @@ -103,8 +107,10 @@ if ($keyObject -is [System.Security.Cryptography.RSACng]) { } $keyBase64 = [Convert]::ToBase64String($keyBytes, 'InsertLineBreaks') -$keyContent = "-----BEGIN PRIVATE KEY-----`r`n$keyBase64`r`n-----END PRIVATE KEY-----" -Set-Content -Path $keyPath -Value $keyContent -Encoding UTF8 +$keyContent = "-----BEGIN PRIVATE KEY-----`n$keyBase64`n-----END PRIVATE KEY-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($keyPath, $keyContent, $utf8NoBOM) Write-Host "Private key saved to: $keyPath" From 6a1e28914547453f2264cb1de1e7e41472b94671 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Thu, 5 Feb 2026 22:57:05 -0600 Subject: [PATCH 13/23] chore(comment): some comments --- crates/longboy/src/server/client_to_server_receiver.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index 77162cc..ff8909b 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -126,6 +126,7 @@ where // Update Client socket addresses. let mut buffer = [0; 64]; + // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { println!("Received mapper datagram from {:?}, length {}", socket_addr, len); @@ -165,6 +166,7 @@ where } // Process datagrams. + // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. let mut buffer = [0; 512]; while let Ok((len, socket_addr)) = self.socket.recv_from(&mut buffer) { From 43f6a394e7351f21d27832b0ef0ac03dc9772e18 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 6 Feb 2026 00:02:32 -0600 Subject: [PATCH 14/23] feat(tracing): spam span for now to stdout --- crates/longboy-client-cli/src/main.rs | 17 ++-- crates/longboy-schema/src/lib.rs | 9 +- crates/longboy-server-cli/Cargo.toml | 2 + crates/longboy-server-cli/src/broker.rs | 76 ++++++---------- crates/longboy-server-cli/src/main.rs | 56 +++++++----- crates/longboy/Cargo.toml | 1 + .../src/client/server_to_client_receiver.rs | 1 - crates/longboy/src/proto/receiver.rs | 6 +- .../src/server/client_to_server_receiver.rs | 88 +++++++++++++------ .../src/server/server_to_client_sender.rs | 70 +++++++++++---- 10 files changed, 195 insertions(+), 131 deletions(-) diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index 1f3373a..a89261e 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -3,11 +3,7 @@ #![feature(generic_const_items)] #![feature(unboxed_closures)] -use std::{ - net::SocketAddr, - sync::Arc, - time::Duration, -}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use anyhow::{Context, Result}; use config::Config; @@ -19,7 +15,7 @@ use rustls::{ pki_types::{CertificateDer, pem::PemObject}, }; use rustls_native_certs::load_native_certs; -use tokio::{time::sleep}; +use tokio::time::sleep; use tokio_util::sync::CancellationToken; #[derive(serde::Deserialize)] @@ -183,12 +179,15 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( { Ok((frame, player_id, player_input)) => { - println!("Recv'd Frame({}) PlayerID({}) PlayerInput({})", frame, player_id, player_input); + println!( + "Recv'd Frame({}) PlayerID({}) PlayerInput({})", + frame, player_id, player_input + ); } - Err(e) => + Err(e) => { println!("{}", e); - break + break; } } } diff --git a/crates/longboy-schema/src/lib.rs b/crates/longboy-schema/src/lib.rs index 9ea4744..861eb6b 100644 --- a/crates/longboy-schema/src/lib.rs +++ b/crates/longboy-schema/src/lib.rs @@ -1,7 +1,7 @@ use longboy::{ClientToServerSchema, ServerToClientSchema}; - -pub fn new_client_to_server_schema() -> ClientToServerSchema{ +pub fn new_client_to_server_schema() -> ClientToServerSchema +{ ClientToServerSchema { name: "Input", mapper_port: 8081, @@ -10,10 +10,11 @@ pub fn new_client_to_server_schema() -> ClientToServerSchema{ } } -pub fn new_server_to_client_schema() -> ServerToClientSchema{ +pub fn new_server_to_client_schema() -> ServerToClientSchema +{ ServerToClientSchema { name: "State", mapper_port: 8080, heartbeat_period: 100, } -} \ No newline at end of file +} diff --git a/crates/longboy-server-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml index b46288c..ceea66c 100644 --- a/crates/longboy-server-cli/Cargo.toml +++ b/crates/longboy-server-cli/Cargo.toml @@ -15,3 +15,5 @@ rustls = { version = "0.23.35", features = ["aws-lc-rs"] } serde = { version = "1.0.228", features = ["derive"] } tokio = "1.48.0" tokio-util = "0.7.17" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index 655698f..fd1b638 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -1,8 +1,14 @@ +use anyhow::Result; use flume::{Receiver, Sender}; use longboy::{Factory, Sink, Source}; -use std::{collections::HashMap, sync::{Arc, RwLock, atomic::{AtomicU64}}}; -use anyhow::{Result}; -use std::sync::atomic::Ordering::SeqCst; +use tracing::info; +use std::{ + collections::HashMap, + sync::{ + Arc, RwLock, + atomic::{AtomicU64, Ordering::SeqCst}, + }, +}; pub struct SessionBroker { @@ -13,7 +19,7 @@ pub struct SessionBroker // key is the session id, value is the player index player_sessions: Arc>>, // thread safe atomic free pointer to the next player index - player_index_free_list: Arc>>, + player_index_free_list: Arc>>, } impl SessionBroker @@ -21,11 +27,10 @@ impl SessionBroker pub fn new() -> Self { let pipe = flume::unbounded(); - let receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS] = - [(); MAX_PLAYERS].map(|_| { - let (_s, r) = pipe.clone(); - r - }); + let receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS] = [(); MAX_PLAYERS].map(|_| { + let (_s, r) = pipe.clone(); + r + }); let broadcast_channel = pipe.0; @@ -44,8 +49,11 @@ impl SessionBroker let insert = self.player_sessions.write().unwrap().insert(session_id, player_index); match insert { - Some(old_player_index) => println!("Warning: Overwriting existing session {} for player index {} with {}", old_player_index, player_index, session_id), - None => println!("Allocated session id {} for player index {}", session_id, player_index), + Some(old_player_index) => info!( + "Warning: Overwriting existing session {} for player index {} with {}", + old_player_index, player_index, session_id + ), + None => info!("Allocated session id {} for player index {}", session_id, player_index), } session_id } @@ -64,22 +72,8 @@ impl SessionBroker None => anyhow::bail!("Maximum number of players reached"), } } - - // pub fn free_player_index(&self, player_index: usize) - // { - // let mut free_list = self.player_index_free_list.write().unwrap(); - // free_list[player_index] = true; - // let mut sessions = self.player_sessions.write().unwrap(); - // let session_id = sessions.iter().find_map(|(&s_id, &p_index)| if p_index == player_index { Some(s_id) } else { None }); - // if let Some(s_id) = session_id - // { - // sessions.remove(&s_id); - // println!("Freed session id {} for player index {}", s_id, player_index); - // } - // } } - pub struct ClientToServerSink { player_index: u8, @@ -100,9 +94,7 @@ impl ServerBroker { pub fn new(session_broker: Arc>) -> Self { - Self { - inner: session_broker, - } + Self { inner: session_broker } } } @@ -112,14 +104,8 @@ impl Factory for ServerBroker fn invoke(&mut self, session_id: u64) -> Self::Type { - let binding = self - .inner - .player_sessions - .read() - .unwrap(); - let player_index = binding - .get(&session_id) - .expect("Unknown Session ID"); + let binding = self.inner.player_sessions.read().unwrap(); + let player_index = binding.get(&session_id).expect("Unknown Session ID"); ServerToClientSource { channel: self.inner.receiver_channels[*player_index].clone(), @@ -136,9 +122,7 @@ impl ClientBroker { pub fn new(session_broker: Arc>) -> Self { - Self { - inner: session_broker, - } + Self { inner: session_broker } } } @@ -148,14 +132,8 @@ impl Factory for ClientBroker fn invoke(&mut self, session_id: u64) -> Self::Type { - let binding = self - .inner - .player_sessions - .read() - .unwrap(); - let player_index = binding - .get(&session_id) - .expect("Unknown Session ID"); + let binding = self.inner.player_sessions.read().unwrap(); + let player_index = binding.get(&session_id).expect("Unknown Session ID"); ClientToServerSink { player_index: *player_index as u8, @@ -170,7 +148,6 @@ impl Sink<16> for ClientToServerSink { let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); - println!("Recv'd Frame({}) PlayerInput({})", frame, player_input); self.channel.send((frame, self.player_index, player_input)).unwrap(); } } @@ -186,10 +163,9 @@ impl Source<32> for ServerToClientSource *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); *(<&mut [u8; 1]>::try_from(&mut buffer[4..5]).unwrap()) = player_id.to_le_bytes(); *(<&mut [u8; 8]>::try_from(&mut buffer[5..13]).unwrap()) = player_input.to_le_bytes(); - println!("Sending Frame({}) PlayerID({}) PlayerInput({})", frame, player_id, player_input); true } Err(_) => false, } } -} \ No newline at end of file +} diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 50d9b72..047893c 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -13,6 +13,8 @@ use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; +use tracing::{error, info, warn}; +use tracing_subscriber::fmt::format::FmtSpan; use std::{net::SocketAddr, sync::Arc}; use tokio_util::sync::CancellationToken; @@ -44,6 +46,12 @@ struct LongboyServerConfig /// This function collects configuration settings and starts the server. fn main() { + //Setup a simple tracer subscriber for logging. + tracing_subscriber::fmt() + .with_span_events(FmtSpan::FULL) + .with_max_level(tracing::Level::TRACE) + .init(); + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); // Setup the configuration builder for the server. Let environment variables take the highest precedence. let settings = Config::builder() @@ -86,14 +94,10 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let client_to_server_schema = new_client_to_server_schema(); let server_builder = Server::builder(config.session_capacity, server_runtime) - .sender::<_, 32, 3>( - &server_to_client_schema, - server_broker - ).unwrap() - .receiver::<_, 16, 3>( - &client_to_server_schema, - client_broker - ).unwrap(); + .sender::<_, 32, 3>(&server_to_client_schema, server_broker) + .unwrap() + .receiver::<_, 16, 3>(&client_to_server_schema, client_broker) + .unwrap(); // Load TLS certificates. TLS is required. let provider = aws_lc_rs::default_provider(); @@ -105,18 +109,24 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let key = if key_path.extension().is_some_and(|x| x == "der") { PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( - std::fs::read(key_path).context("failed to read private key file").unwrap(), + std::fs::read(key_path) + .context("failed to read private key file") + .unwrap(), )) } else { - PrivateKeyDer::from_pem_file(key_path).context("failed to read PEM from private key file").unwrap() + PrivateKeyDer::from_pem_file(key_path) + .context("failed to read PEM from private key file") + .unwrap() }; let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") { vec![CertificateDer::from( - std::fs::read(cert_path).context("failed to read certificate chain file").unwrap(), + std::fs::read(cert_path) + .context("failed to read certificate chain file") + .unwrap(), )] } else @@ -124,7 +134,8 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: CertificateDer::pem_file_iter(cert_path) .context("failed to read PEM from certificate chain file")? .collect::>() - .context("invalid PEM-encoded certificate").unwrap() + .context("invalid PEM-encoded certificate") + .unwrap() }; (cert_chain, key) @@ -144,23 +155,22 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let mut server_instance = server_builder.build(); // log out some info on server startup - println!("Longboy server listening on {}", listen_addr); + info!("Longboy server listening on {}", listen_addr); // print stats from server endpoint - println!("Server endpoint stats: {:?}", server_endpoint.stats()); - + info!("Server endpoint stats: {:?}", server_endpoint.stats()); // start accepting connections while let Some(conn) = server_endpoint.accept().await { // print it - println!("Incoming connection: {:?}", conn.remote_address()); + info!("Incoming connection: {:?}", conn.remote_address()); // try to accept and handle errors gracefully match conn.accept() { Err(e) => { - println!("Failed to accept connection: {:?}", e); + error!("Failed to accept connection: {:?}", e); continue; } Ok(connecting) => @@ -169,7 +179,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: { Err(e) => { - println!("Failed to establish connection: {:?}", e); + warn!("Failed to establish connection: {:?}", e); continue; } Ok(connection) => @@ -177,7 +187,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: // handle the connection if let Err(e) = server_handle_connection(connection, &mut server_instance, &broker).await { - println!("Error handling connection: {:?}", e); + warn!("Error handling connection: {:?}", e); } } } @@ -188,10 +198,14 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Ok(()) } -async fn server_handle_connection(conn: Connection, server: &mut Server, broker: &std::sync::Arc>) -> Result<()> +async fn server_handle_connection( + conn: Connection, + server: &mut Server, + broker: &std::sync::Arc>, +) -> Result<()> { let remote_address = conn.remote_address(); - println!("New connection from {}", remote_address); + info!("New connection from {}", remote_address); let player_index = broker.next_player_index()?; let session_id = broker.allocate_session_id(player_index); diff --git a/crates/longboy/Cargo.toml b/crates/longboy/Cargo.toml index cd14b02..9ccd050 100644 --- a/crates/longboy/Cargo.toml +++ b/crates/longboy/Cargo.toml @@ -23,6 +23,7 @@ rc5 = { git = "https://github.com/RustCrypto/block-ciphers.git", rev = "36b34efd thunderdome = "0.6.1" tokio = { version = "1.48", features = ["full"] } tokio-util = "0.7.11" +tracing = "0.1.44" [dev-dependencies] parking_lot = "0.12.3" diff --git a/crates/longboy/src/client/server_to_client_receiver.rs b/crates/longboy/src/client/server_to_client_receiver.rs index 4092e78..173b2a0 100644 --- a/crates/longboy/src/client/server_to_client_receiver.rs +++ b/crates/longboy/src/client/server_to_client_receiver.rs @@ -90,7 +90,6 @@ where let mut buffer = [0; 512]; while let Ok((len, _)) = self.socket.recv_from(&mut buffer) { - println!("Received datagram of length {}", len); if len != DATAGRAM_SIZE { continue; diff --git a/crates/longboy/src/proto/receiver.rs b/crates/longboy/src/proto/receiver.rs index 3bf46b4..5ff48d6 100644 --- a/crates/longboy/src/proto/receiver.rs +++ b/crates/longboy/src/proto/receiver.rs @@ -42,6 +42,7 @@ where self.cycle } + #[tracing::instrument(skip(self))] pub fn handle_datagram( &mut self, timestamp: u16, @@ -60,11 +61,10 @@ where let datagram_cycle = u16::from_le_bytes(*<&[u8; 2]>::try_from(&datagram[0..2]).unwrap()) as usize; let datagram_timestamp = u16::from_le_bytes(*<&[u8; 2]>::try_from(&datagram[2..4]).unwrap()); - println!("Received datagram with cycle {} and timestamp {}, my cycle is {} and my timestamp is {}", datagram_cycle, datagram_timestamp, self.cycle, timestamp); - // Calculate diff for cycle and timestamp. let cycle_diff = ((datagram_cycle + MAX_CYCLE) - self.cycle) % MAX_CYCLE; - let timestamp_diff = (((datagram_timestamp as u32 + u16::MAX as u32) - timestamp as u32) % (u16::MAX as u32)) as u16; + let timestamp_diff = + (((datagram_timestamp as u32 + u16::MAX as u32) - timestamp as u32) % (u16::MAX as u32)) as u16; // Check for bad datagrams or late datagrams that are already processed. Because // we ensure only a positive diff, this is done by checking for any values greater diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index ff8909b..441bdea 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -37,6 +37,33 @@ where receiver: Receiver, } +impl std::fmt::Debug + for ClientToServerReceiver +where + SinkFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, + [(); >::MAX_BUFFERED]:, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.debug_struct("ClientToServerReceiver") + .field("name", &self.name) + .field("mapper_socket", &self.mapper_socket) + .field("socket", &self.socket) + .field("session_receiver", &"FlumeReceiver") + .field( + "sessions", + &format!( + "Arena>", + "SinkFactoryType::Type", SIZE, WINDOW_SIZE + ), + ) + .field("session_id_to_session_map", &self.session_id_to_session_map) + .field("socket_addr_to_session_map", &self.socket_addr_to_session_map) + .finish() + } +} + impl ClientToServerReceiver where @@ -74,26 +101,10 @@ where sink_factory, }) } -} -impl RuntimeTask - for ClientToServerReceiver -where - SinkFactoryType: Factory>, - [(); >::DATAGRAM_SIZE]:, - [(); >::MAX_BUFFERED]:, -{ - fn name(&self) -> &str + #[tracing::instrument(skip(self))] + fn poll_server_session_events(&mut self, timestamp: u16) { - &self.name - } - - fn poll(&mut self, timestamp: u16) - { - // Alias constants so they're less painful to read. - #[allow(non_snake_case)] - let DATAGRAM_SIZE: usize = Constants::::DATAGRAM_SIZE; - // Handle Session changes. for event in self.session_receiver.try_iter() { @@ -123,13 +134,16 @@ where } } } + } + #[tracing::instrument(skip(self))] + fn poll_for_client_socket_addresses(&mut self) + { // Update Client socket addresses. let mut buffer = [0; 64]; // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { - println!("Received mapper datagram from {:?}, length {}", socket_addr, len); if len != std::mem::size_of::() + std::mem::size_of::() { continue; @@ -139,12 +153,6 @@ where let mirroring = buffer[8] as usize; - println!( - "Mapping session_id {} mirroring {} to socket_addr {:?}", - session_id, - mirroring, - socket_addr - ); if mirroring >= Mirroring::LENGTH { continue; @@ -164,13 +172,19 @@ where self.socket_addr_to_session_map.insert(socket_addr, *index); } } + } + + #[tracing::instrument(skip(self))] + fn poll_for_client_datagrams(&mut self, timestamp: u16) + { + // Alias constants so they're less painful to read. + #[allow(non_snake_case)] + let DATAGRAM_SIZE: usize = Constants::::DATAGRAM_SIZE; - // Process datagrams. // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. let mut buffer = [0; 512]; while let Ok((len, socket_addr)) = self.socket.recv_from(&mut buffer) { - println!("Received datagram from {:?}, length {}", socket_addr, len); if len != DATAGRAM_SIZE { continue; @@ -188,3 +202,23 @@ where } } } + +impl RuntimeTask + for ClientToServerReceiver +where + SinkFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, + [(); >::MAX_BUFFERED]:, +{ + fn name(&self) -> &str + { + &self.name + } + + fn poll(&mut self, timestamp: u16) + { + self.poll_server_session_events(timestamp); + self.poll_for_client_socket_addresses(); + self.poll_for_client_datagrams(timestamp); + } +} diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 1f0e22d..35245b3 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -34,6 +34,31 @@ where sender: Sender, } +impl std::fmt::Debug + for ServerToClientSender +where + SourceFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.debug_struct("ServerToClientSender") + .field("name", &self.name) + .field("mapper_socket", &self.mapper_socket) + .field("sockets", &self.sockets) + .field("session_receiver", &"FlumeReceiver") + .field( + "sessions", + &format!( + "Arena>", + "SourceFactoryType::Type", SIZE, WINDOW_SIZE + ), + ) + .field("session_id_to_session_map", &self.session_id_to_session_map) + .finish() + } +} + impl ServerToClientSender where @@ -73,22 +98,10 @@ where source_factory, }) } -} - -impl RuntimeTask - for ServerToClientSender -where - SourceFactoryType: Factory>, - [(); >::DATAGRAM_SIZE]:, -{ - fn name(&self) -> &str - { - &self.name - } - fn poll(&mut self, timestamp: u16) + #[tracing::instrument(skip(self))] + fn poll_server_session_events(&mut self, timestamp: u16) { - // Handle Session changes. for event in self.session_receiver.try_iter() { match event @@ -113,7 +126,11 @@ where } } } + } + #[tracing::instrument(skip(self))] + fn poll_for_client_socket_addresses(&mut self) + { // Update Client socket addresses. let mut buffer = [0; 64]; while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) @@ -124,14 +141,17 @@ where } let session_id = u64::from_le_bytes(*<&[u8; 8]>::try_from(&buffer[0..8]).unwrap()); - println!("Received heartbeat from session_id {}, socket_addr {:?}", session_id, socket_addr); if let Some(index) = self.session_id_to_session_map.get(&session_id) { self.sessions[*index].socket_addr = Some(socket_addr); } } + } + #[tracing::instrument(skip(self))] + fn poll_sessions(&mut self, timestamp: u16) + { // Poll Sessions for (_, session) in self.sessions.iter_mut() { @@ -140,10 +160,28 @@ where { for socket in self.sockets.values() { - println!("Sending datagram to {:?}: {:?}", socket_addr, datagram); socket.send_to(datagram, socket_addr).expect("send_to failure"); } } } } } + +impl RuntimeTask + for ServerToClientSender +where + SourceFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, +{ + fn name(&self) -> &str + { + &self.name + } + + fn poll(&mut self, timestamp: u16) + { + self.poll_server_session_events(timestamp); + self.poll_for_client_socket_addresses(); + self.poll_sessions(timestamp); + } +} From d849ae85c0b78586c32774432b4c08944bd94072 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 6 Feb 2026 00:29:02 -0600 Subject: [PATCH 15/23] fix: set data width to what works? --- crates/longboy-server-cli/src/broker.rs | 4 ++-- crates/longboy-server-cli/src/main.rs | 2 +- .../src/server/client_to_server_receiver.rs | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index fd1b638..1905e16 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -142,9 +142,9 @@ impl Factory for ClientBroker } } -impl Sink<16> for ClientToServerSink +impl Sink<28> for ClientToServerSink { - fn handle(&mut self, buffer: &[u8; 16]) + fn handle(&mut self, buffer: &[u8; 28]) { let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 047893c..006df0f 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -96,7 +96,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let server_builder = Server::builder(config.session_capacity, server_runtime) .sender::<_, 32, 3>(&server_to_client_schema, server_broker) .unwrap() - .receiver::<_, 16, 3>(&client_to_server_schema, client_broker) + .receiver::<_, 28, 1>(&client_to_server_schema, client_broker) .unwrap(); // Load TLS certificates. TLS is required. diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index 441bdea..e06b2cc 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -5,6 +5,7 @@ use enum_map::{Enum, EnumMap}; use flume::Receiver as FlumeReceiver; use fnv::FnvHashMap; use thunderdome::{Arena, Index}; +use tracing::{info, warn}; use crate::{Constants, Factory, Mirroring, Receiver, RuntimeTask, ServerSessionEvent, Sink}; @@ -146,11 +147,18 @@ where { if len != std::mem::size_of::() + std::mem::size_of::() { + warn!( + "Received mapping of invalid length {} from socket_addr {:?}", + len, socket_addr + ); continue; } let session_id = u64::from_le_bytes(*<&[u8; 8]>::try_from(&buffer[0..8]).unwrap()); - + info!( + "Received mapping from session_id {} to socket_addr {:?}", + session_id, socket_addr + ); let mirroring = buffer[8] as usize; if mirroring >= Mirroring::LENGTH @@ -187,9 +195,14 @@ where { if len != DATAGRAM_SIZE { + warn!( + "Received datagram of invalid length {} from socket_addr {:?}", + len, socket_addr + ); continue; } let datagram = (&mut buffer[0..DATAGRAM_SIZE]).try_into().unwrap(); + info!("Received datagram from socket_addr {:?} - datagram {:?}", socket_addr, datagram); if let Some(index) = self.socket_addr_to_session_map.get(&socket_addr) { From de08fdb7c5cf27be2cd99380c8cd43a846ab6c9b Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 20 Feb 2026 00:25:51 -0600 Subject: [PATCH 16/23] feat: longboy bristlecone --- .vscode/launch.json | 21 ++++++++ crates/longboy-client-cli/src/main.rs | 49 ++++++++++--------- crates/longboy-server-cli/src/broker.rs | 32 ++++++------ crates/longboy-server-cli/src/main.rs | 2 +- crates/longboy/src/client/client_session.rs | 3 ++ .../src/server/client_to_server_receiver.rs | 9 ++-- .../src/server/server_to_client_sender.rs | 7 ++- 7 files changed, 75 insertions(+), 48 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index dff919c..06bebb1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,27 @@ ], "externalConsole": true }, + { + "name": "(Windows) Launch longboy-client-cli", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/longboy-client-cli.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "externalConsole": true + }, { "type": "cppdbg", "request": "launch", diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs index a89261e..707e6cc 100644 --- a/crates/longboy-client-cli/src/main.rs +++ b/crates/longboy-client-cli/src/main.rs @@ -28,26 +28,27 @@ struct LongboyClientConfig struct ServerToClientSink { - channel: flume::Sender<(u32, u8, u64)>, + channel: flume::Sender<(u64, u64, u64, u8)>, } struct ClientToServerSource { - channel: flume::Receiver<(u32, u64)>, + channel: flume::Receiver<(u64, u64, u64)>, } -impl Source<16> for ClientToServerSource +impl Source<32> for ClientToServerSource { - fn poll(&mut self, buffer: &mut [u8; 16]) -> bool + fn poll(&mut self, buffer: &mut [u8; 32]) -> bool { let msg = self.channel.recv(); match msg { - Ok((frame, val)) => + Ok((input0, input1, input2)) => { - print!("Sending {}", val); - *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[4..12]).unwrap()) = u64::from(val).to_le_bytes(); + print!("Sending {}, {}, {}", input0, input1, input2); + *(<&mut [u8; 8]>::try_from(&mut buffer[0..8]).unwrap()) = input0.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[8..16]).unwrap()) = input1.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[16..24]).unwrap()) = input2.to_le_bytes(); true } _ => false, @@ -59,10 +60,11 @@ impl Sink<32> for ServerToClientSink { fn handle(&mut self, buffer: &[u8; 32]) { - let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); - let player_id = u8::from_le_bytes(*(<&[u8; 1]>::try_from(&buffer[4..5]).unwrap())); - let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[5..13]).unwrap())); - self.channel.send((frame, player_id, player_input)).unwrap(); + let input0 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[0..8]).unwrap())); + let input1 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[8..16]).unwrap())); + let input2 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[16..24]).unwrap())); + let player_id = u8::from_le_bytes(*(<&[u8; 1]>::try_from(&buffer[24..25]).unwrap())); + self.channel.send((input0, input1, input2, player_id)).unwrap(); } } @@ -157,7 +159,7 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( channel: receiver_channel.0, }, )? - .sender::<_, 16, 3>( + .sender::<_, 32, 3>( &client_to_server_schema, ClientToServerSource { channel: sender_channel.1, @@ -177,11 +179,11 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( match incoming { - Ok((frame, player_id, player_input)) => + Ok((input0, input1, input2, player_id)) => { println!( - "Recv'd Frame({}) PlayerID({}) PlayerInput({})", - frame, player_id, player_input + "Recv'd Input0({}) Input1({}) Input2({}) PlayerID({})", + input0, input1, input2, player_id ); } Err(e) => @@ -193,7 +195,6 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( } }); - let mut frame: u32 = 0; loop { if cancellation_token.is_cancelled() @@ -201,13 +202,13 @@ async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<( break; } // send keys - sleep(Duration::from_millis(100)).await; - // random ascii between 32 and 126 - let player_input: u64 = rand::random::() as u64 % (126 - 32) + 32; - println!("Sending Frame({}) PlayerInput({})", frame, player_input); - sender_channel.0.send((frame, player_input)).unwrap(); - frame += 1; - println!("Frame {}", frame); + sleep(Duration::from_millis(5000)).await; + // random inputs for testing + let input0 = rand::random::(); + let input1 = rand::random::(); + let input2 = rand::random::(); + println!("Sending Input0({}) Input1({}) Input2({})", input0, input1, input2); + sender_channel.0.send((input0, input1, input2)).unwrap(); } Ok(()) diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index 1905e16..21de76f 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -12,8 +12,8 @@ use std::{ pub struct SessionBroker { - receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS], - broadcast_channel: Sender<(u32, u8, u64)>, + receiver_channels: [Receiver<(u64, u64, u64, u8)>; MAX_PLAYERS], + broadcast_channel: Sender<(u64, u64, u64, u8)>, // thread safe atomic always incrementing session id next_session_id: AtomicU64, // key is the session id, value is the player index @@ -27,7 +27,7 @@ impl SessionBroker pub fn new() -> Self { let pipe = flume::unbounded(); - let receiver_channels: [Receiver<(u32, u8, u64)>; MAX_PLAYERS] = [(); MAX_PLAYERS].map(|_| { + let receiver_channels: [Receiver<(u64, u64, u64, u8)>; MAX_PLAYERS] = [(); MAX_PLAYERS].map(|_| { let (_s, r) = pipe.clone(); r }); @@ -77,12 +77,12 @@ impl SessionBroker pub struct ClientToServerSink { player_index: u8, - channel: Sender<(u32, u8, u64)>, + channel: Sender<(u64, u64, u64, u8)>, } pub struct ServerToClientSource { - channel: Receiver<(u32, u8, u64)>, + channel: Receiver<(u64, u64, u64, u8)>, } pub struct ServerBroker @@ -144,25 +144,29 @@ impl Factory for ClientBroker impl Sink<28> for ClientToServerSink { + #[tracing::instrument(skip(self))] fn handle(&mut self, buffer: &[u8; 28]) { - let frame = u32::from_le_bytes(*(<&[u8; 4]>::try_from(&buffer[0..4]).unwrap())); - let player_input = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[4..12]).unwrap())); - self.channel.send((frame, self.player_index, player_input)).unwrap(); + let a = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[0..8]).unwrap())); + let b = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[8..16]).unwrap())); + let c = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[16..24]).unwrap())); + self.channel.send((a, b, c, self.player_index)).unwrap(); } } -impl Source<32> for ServerToClientSource +impl Source<28> for ServerToClientSource { - fn poll(&mut self, buffer: &mut [u8; 32]) -> bool + #[tracing::instrument(skip(self))] + fn poll(&mut self, buffer: &mut [u8; 28]) -> bool { match self.channel.try_recv() { - Ok((frame, player_id, player_input)) => + Ok((a, b, c, player_index)) => { - *(<&mut [u8; 4]>::try_from(&mut buffer[0..4]).unwrap()) = frame.to_le_bytes(); - *(<&mut [u8; 1]>::try_from(&mut buffer[4..5]).unwrap()) = player_id.to_le_bytes(); - *(<&mut [u8; 8]>::try_from(&mut buffer[5..13]).unwrap()) = player_input.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[0..8]).unwrap()) = a.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[8..16]).unwrap()) = b.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[16..24]).unwrap()) = c.to_le_bytes(); + *(<&mut [u8; 1]>::try_from(&mut buffer[24..25]).unwrap()) = player_index.to_le_bytes(); true } Err(_) => false, diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 006df0f..a83da0c 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -94,7 +94,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let client_to_server_schema = new_client_to_server_schema(); let server_builder = Server::builder(config.session_capacity, server_runtime) - .sender::<_, 32, 3>(&server_to_client_schema, server_broker) + .sender::<_, 28, 3>(&server_to_client_schema, server_broker) .unwrap() .receiver::<_, 28, 1>(&client_to_server_schema, client_broker) .unwrap(); diff --git a/crates/longboy/src/client/client_session.rs b/crates/longboy/src/client/client_session.rs index 92af6ce..e4c29df 100644 --- a/crates/longboy/src/client/client_session.rs +++ b/crates/longboy/src/client/client_session.rs @@ -20,6 +20,9 @@ impl ClientSession let cipher_key = receive.read_u64_le().await?; receive.stop(Default::default())?; + // debug print the session id and cipher key + println!("Received session id: {}, cipher key: {}", session_id, cipher_key); + Ok(Self { connection, session_id, diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index e06b2cc..b0255a1 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -103,8 +103,7 @@ where }) } - #[tracing::instrument(skip(self))] - fn poll_server_session_events(&mut self, timestamp: u16) + fn poll_server_session_events(&mut self, _timestamp: u16) { // Handle Session changes. for event in self.session_receiver.try_iter() @@ -137,7 +136,6 @@ where } } - #[tracing::instrument(skip(self))] fn poll_for_client_socket_addresses(&mut self) { // Update Client socket addresses. @@ -145,7 +143,7 @@ where // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { - if len != std::mem::size_of::() + std::mem::size_of::() + if len < std::mem::size_of::() + std::mem::size_of::() // disqualify not enough data? { warn!( "Received mapping of invalid length {} from socket_addr {:?}", @@ -173,16 +171,17 @@ where if let Some(socket_addr) = session.socket_addrs[mirroring] { + info!("Removing old mapping from session_id {} to socket_addr {:?} for mirroring {:?}", session_id, socket_addr, mirroring); self.socket_addr_to_session_map.remove(&socket_addr); } + info!("Adding mapping from session_id {} to socket_addr {:?} for mirroring {:?}", session_id, socket_addr, mirroring); session.socket_addrs[mirroring] = Some(socket_addr); self.socket_addr_to_session_map.insert(socket_addr, *index); } } } - #[tracing::instrument(skip(self))] fn poll_for_client_datagrams(&mut self, timestamp: u16) { // Alias constants so they're less painful to read. diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 35245b3..6561619 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -5,6 +5,7 @@ use enum_map::EnumMap; use flume::Receiver as FlumeReceiver; use fnv::FnvHashMap; use thunderdome::{Arena, Index}; +use tracing::trace; use crate::{Constants, Factory, Mirroring, RuntimeTask, Sender, ServerSessionEvent, Source, UdpSocketExt}; @@ -99,8 +100,7 @@ where }) } - #[tracing::instrument(skip(self))] - fn poll_server_session_events(&mut self, timestamp: u16) + fn poll_server_session_events(&mut self, _timestamp: u16) { for event in self.session_receiver.try_iter() { @@ -128,7 +128,6 @@ where } } - #[tracing::instrument(skip(self))] fn poll_for_client_socket_addresses(&mut self) { // Update Client socket addresses. @@ -144,12 +143,12 @@ where if let Some(index) = self.session_id_to_session_map.get(&session_id) { + trace!("Received mapping from session {} to socket address {}", session_id, socket_addr); self.sessions[*index].socket_addr = Some(socket_addr); } } } - #[tracing::instrument(skip(self))] fn poll_sessions(&mut self, timestamp: u16) { // Poll Sessions From fb64edc4c0b2032a4899a483639669b69b47a68e Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 20 Feb 2026 00:31:05 -0600 Subject: [PATCH 17/23] chore: clippy and format checks --- crates/longboy-server-cli/src/broker.rs | 2 +- crates/longboy-server-cli/src/main.rs | 8 ++++---- .../src/server/client_to_server_receiver.rs | 18 ++++++++++++++---- .../src/server/server_to_client_sender.rs | 5 ++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index 21de76f..cd9f9bf 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -1,7 +1,6 @@ use anyhow::Result; use flume::{Receiver, Sender}; use longboy::{Factory, Sink, Source}; -use tracing::info; use std::{ collections::HashMap, sync::{ @@ -9,6 +8,7 @@ use std::{ atomic::{AtomicU64, Ordering::SeqCst}, }, }; +use tracing::info; pub struct SessionBroker { diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index a83da0c..df23ce4 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -13,10 +13,10 @@ use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use tracing::{error, info, warn}; -use tracing_subscriber::fmt::format::FmtSpan; use std::{net::SocketAddr, sync::Arc}; use tokio_util::sync::CancellationToken; +use tracing::{error, info, warn}; +use tracing_subscriber::fmt::format::FmtSpan; use crate::broker::{ClientBroker, ServerBroker, SessionBroker}; @@ -86,8 +86,8 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: }; let broker = std::sync::Arc::new(SessionBroker::<32>::new()); - let client_broker = ClientBroker::new(broker.clone().into()); - let server_broker = ServerBroker::new(broker.clone().into()); + let client_broker = ClientBroker::new(broker.clone()); + let server_broker = ServerBroker::new(broker.clone()); // Setup the receivers for client-server communication. let server_to_client_schema = new_server_to_client_schema(); diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index b0255a1..7c2824f 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -143,7 +143,8 @@ where // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { - if len < std::mem::size_of::() + std::mem::size_of::() // disqualify not enough data? + if len < std::mem::size_of::() + std::mem::size_of::() + // disqualify not enough data? { warn!( "Received mapping of invalid length {} from socket_addr {:?}", @@ -171,11 +172,17 @@ where if let Some(socket_addr) = session.socket_addrs[mirroring] { - info!("Removing old mapping from session_id {} to socket_addr {:?} for mirroring {:?}", session_id, socket_addr, mirroring); + info!( + "Removing old mapping from session_id {} to socket_addr {:?} for mirroring {:?}", + session_id, socket_addr, mirroring + ); self.socket_addr_to_session_map.remove(&socket_addr); } - info!("Adding mapping from session_id {} to socket_addr {:?} for mirroring {:?}", session_id, socket_addr, mirroring); + info!( + "Adding mapping from session_id {} to socket_addr {:?} for mirroring {:?}", + session_id, socket_addr, mirroring + ); session.socket_addrs[mirroring] = Some(socket_addr); self.socket_addr_to_session_map.insert(socket_addr, *index); } @@ -201,7 +208,10 @@ where continue; } let datagram = (&mut buffer[0..DATAGRAM_SIZE]).try_into().unwrap(); - info!("Received datagram from socket_addr {:?} - datagram {:?}", socket_addr, datagram); + info!( + "Received datagram from socket_addr {:?} - datagram {:?}", + socket_addr, datagram + ); if let Some(index) = self.socket_addr_to_session_map.get(&socket_addr) { diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 6561619..e262f4e 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -143,7 +143,10 @@ where if let Some(index) = self.session_id_to_session_map.get(&session_id) { - trace!("Received mapping from session {} to socket address {}", session_id, socket_addr); + trace!( + "Received mapping from session {} to socket address {}", + session_id, socket_addr + ); self.sessions[*index].socket_addr = Some(socket_addr); } } From dc94dc29516685223cdd73d713793ab304575ab7 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Wed, 18 Mar 2026 23:52:32 -0500 Subject: [PATCH 18/23] feat(alpn): set alpn for server to work with game client --- crates/longboy-server-cli/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index df23ce4..95db023 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -145,9 +145,11 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let listen_addr = config.listen_address.unwrap_or_else(|| "[::1]:4433".parse().unwrap()); // Now use Quinn (HTTP/3 (QUIP)) to start a listening endpoint. - let server_crypto = rustls::ServerConfig::builder() + let mut server_crypto = rustls::ServerConfig::builder() .with_no_client_auth() - .with_single_cert(cert_chain, key)?; + .with_single_cert(cert_chain, key) + .context("failed to build server crypto configuration")?; + server_crypto.alpn_protocols = vec![b"longboyquic".to_vec()]; let server_config = quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(server_crypto)?)); let server_endpoint = Endpoint::server(server_config, listen_addr).context("failed to build endpoint")?; From 388299a913087b8c4c706697a0bed23535ccf228 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Fri, 27 Mar 2026 23:36:22 -0500 Subject: [PATCH 19/23] feat(crypto): use a basic random key generator --- crates/longboy-server-cli/Cargo.toml | 2 ++ crates/longboy-server-cli/src/broker.rs | 2 +- crates/longboy-server-cli/src/main.rs | 13 +++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/longboy-server-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml index ceea66c..97f8f65 100644 --- a/crates/longboy-server-cli/Cargo.toml +++ b/crates/longboy-server-cli/Cargo.toml @@ -11,6 +11,8 @@ flume = "0.12.0" longboy = { version = "1.0.0", path = "../longboy" } longboy_schema = { version = "1.0.0", path = "../longboy-schema" } quinn = "0.11.9" +rand = "0.10.0" +rand-unique = "0.3.0" rustls = { version = "0.23.35", features = ["aws-lc-rs"] } serde = { version = "1.0.228", features = ["derive"] } tokio = "1.48.0" diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index cd9f9bf..6e4cd52 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -39,7 +39,7 @@ impl SessionBroker broadcast_channel, next_session_id: AtomicU64::new(1), player_sessions: Arc::new(RwLock::new(HashMap::new())), - player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())), + player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())) } } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 95db023..58065fa 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -9,11 +9,12 @@ use config::Config; use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; +use rand_unique::RandomSequence; use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use std::{net::SocketAddr, sync::Arc}; +use std::{net::SocketAddr, sync::{Arc, Mutex}}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; use tracing_subscriber::fmt::format::FmtSpan; @@ -94,7 +95,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let client_to_server_schema = new_client_to_server_schema(); let server_builder = Server::builder(config.session_capacity, server_runtime) - .sender::<_, 28, 3>(&server_to_client_schema, server_broker) + .sender::<_, 28, 1>(&server_to_client_schema, server_broker) .unwrap() .receiver::<_, 28, 1>(&client_to_server_schema, client_broker) .unwrap(); @@ -156,6 +157,9 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: // create a new logical server instance let mut server_instance = server_builder.build(); + // weak key generator for session encryption. This is not ideal but it is important that the server can generate keys for sessions without blocking on a mutex or something. We can replace this with a more robust solution later if needed. + let mut keygenerator = Arc::new(Mutex::new(RandomSequence::::rand(&mut rand::rngs::ThreadRng::default()))); + // log out some info on server startup info!("Longboy server listening on {}", listen_addr); // print stats from server endpoint @@ -187,7 +191,7 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Ok(connection) => { // handle the connection - if let Err(e) = server_handle_connection(connection, &mut server_instance, &broker).await + if let Err(e) = server_handle_connection(connection, &mut server_instance, &broker, &mut keygenerator).await { warn!("Error handling connection: {:?}", e); } @@ -204,6 +208,7 @@ async fn server_handle_connection( conn: Connection, server: &mut Server, broker: &std::sync::Arc>, + keygenerator: &std::sync::Arc>>, ) -> Result<()> { let remote_address = conn.remote_address(); @@ -211,7 +216,7 @@ async fn server_handle_connection( let player_index = broker.next_player_index()?; let session_id = broker.allocate_session_id(player_index); - let cipher_key = 0xdeadbeef; + let cipher_key = keygenerator.lock().unwrap().next().expect("key generator should never exhaust"); let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? Ok(()) From d761b32f4b0973852af7243880be60200434a162 Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Mon, 30 Mar 2026 21:21:44 -0500 Subject: [PATCH 20/23] fix: some wrapping math wasn't doing alright. Add tracing --- crates/longboy/src/proto/receiver.rs | 23 +++++++++++++++++-- .../src/server/server_to_client_sender.rs | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/longboy/src/proto/receiver.rs b/crates/longboy/src/proto/receiver.rs index 5ff48d6..cd35084 100644 --- a/crates/longboy/src/proto/receiver.rs +++ b/crates/longboy/src/proto/receiver.rs @@ -55,6 +55,9 @@ where #[allow(non_snake_case)] let MAX_BUFFERED: usize = Constants::::MAX_BUFFERED; + tracing::trace!("Handling datagram with timestamp {timestamp} at local cycle {}", self.cycle); + tracing::trace!("Flags: {:?}", self.flags); + // Grab cycle and timestamp. self.cipher .decrypt_header(<&mut [u8; 4]>::try_from(&mut datagram[0..4]).unwrap()); @@ -63,8 +66,17 @@ where // Calculate diff for cycle and timestamp. let cycle_diff = ((datagram_cycle + MAX_CYCLE) - self.cycle) % MAX_CYCLE; - let timestamp_diff = - (((datagram_timestamp as u32 + u16::MAX as u32) - timestamp as u32) % (u16::MAX as u32)) as u16; + let timestamp_diff = timestamp.wrapping_sub(datagram_timestamp); + + // trace cycle and timestamp info. + tracing::trace!( + datagram_cycle, + datagram_timestamp, + cycle_diff, + timestamp_diff, + local_cycle = self.cycle, + local_timestamp = timestamp + ); // Check for bad datagrams or late datagrams that are already processed. Because // we ensure only a positive diff, this is done by checking for any values greater @@ -72,6 +84,7 @@ where if cycle_diff > 256 || timestamp_diff > 2048 { // Bad datagram or already received. + tracing::warn!("Dropping datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff}"); return; } @@ -80,10 +93,12 @@ where if cycle_diff > std::cmp::min(8, WINDOW_SIZE + 1) { // soft warning + tracing::warn!("Late datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff}"); } if cycle_diff > MAX_BUFFERED { // hard warning + tracing::error!("Datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff} is too far ahead"); for _ in 0..(cycle_diff - MAX_BUFFERED) { @@ -103,6 +118,7 @@ where // already adanced the local cycle to catch up, if applicable. if ((cycle_i + MAX_CYCLE) - self.cycle) % MAX_CYCLE > MAX_BUFFERED { + tracing::trace!("Stopping sink input at cycle {} because it's too far ahead of local cycle {}", cycle_i, self.cycle); break; } @@ -127,13 +143,16 @@ where // Advance cycles. loop { + tracing::trace!("Advancing cycle from {}", self.cycle); let index = self.cycle % MAX_BUFFERED; if !self.flags[index] { + tracing::trace!("Stopping cycle advance at cycle {} because flag is not set", self.cycle); break; } self.flags[index] = false; self.cycle = (self.cycle + 1) % MAX_CYCLE; + tracing::trace!("Advanced cycle to {}", self.cycle); } } } diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index e262f4e..14b83cf 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -162,6 +162,7 @@ where { for socket in self.sockets.values() { + trace!("Sending datagram to socket address {} via socket {:?}", socket_addr, socket); socket.send_to(datagram, socket_addr).expect("send_to failure"); } } From 5f62c08ebf678e24e9fb38c40d03605a923b777f Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Mon, 6 Apr 2026 22:26:58 -0500 Subject: [PATCH 21/23] fix(ci): fix ci failing checks for PR --- crates/longboy-server-cli/src/broker.rs | 2 +- crates/longboy-server-cli/src/main.rs | 18 ++++++++++++++---- crates/longboy/src/proto/receiver.rs | 15 ++++++++++++--- .../src/server/server_to_client_sender.rs | 5 ++++- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs index 6e4cd52..cd9f9bf 100644 --- a/crates/longboy-server-cli/src/broker.rs +++ b/crates/longboy-server-cli/src/broker.rs @@ -39,7 +39,7 @@ impl SessionBroker broadcast_channel, next_session_id: AtomicU64::new(1), player_sessions: Arc::new(RwLock::new(HashMap::new())), - player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())) + player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())), } } diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs index 58065fa..38ae5e8 100644 --- a/crates/longboy-server-cli/src/main.rs +++ b/crates/longboy-server-cli/src/main.rs @@ -14,7 +14,10 @@ use rustls::{ crypto::{CryptoProvider, aws_lc_rs}, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; -use std::{net::SocketAddr, sync::{Arc, Mutex}}; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex}, +}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; use tracing_subscriber::fmt::format::FmtSpan; @@ -158,7 +161,9 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: let mut server_instance = server_builder.build(); // weak key generator for session encryption. This is not ideal but it is important that the server can generate keys for sessions without blocking on a mutex or something. We can replace this with a more robust solution later if needed. - let mut keygenerator = Arc::new(Mutex::new(RandomSequence::::rand(&mut rand::rngs::ThreadRng::default()))); + let keygenerator = Arc::new(Mutex::new(RandomSequence::::rand( + &mut rand::rngs::ThreadRng::default(), + ))); // log out some info on server startup info!("Longboy server listening on {}", listen_addr); @@ -191,7 +196,8 @@ async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: Ok(connection) => { // handle the connection - if let Err(e) = server_handle_connection(connection, &mut server_instance, &broker, &mut keygenerator).await + if let Err(e) = + server_handle_connection(connection, &mut server_instance, &broker, &keygenerator).await { warn!("Error handling connection: {:?}", e); } @@ -216,7 +222,11 @@ async fn server_handle_connection( let player_index = broker.next_player_index()?; let session_id = broker.allocate_session_id(player_index); - let cipher_key = keygenerator.lock().unwrap().next().expect("key generator should never exhaust"); + let cipher_key = keygenerator + .lock() + .unwrap() + .next() + .expect("key generator should never exhaust"); let server_session = ServerSession::new(session_id, cipher_key, conn).await?; server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? Ok(()) diff --git a/crates/longboy/src/proto/receiver.rs b/crates/longboy/src/proto/receiver.rs index cd35084..b8822dd 100644 --- a/crates/longboy/src/proto/receiver.rs +++ b/crates/longboy/src/proto/receiver.rs @@ -55,7 +55,10 @@ where #[allow(non_snake_case)] let MAX_BUFFERED: usize = Constants::::MAX_BUFFERED; - tracing::trace!("Handling datagram with timestamp {timestamp} at local cycle {}", self.cycle); + tracing::trace!( + "Handling datagram with timestamp {timestamp} at local cycle {}", + self.cycle + ); tracing::trace!("Flags: {:?}", self.flags); // Grab cycle and timestamp. @@ -98,7 +101,9 @@ where if cycle_diff > MAX_BUFFERED { // hard warning - tracing::error!("Datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff} is too far ahead"); + tracing::error!( + "Datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff} is too far ahead" + ); for _ in 0..(cycle_diff - MAX_BUFFERED) { @@ -118,7 +123,11 @@ where // already adanced the local cycle to catch up, if applicable. if ((cycle_i + MAX_CYCLE) - self.cycle) % MAX_CYCLE > MAX_BUFFERED { - tracing::trace!("Stopping sink input at cycle {} because it's too far ahead of local cycle {}", cycle_i, self.cycle); + tracing::trace!( + "Stopping sink input at cycle {} because it's too far ahead of local cycle {}", + cycle_i, + self.cycle + ); break; } diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 14b83cf..690233d 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -162,7 +162,10 @@ where { for socket in self.sockets.values() { - trace!("Sending datagram to socket address {} via socket {:?}", socket_addr, socket); + trace!( + "Sending datagram to socket address {} via socket {:?}", + socket_addr, socket + ); socket.send_to(datagram, socket_addr).expect("send_to failure"); } } From c3f5a217104ddc288d068b7d7daf8fcb2b46357c Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Mon, 6 Apr 2026 22:38:42 -0500 Subject: [PATCH 22/23] chore(lockfile): making executables changes things --- .gitignore | 3 - Cargo.lock | 2449 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2449 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 1747ab8..ead180b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,6 @@ debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c648f1d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2449 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + +[[package]] +name = "cipher" +version = "0.5.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71c893d5a1e8257048dbb29954d2e1f85f091a150304f1defe4ca2806da5d3f" +dependencies = [ + "crypto-common 0.2.0-rc.10", + "inout", + "zeroize", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "config" +version = "0.15.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fc0003068acd7e9cb6659fd956dc4d671f102a06cc115990b9e7bb5745c25" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common 0.1.7", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "longboy" +version = "1.0.0" +dependencies = [ + "anyhow", + "cipher", + "derive_builder", + "enum-map", + "flume 0.11.1", + "fnv", + "parking_lot", + "quinn", + "rc5", + "rcgen", + "thunderdome", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "longboy-client-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "crossterm", + "flume 0.12.0", + "longboy", + "longboy_schema", + "quinn", + "rand 0.9.2", + "rustls", + "rustls-native-certs", + "serde", + "tokio", + "tokio-util", +] + +[[package]] +name = "longboy-server-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "enum-map", + "flume 0.12.0", + "longboy", + "longboy_schema", + "quinn", + "rand 0.10.0", + "rand-unique", + "rustls", + "serde", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "longboy_schema" +version = "1.0.0" +dependencies = [ + "longboy", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "fastbloom", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand-unique" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f45448a2f51627504e8592dc11fe0e46828de85a29b412671e726843d738fe1" +dependencies = [ + "num-traits", + "rand 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rc5" +version = "0.1.0-pre" +source = "git+https://github.com/RustCrypto/block-ciphers.git?rev=36b34efddc5da2340c4e08e09f590f2200c715ba#36b34efddc5da2340c4e08e09f590f2200c715ba" +dependencies = [ + "cipher", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thunderdome" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e170f93360bf9ae6fe3c31116bbf27adb1d054cedd6bc3d7857e34f2d98d0b" + +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" From 61e973e8941d25bdc58fdd47aedf7b60bc62b4ac Mon Sep 17 00:00:00 2001 From: Martin Hollstein Date: Mon, 6 Apr 2026 22:43:18 -0500 Subject: [PATCH 23/23] fix(try_blocks): remove feature macro decl --- crates/longboy/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/longboy/src/lib.rs b/crates/longboy/src/lib.rs index 4df8094..82e618f 100644 --- a/crates/longboy/src/lib.rs +++ b/crates/longboy/src/lib.rs @@ -2,7 +2,6 @@ #![feature(generic_const_exprs)] #![feature(generic_const_items)] #![feature(map_try_insert)] -#![feature(try_blocks)] // API mod client;