From 9f66a28017a6d167c1c72db1217cd36a44b8a27a Mon Sep 17 00:00:00 2001 From: dank_meme01 <42031238+dankmeme01@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:53:27 +0100 Subject: [PATCH] where is 2.2.... :( --- server/game/src/data/types/gd.rs | 5 +- server/game/src/main.rs | 213 +++++++---- server/game/src/server.rs | 44 ++- .../game/src/server_thread/handlers/game.rs | 4 +- server/game/src/server_thread/handlers/mod.rs | 5 +- server/game/src/server_thread/mod.rs | 12 +- server/game/src/util/logger.rs | 6 +- server/readme.md | 70 ++-- src/data/types/gd.hpp | 7 +- src/net/network_manager.cpp | 2 +- src/ui/error_check_node.cpp | 11 + src/ui/error_check_node.hpp | 14 +- src/ui/menu/main/globed_menu_layer.cpp | 45 ++- src/ui/menu/main/globed_menu_layer.hpp | 31 +- src/ui/menu/main/server_list_cell.cpp | 10 + src/ui/menu/main/server_list_cell.hpp | 13 +- src/ui/menu/main/signup_layer.cpp | 13 +- src/ui/menu/main/signup_layer.hpp | 14 +- src/ui/menu/main/signup_popup.cpp | 15 +- src/ui/menu/main/signup_popup.hpp | 24 +- src/ui/menu/player_list/player_list_cell.cpp | 67 ++++ src/ui/menu/player_list/player_list_cell.hpp | 20 + src/ui/menu/player_list/player_list_popup.cpp | 95 +++++ src/ui/menu/player_list/player_list_popup.hpp | 26 ++ tests/CMakeLists.txt | 50 --- tests/FindSodium.cmake | 294 --------------- tests/testdef.hpp | 9 - tests/tests.cpp | 356 ------------------ 28 files changed, 557 insertions(+), 918 deletions(-) create mode 100644 src/ui/menu/player_list/player_list_cell.cpp create mode 100644 src/ui/menu/player_list/player_list_cell.hpp create mode 100644 src/ui/menu/player_list/player_list_popup.cpp create mode 100644 src/ui/menu/player_list/player_list_popup.hpp delete mode 100644 tests/CMakeLists.txt delete mode 100644 tests/FindSodium.cmake delete mode 100644 tests/testdef.hpp delete mode 100644 tests/tests.cpp diff --git a/server/game/src/data/types/gd.rs b/server/game/src/data/types/gd.rs index dbdc6961..88a5e071 100644 --- a/server/game/src/data/types/gd.rs +++ b/server/game/src/data/types/gd.rs @@ -150,13 +150,14 @@ size_calc_impl!( ); impl PlayerAccountData { - pub fn make_preview(&self) -> PlayerPreviewAccountData { + pub fn make_preview(&self, level_id: i32) -> PlayerPreviewAccountData { PlayerPreviewAccountData { account_id: self.account_id, name: self.name.clone(), cube: self.icons.cube, color1: self.icons.color1, color2: self.icons.color2, + level_id, } } } @@ -170,6 +171,7 @@ pub struct PlayerPreviewAccountData { pub cube: i16, pub color1: i16, pub color2: i16, + pub level_id: i32, } encode_impl!(PlayerPreviewAccountData, buf, self, { @@ -178,6 +180,7 @@ encode_impl!(PlayerPreviewAccountData, buf, self, { buf.write_i16(self.cube); buf.write_i16(self.color1); buf.write_i16(self.color2); + buf.write_i32(self.level_id); }); decode_unimpl!(PlayerPreviewAccountData); diff --git a/server/game/src/main.rs b/server/game/src/main.rs index 1459047a..b39ebbbc 100644 --- a/server/game/src/main.rs +++ b/server/game/src/main.rs @@ -4,7 +4,7 @@ * Everything you see here is the exact definition of over-engineered and over-optimized. * Good luck. */ - +#![feature(sync_unsafe_cell)] #![allow( clippy::must_use_candidate, clippy::module_name_repetitions, @@ -17,13 +17,15 @@ use std::{ collections::{HashMap, HashSet}, error::Error, + net::SocketAddr, }; -use anyhow::anyhow; use globed_shared::{GameServerBootData, PROTOCOL_VERSION}; use log::{error, info, warn, LevelFilter}; +use reqwest::StatusCode; use server::GameServerConfiguration; use state::ServerState; +use tokio::net::UdpSocket; use util::Logger; use server::GameServer; @@ -35,120 +37,197 @@ pub mod server_thread; pub mod state; pub mod util; -#[tokio::main] -async fn main() -> Result<(), Box> { - log::set_logger(Logger::instance()).unwrap(); +struct StartupConfiguration { + bind_address: SocketAddr, + central_data: Option<(String, String)>, +} - if std::env::var("GLOBED_GS_LESS_LOG").unwrap_or("0".to_string()) == "1" { - log::set_max_level(LevelFilter::Warn); - } else { - log::set_max_level(if cfg!(debug_assertions) { - LevelFilter::Trace - } else { - LevelFilter::Info - }); +fn parse_configuration() -> StartupConfiguration { + let mut args = std::env::args(); + let exe_name = args.next().unwrap(); // skip executable + let arg = args.next(); + + let env_addr = std::env::var("GLOBED_GS_ADDRESS"); + let using_env_variables: bool = env_addr.is_ok(); + + if arg.is_none() && !using_env_variables { + // standalone with default params + return StartupConfiguration { + bind_address: "0.0.0.0:41001".parse().unwrap(), + central_data: None, + }; } - let mut host_address = String::new(); - let mut central_url = String::new(); - let mut central_pw = String::new(); + // env variable takes precedence, otherwise grab the 1st arg from the command line + let bind_address = env_addr.ok().or(arg).unwrap(); - let mut args = std::env::args(); - let exe_name = args.next().unwrap(); // skip executable - let arg_hostaddr = args.next(); - let arg_central = args.next(); - let arg_password = args.next(); - - if arg_hostaddr.is_some() { - host_address = arg_hostaddr.unwrap(); - if arg_central.is_some() { - central_url = arg_central.unwrap(); - if arg_password.is_some() { - central_pw = arg_password.unwrap(); - } + let bind_address = match bind_address.parse::() { + Ok(x) => x, + Err(e) => { + error!("failed to parse the given IP address ({bind_address}): {e}"); + warn!("hint: you have to pass a valid IPv4 address with a port number, for example 0.0.0.0:41000"); + error!("aborting launch due to misconfiguration"); + std::process::exit(1); } - } + }; + + let arg = if using_env_variables { + std::env::var("GLOBED_GS_CENTRAL_URL").ok() + } else { + args.next() + }; - if host_address.is_empty() { - host_address = std::env::var("GLOBED_GS_ADDRESS").unwrap_or_default(); + if arg.is_none() { + // standalone with a specified bind addr + return StartupConfiguration { + bind_address, + central_data: None, + }; } - if central_url.is_empty() { - central_url = std::env::var("GLOBED_GS_CENTRAL_URL").unwrap_or_default(); + let mut central_url = arg.unwrap(); + if !central_url.ends_with('/') { + central_url += "/"; } - if central_pw.is_empty() { - central_pw = std::env::var("GLOBED_GS_CENTRAL_PASSWORD").unwrap_or_default(); + let arg = if using_env_variables { + std::env::var("GLOBED_GS_CENTRAL_PASSWORD").ok() + } else { + args.next() + }; + + if arg.is_none() { + if using_env_variables { + error!("expected the environment variable 'GLOBED_GS_CENTRAL_PASSWORD', couldn't find it"); + } else { + error!("not enough arguments, expected the password of the central server"); + error!("correct usage: \"{exe_name}
\""); + } + warn!("hint: you must specify the password for connecting to the central server, see the server readme."); + error!("aborting launch due to misconfiguration"); + std::process::exit(1); } - if central_url.is_empty() || central_pw.is_empty() || host_address.is_empty() { - error!("Some of the configuration values are not set, aborting launch due to misconfiguration."); - error!("Correct usage: {exe_name}
"); - error!( - "or use the environment variables 'GLOBED_GS_ADDRESS', 'GLOBED_GS_CENTRAL_URL' and 'GLOBED_GS_CENTRAL_PASSWORD'" - ); + let central_pw = arg.unwrap(); - panic!("aborting due to misconfiguration"); + // full configuration with a central server + StartupConfiguration { + bind_address, + central_data: Some((central_url, central_pw)), } +} - if central_url != "none" && !central_url.ends_with('/') { - central_url += "/"; +#[tokio::main] +async fn main() -> Result<(), Box> { + log::set_logger(Logger::instance()).unwrap(); + + if std::env::var("GLOBED_GS_LESS_LOG").unwrap_or("0".to_string()) == "1" { + log::set_max_level(LevelFilter::Warn); + } else { + log::set_max_level(if cfg!(debug_assertions) { + LevelFilter::Trace + } else { + LevelFilter::Info + }); } + let startup_config = parse_configuration(); + let standalone = startup_config.central_data.is_none(); + let client = reqwest::Client::builder() .user_agent(format!("globed-game-server/{}", env!("CARGO_PKG_VERSION"))) .build() .unwrap(); - let config = GameServerConfiguration { + let mut config = GameServerConfiguration { http_client: client, - central_url, - central_pw, + central_url: String::new(), + central_pw: String::new(), }; let state = ServerState::new(); - - let (gsbd, standalone) = if config.central_url == "none" { + let gsbd = if standalone { warn!("Starting in standalone mode, authentication is disabled"); - - let gsbd = GameServerBootData { + GameServerBootData { protocol: PROTOCOL_VERSION, no_chat: HashSet::new(), special_users: HashMap::new(), - }; - - (gsbd, true) + } } else { + let (central_url, central_pw) = startup_config.central_data.unwrap(); + config.central_url = central_url; + config.central_pw = central_pw; + info!("Retrieving config from the central server.."); - let response = config + let response = match config .http_client .post(format!("{}{}", config.central_url, "gs/boot")) .query(&[("pw", config.central_pw.clone())]) .send() - .await? - .error_for_status() - .map_err(|e| anyhow!("central server returned an error: {e}"))?; + .await + { + Ok(x) => match x.error_for_status() { + Ok(x) => x, + Err(err) => { + error!("the central server returned an error: {err}"); + if err.status().unwrap_or(StatusCode::OK) == StatusCode::UNAUTHORIZED { + warn!("hint: there is a high chance that you have supplied a wrong password"); + } + error!("aborting launch due to misconfiguration"); + std::process::exit(1); + } + }, + Err(err) => { + error!("failed to make a request to the central server: {err}"); + error!("aborting launch due to misconfiguration"); + std::process::exit(1); + } + }; let configuration = response.text().await?; - let boot_data: GameServerBootData = serde_json::from_str(&configuration)?; + let boot_data: GameServerBootData = match serde_json::from_str(&configuration) { + Ok(x) => x, + Err(err) => { + error!("failed to parse the data sent by the central server: {err}"); + error!("aborting launch due to misconfiguration"); + std::process::exit(1); + } + }; if boot_data.protocol != PROTOCOL_VERSION { - error!("Incompatible protocol versions!"); + error!("incompatible protocol versions!"); error!( - "This game server is on {}, while the central server uses {}", - PROTOCOL_VERSION, boot_data.protocol + "this game server is on v{PROTOCOL_VERSION}, while the central server uses v{}", + boot_data.protocol ); - panic!("aborting due to incompatible protocol versions"); + error!("aborting launch due to incompatible protocol versions"); + std::process::exit(1); } - (boot_data, false) + boot_data + }; + + let socket = match UdpSocket::bind(&startup_config.bind_address).await { + Ok(x) => x, + Err(err) => { + error!( + "Failed to bind the socket with address {}: {err}", + startup_config.bind_address + ); + if startup_config.bind_address.port() < 1024 { + warn!("hint: ports below 1024 are commonly privileged and you can't use them as a regular user"); + warn!("hint: pick a higher port number or use port 0 to get a randomly generated port"); + } + error!("aborting launch due to an unexpected error"); + std::process::exit(1); + } }; - let server = GameServer::new(host_address, state, gsbd, config, standalone).await; + let server = GameServer::new(socket, state, gsbd, config, standalone); let server = Box::leak(Box::new(server)); - Box::pin(server.run()).await?; + Box::pin(server.run()).await; Ok(()) } diff --git a/server/game/src/server.rs b/server/game/src/server.rs index a0d811c9..6449d9f4 100644 --- a/server/game/src/server.rs +++ b/server/game/src/server.rs @@ -42,8 +42,8 @@ pub struct GameServer { } impl GameServer { - pub async fn new( - address: String, + pub fn new( + socket: UdpSocket, state: ServerState, central_conf: GameServerBootData, config: GameServerConfiguration, @@ -51,7 +51,7 @@ impl GameServer { ) -> Self { Self { state, - socket: UdpSocket::bind(&address).await.unwrap(), + socket, threads: SyncMutex::new(FxHashMap::default()), secret_key: SecretKey::generate(&mut OsRng), central_conf: SyncMutex::new(central_conf), @@ -60,8 +60,8 @@ impl GameServer { } } - pub async fn run(&'static self) -> anyhow::Result<()> { - info!("Server launched on {}", self.socket.local_addr()?); + pub async fn run(&'static self) -> ! { + info!("Server launched on {}", self.socket.local_addr().unwrap()); if !self.standalone { tokio::spawn(async move { @@ -78,8 +78,11 @@ impl GameServer { }); } + // preallocate a buffer + let mut buf = [0u8; MAX_PACKET_SIZE]; + loop { - match self.recv_and_handle().await { + match self.recv_and_handle(&mut buf).await { Ok(()) => {} Err(err) => { warn!("Failed to handle a packet: {err}"); @@ -120,7 +123,12 @@ impl GameServer { .lock() .values() .filter(|thr| thr.authenticated.load(Ordering::Relaxed)) - .map(|thread| thread.account_data.lock().make_preview()) + .map(|thread| { + thread + .account_data + .lock() + .make_preview(thread.level_id.load(Ordering::Relaxed)) + }) .fold(0, |count, preview| count + usize::from(f(&preview, count, additional))) } @@ -179,13 +187,12 @@ impl GameServer { Ok(()) } - async fn recv_and_handle(&'static self) -> anyhow::Result<()> { - let mut buf = [0u8; MAX_PACKET_SIZE]; - let (len, peer) = self.socket.recv_from(&mut buf).await?; + async fn recv_and_handle(&'static self, buf: &mut [u8]) -> anyhow::Result<()> { + let (len, peer) = self.socket.recv_from(buf).await?; let peer = match peer { - SocketAddr::V6(_) => bail!("rejecting request from ipv6 host"), SocketAddr::V4(x) => x, + SocketAddr::V6(_) => bail!("rejecting request from ipv6 host"), }; let thread = self.threads.lock().get(&peer).cloned(); @@ -213,6 +220,21 @@ impl GameServer { thread_cl }; + // check if the client is sending too many packets + // safety: `thread.rate_limiter` is only used here and never accessed anywhere else. + // it is also guaranteed to not be a nullptr, as per `UnsafeCell::get`. + unsafe { + let rate_limiter = thread.rate_limiter.get(); + if !rate_limiter.as_mut().unwrap_unchecked().try_tick() { + if cfg!(debug_assertions) { + bail!("{peer} is ratelimited"); + } + + // silently reject the packet in release mode + return Ok(()); + } + } + // don't heap allocate for small packets let message = if len <= SMALL_PACKET_LIMIT { let mut smallbuf = [0u8; SMALL_PACKET_LIMIT]; diff --git a/server/game/src/server_thread/handlers/game.rs b/server/game/src/server_thread/handlers/game.rs index e7ae48b1..760489e5 100644 --- a/server/game/src/server_thread/handlers/game.rs +++ b/server/game/src/server_thread/handlers/game.rs @@ -151,8 +151,6 @@ impl GameServerThread { gs_handler!(self, handle_request_player_list, RequestPlayerListPacket, _packet, { gs_needauth!(self); - let account_id = self.account_id.load(Ordering::Relaxed); - let player_count = self.game_server.state.player_count.load(Ordering::Relaxed); let encoded_size = size_of_types!(PlayerPreviewAccountData) * player_count as usize; @@ -163,7 +161,7 @@ impl GameServerThread { let written = self.game_server.for_every_player_preview( move |preview, count, buf| { // we do additional length check because player count may have increased since then - if count < player_count as usize && preview.account_id != account_id { + if count < player_count as usize { buf.write_value(preview); true } else { diff --git a/server/game/src/server_thread/handlers/mod.rs b/server/game/src/server_thread/handlers/mod.rs index 4f5d9e6b..978d7350 100644 --- a/server/game/src/server_thread/handlers/mod.rs +++ b/server/game/src/server_thread/handlers/mod.rs @@ -66,8 +66,9 @@ pub const MAX_ALLOCA_SIZE: usize = 100_000; macro_rules! gs_alloca_check_size { ($size:expr) => { - if $size > crate::server_thread::handlers::MAX_ALLOCA_SIZE { - let err = crate::server_thread::error::PacketHandlingError::DangerousAllocation($size); + let size = $size; + if size > crate::server_thread::handlers::MAX_ALLOCA_SIZE { + let err = crate::server_thread::error::PacketHandlingError::DangerousAllocation(size); return Err(err); } }; diff --git a/server/game/src/server_thread/mod.rs b/server/game/src/server_thread/mod.rs index e7903417..550a09c8 100644 --- a/server/game/src/server_thread/mod.rs +++ b/server/game/src/server_thread/mod.rs @@ -1,4 +1,5 @@ use std::{ + cell::SyncUnsafeCell, net::SocketAddrV4, sync::{ atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}, @@ -57,7 +58,7 @@ pub struct GameServerThread { pub account_data: SyncMutex, last_voice_packet: AtomicU64, - rate_limiter: SyncMutex, + pub rate_limiter: SyncUnsafeCell, // do NOT interact with this field oustide of GameServer. } impl GameServerThread { @@ -65,6 +66,8 @@ impl GameServerThread { pub fn new(peer: SocketAddrV4, game_server: &'static GameServer) -> Self { let (tx, rx) = mpsc::channel::(CHANNEL_BUFFER_SIZE); + let rate_limiter = SimpleRateLimiter::new(SERVER_TPS + 5, Duration::from_millis(950)); + let rate_limiter = SyncUnsafeCell::new(rate_limiter); Self { tx, rx: Mutex::new(rx), @@ -77,7 +80,7 @@ impl GameServerThread { awaiting_termination: AtomicBool::new(false), account_data: SyncMutex::new(PlayerAccountData::default()), last_voice_packet: AtomicU64::new(0), - rate_limiter: SyncMutex::new(SimpleRateLimiter::new(SERVER_TPS + 5, Duration::from_millis(950))), + rate_limiter, } } @@ -325,11 +328,6 @@ impl GameServerThread { return Err(PacketHandlingError::MalformedMessage); } - // check if we are sending too many packets - if !self.rate_limiter.lock().try_tick() { - return Err(PacketHandlingError::Ratelimited); - } - let mut data = ByteReader::from_bytes(message); let header = data.read_packet_header()?; diff --git a/server/game/src/util/logger.rs b/server/game/src/util/logger.rs index 57883f0e..ca117822 100644 --- a/server/game/src/util/logger.rs +++ b/server/game/src/util/logger.rs @@ -50,7 +50,11 @@ impl log::Log for Logger { Level::Trace => (record.level().to_string().black(), record.args().to_string().black()), }; - println!("[{formatted_time}] [{level}] - {args}"); + if record.level() == Level::Error { + eprintln!("[{formatted_time}] [{level}] - {args}"); + } else { + println!("[{formatted_time}] [{level}] - {args}"); + } } fn flush(&self) {} diff --git a/server/readme.md b/server/readme.md index d30454d3..55ec0cfd 100644 --- a/server/readme.md +++ b/server/readme.md @@ -1,16 +1,50 @@ # Globed Server -Prebuilt server binaries are available for Linux (x64 and ARM64) and Windows (x64) in every GitHub release. The server is *extremely* optimized so it is very lightweight and will run well even on weak hardware. +If you want to host a server yourself, the process is as easy as downloading the server binaries from the latest GitHub release, named `globed-central-server` and `globed-game-server`. Depending on your OS and architecture, you want the one ending in `.exe` on Windows, the `-x64` one on Linux x64, and the `-arm64` one on Linux ARM64. -If you want to build it yourself, do note that the central server must be compiled with a nightly Rust toolchain, until `async fn` in traits becomes stable (which is set to be in Rust 1.75). +If you want to build the server yourself, you need a nightly Rust toolchain. After that, it's as simple as: +```sh +cd server/ +rustup override set nightly # has to be done only once +cargo build --release +``` + +## Game server configuration + +note: if you are not on Windows, in the following examples replace `set` with `export` and replace `globed-game-server.exe` with the appropriate server binary (such as `globed-game-server-x64`) + +If you want to spin up a quick, standalone game server, without needing to start a central server, then it is as simple as running the `globed-game-server.exe` executable directly. + +If you want to change the address then you'll have to run the executable with an additional argument like so: +```sh +# replace 0.0.0.0:41001 with your address +globed-game-server.exe 0.0.0.0:41001 +``` + +Keep in mind that this makes the configuration very limited (for example you can't blacklist/whitelist users anymore) and disables any kind of player authentication. -## Central server +To start the game server and bridge it together with an active central server +you must use the password from the `game_server_password` option in the central server configuration. Then, you have 2 options whenever you start the server: -The central server uses a single JSON file for configuration. By default, the file is created with the name `central-conf.json` in the current working directory when you run the server, but it can be overriden with the environment variable `GLOBED_CONFIG_PATH`. The path can be a folder or a full file path. +```sh +globed-game-server.exe 0.0.0.0:41001 http://127.0.0.1:41000 password + +# or like this: -### Central server configuration +set GLOBED_GS_ADDRESS=0.0.0.0:41001 +set GLOBED_GS_CENTRAL_URL=http://127.0.0.1:41000 +set GLOBED_GS_CENTRAL_PASSWORD=password +globed-game-server.exe +``` + +Replace `0.0.0.0:41001` with the address you want the game server to listen on, `http://127.0.0.1:41000` with the URL of your central server, and `password` with the password. + +## Central server configuration + +The central server allows configuration hot reloading, so you can modify the configuration file and see updates in real time without restarting the server. + +By default, the file is created with the name `central-conf.json` in the current working directory when you run the server, but it can be overriden with the environment variable `GLOBED_CONFIG_PATH`. The path can be a folder or a full file path. -The central server allows configuration hot reloading, so you can modify the file and see updates in real time without restarting the server. Note that the server is written with security in mind, so many of those options may not be exactly interesting for you. If you are hosting a small server for your friends then the defaults should be good enough, however if you are hosting a big public server, it is recommended that you adjust the settings accordingly. @@ -40,28 +74,8 @@ Note that the server is written with security in mind, so many of those options ** - it may take a few minutes for you to see any changes -## Game server - -To bridge the servers together, you must use the password from the `game_server_password` option in the central server configuration. Then, you have 2 options whenever you start the server: - -```sh -./globed-game-server.exe 0.0.0.0:41001 http://127.0.0.1:41000 password - -# or like this: - -# on linux remember to replace `set` with `export` and all that -set GLOBED_GS_ADDRESS=0.0.0.0:41001 -set GLOBED_GS_CENTRAL_URL=http://127.0.0.1:41000 -set GLOBED_GS_CENTRAL_PASSWORD=password -./globed-game-server.exe -``` - -Replace `0.0.0.0:41001` with the address you want the game server to listen on, `http://127.0.0.1:41000` with the URL of your central server, and `password` with the password. - -**You can start the game server in a standalone mode (without needing a central server)**. For that, simply replace the central URL with the string `none`. Do keep in mind this disables player authentication completely and is recommended only for testing purposes. - ## Extra -In debug builds, all logging is enabled. In release builds, the `Debug` and `Trace` levels are disabled, so you will only see logs with levels `Info`, `Warn` and `Error`. +In release builds, the `Debug` and `Trace` levels are disabled, so you will only see logs with levels `Info`, `Warn` and `Error`. -This can be changed by setting the environment variable `GLOBED_LESS_LOG=1` for the central server, or `GLOBED_GS_LESS_LOG=1` for the game server. With this option, only logs with levels `Warn` and `Error` will be printed (in both debug and release builds) +This can be changed by setting the environment variable `GLOBED_LESS_LOG=1` for the central server, or `GLOBED_GS_LESS_LOG=1` for the game server. With this option, only logs with levels `Warn` and `Error` will be printed. diff --git a/src/data/types/gd.hpp b/src/data/types/gd.hpp index 48b05612..386c62ee 100644 --- a/src/data/types/gd.hpp +++ b/src/data/types/gd.hpp @@ -93,8 +93,8 @@ class PlayerAccountData { class PlayerPreviewAccountData { public: - PlayerPreviewAccountData(int32_t id, std::string name, int16_t cube, int16_t color1, int16_t color2) - : id(id), name(name), cube(cube), color1(color1), color2(color2) {} + PlayerPreviewAccountData(int32_t id, std::string name, int16_t cube, int16_t color1, int16_t color2, int32_t levelId) + : id(id), name(name), cube(cube), color1(color1), color2(color2), levelId(levelId) {} PlayerPreviewAccountData() {} GLOBED_ENCODE { @@ -103,6 +103,7 @@ class PlayerPreviewAccountData { buf.writeI16(cube); buf.writeI16(color1); buf.writeI16(color2); + buf.writeI32(levelId); } GLOBED_DECODE { @@ -111,11 +112,13 @@ class PlayerPreviewAccountData { cube = buf.readI16(); color1 = buf.readI16(); color2 = buf.readI16(); + levelId = buf.readI32(); } int32_t id; std::string name; int16_t cube, color1, color2; + int32_t levelId; }; class PlayerData { diff --git a/src/net/network_manager.cpp b/src/net/network_manager.cpp index 54289f59..29fb8b71 100644 --- a/src/net/network_manager.cpp +++ b/src/net/network_manager.cpp @@ -232,7 +232,7 @@ void NetworkManager::threadRecvFunc() { geode::Loader::get()->queueInMainThread([this, packetId, packet]() { auto listeners_ = this->listeners.lock(); if (!listeners_->contains(packetId)) { - ErrorQueues::get().warn(fmt::format("Unhandled packet: {}", packetId)); + ErrorQueues::get().debugWarn(fmt::format("Unhandled packet: {}", packetId)); } else { // xd (*listeners_)[packetId](packet); diff --git a/src/ui/error_check_node.cpp b/src/ui/error_check_node.cpp index 7c1a1a0e..516164d3 100644 --- a/src/ui/error_check_node.cpp +++ b/src/ui/error_check_node.cpp @@ -54,4 +54,15 @@ void ErrorCheckNode::updateErrors(float _unused) { for (auto& notice : notices) { FLAlertLayer::create("Globed notice", notice, "Ok")->show(); } +} + +ErrorCheckNode* ErrorCheckNode::create() { + auto* ret = new ErrorCheckNode; + if (ret && ret->init()) { + ret->autorelease(); + return ret; + } + + CC_SAFE_DELETE(ret); + return nullptr; } \ No newline at end of file diff --git a/src/ui/error_check_node.hpp b/src/ui/error_check_node.hpp index df238f74..67ceeb15 100644 --- a/src/ui/error_check_node.hpp +++ b/src/ui/error_check_node.hpp @@ -3,17 +3,9 @@ class ErrorCheckNode : public cocos2d::CCNode { public: + static ErrorCheckNode* create(); + +protected: bool init(); void updateErrors(float _unused); - - static ErrorCheckNode* create() { - auto* ret = new ErrorCheckNode; - if (ret && ret->init()) { - ret->autorelease(); - return ret; - } - - CC_SAFE_DELETE(ret); - return nullptr; - } }; \ No newline at end of file diff --git a/src/ui/menu/main/globed_menu_layer.cpp b/src/ui/menu/main/globed_menu_layer.cpp index 2644758b..6779b593 100644 --- a/src/ui/menu/main/globed_menu_layer.cpp +++ b/src/ui/menu/main/globed_menu_layer.cpp @@ -4,6 +4,7 @@ #include #include "server_list_cell.hpp" +#include #include #include #include @@ -50,7 +51,11 @@ bool GlobedMenuLayer::init() { .scale(1.2f) .pos({-250.f, -70.f}) .intoMenuItem([this](auto) { - this->requestServerList(); + // this->requestServerList(); + if (auto* popup = PlayerListPopup::create()) { + popup->m_noElasticity = true; + popup->show(); + } }) .intoNewParent(CCMenu::create()) .id("btn-refresh-servers") @@ -58,15 +63,15 @@ bool GlobedMenuLayer::init() { // TODO prod remove wipe authtoken button - Build::createSpriteName("d_skull01_001.png") - .scale(1.2f) - .pos(-250.f, -30.f) - .intoMenuItem([this](auto) { - GlobedAccountManager::get().clearAuthKey(); - }) - .intoNewParent(CCMenu::create()) - .id("btn-clear-authtoken") - .parent(this); + // Build::createSpriteName("d_skull01_001.png") + // .scale(1.2f) + // .pos(-250.f, -30.f) + // .intoMenuItem([this](auto) { + // GlobedAccountManager::get().clearAuthKey(); + // }) + // .intoNewParent(CCMenu::create()) + // .id("btn-clear-authtoken") + // .parent(this); util::ui::addBackground(this); @@ -203,4 +208,24 @@ void GlobedMenuLayer::keyBackClicked() { void GlobedMenuLayer::pingServers(float _) { NetworkManager::get().taskPingServers(); +} + +GlobedMenuLayer* GlobedMenuLayer::create() { + auto ret = new GlobedMenuLayer; + if (ret && ret->init()) { + ret->autorelease(); + return ret; + } + + CC_SAFE_DELETE(ret); + return nullptr; +} + +cocos2d::CCScene* GlobedMenuLayer::scene() { + auto layer = GlobedMenuLayer::create(); + auto scene = cocos2d::CCScene::create(); + layer->setPosition({0.f, 0.f}); + scene->addChild(layer); + + return scene; } \ No newline at end of file diff --git a/src/ui/menu/main/globed_menu_layer.hpp b/src/ui/menu/main/globed_menu_layer.hpp index b0d715bb..f8b5d0ad 100644 --- a/src/ui/menu/main/globed_menu_layer.hpp +++ b/src/ui/menu/main/globed_menu_layer.hpp @@ -7,34 +7,17 @@ class GlobedMenuLayer : public cocos2d::CCLayer { static constexpr float LIST_WIDTH = 358.f; static constexpr float LIST_HEIGHT = 220.f; + static GlobedMenuLayer* create(); + static cocos2d::CCScene* scene(); + +private: + GJListLayer* listLayer; + GlobedSignupLayer* signupLayer; + bool init(); cocos2d::CCArray* createServerList(); void refreshServerList(float _); void requestServerList(); void keyBackClicked(); void pingServers(float _); - - static GlobedMenuLayer* create() { - auto ret = new GlobedMenuLayer; - if (ret && ret->init()) { - ret->autorelease(); - return ret; - } - - CC_SAFE_DELETE(ret); - return nullptr; - } - - static cocos2d::CCScene* scene() { - auto layer = GlobedMenuLayer::create(); - auto scene = cocos2d::CCScene::create(); - layer->setPosition({0.f, 0.f}); - scene->addChild(layer); - - return scene; - } - -private: - GJListLayer* listLayer; - GlobedSignupLayer* signupLayer; }; \ No newline at end of file diff --git a/src/ui/menu/main/server_list_cell.cpp b/src/ui/menu/main/server_list_cell.cpp index 8d9028bc..c74809f8 100644 --- a/src/ui/menu/main/server_list_cell.cpp +++ b/src/ui/menu/main/server_list_cell.cpp @@ -144,4 +144,14 @@ void ServerListCell::requestTokenAndConnect() { am.clearAuthKey(); }) .send(); +} + +ServerListCell* ServerListCell::create(const GameServerView& gsview, bool active) { + auto ret = new ServerListCell; + if (ret && ret->init(gsview, active)) { + return ret; + } + + CC_SAFE_DELETE(ret); + return nullptr; } \ No newline at end of file diff --git a/src/ui/menu/main/server_list_cell.hpp b/src/ui/menu/main/server_list_cell.hpp index 1bb1532d..f85192e8 100644 --- a/src/ui/menu/main/server_list_cell.hpp +++ b/src/ui/menu/main/server_list_cell.hpp @@ -9,19 +9,10 @@ class ServerListCell : public cocos2d::CCLayer { static constexpr cocos2d::ccColor3B ACTIVE_COLOR = {0, 255, 25}; static constexpr cocos2d::ccColor3B INACTIVE_COLOR = {255, 255, 255}; - bool init(const GameServerView& gsview, bool active); void updateWith(const GameServerView& gsview, bool active); void requestTokenAndConnect(); - static ServerListCell* create(const GameServerView& gsview, bool active) { - auto ret = new ServerListCell; - if (ret && ret->init(gsview, active)) { - return ret; - } - - CC_SAFE_DELETE(ret); - return nullptr; - } + static ServerListCell* create(const GameServerView& gsview, bool active); GameServerView gsview; bool active; @@ -31,4 +22,6 @@ class ServerListCell : public cocos2d::CCLayer { cocos2d::CCLabelBMFont *labelName, *labelPing, *labelExtra; cocos2d::CCMenu* btnMenu; CCMenuItemSpriteExtra* btnConnect = nullptr; + + bool init(const GameServerView& gsview, bool active); }; \ No newline at end of file diff --git a/src/ui/menu/main/signup_layer.cpp b/src/ui/menu/main/signup_layer.cpp index d11fcc8f..c744a8e6 100644 --- a/src/ui/menu/main/signup_layer.cpp +++ b/src/ui/menu/main/signup_layer.cpp @@ -27,7 +27,7 @@ bool GlobedSignupLayer::init() { geode::createQuickPopup("Notice", CONSENT_MESSAGE, "Cancel", "Ok", [](auto, bool agreed){ if (agreed) { GlobedSettings::get().setFlag("seen-signup-notice"); - GlobedSignupPopup::create()->show(); + GlobedSignupPopup::create()->show(); } }); } else { @@ -40,4 +40,15 @@ bool GlobedSignupLayer::init() { .parent(listLayer); return true; +} + +GlobedSignupLayer* GlobedSignupLayer::create() { + auto ret = new GlobedSignupLayer; + if (ret && ret->init()) { + ret->autorelease(); + return ret; + } + + CC_SAFE_DELETE(ret); + return nullptr; } \ No newline at end of file diff --git a/src/ui/menu/main/signup_layer.hpp b/src/ui/menu/main/signup_layer.hpp index 2a00dac5..d4cda1e3 100644 --- a/src/ui/menu/main/signup_layer.hpp +++ b/src/ui/menu/main/signup_layer.hpp @@ -5,20 +5,12 @@ class GlobedSignupLayer : public cocos2d::CCLayer { public: static constexpr float LIST_WIDTH = 358.f; static constexpr float LIST_HEIGHT = 220.f; - - bool init(); - static GlobedSignupLayer* create() { - auto ret = new GlobedSignupLayer; - if (ret && ret->init()) { - ret->autorelease(); - return ret; - } + static GlobedSignupLayer* create(); - CC_SAFE_DELETE(ret); - return nullptr; - } private: + bool init(); + static constexpr const char* CONSENT_MESSAGE = "For verification purposes, this action will cause your account to leave a comment on a certain level. " "There is nothing else that has to be done from your side, and once the verification is complete, " diff --git a/src/ui/menu/main/signup_popup.cpp b/src/ui/menu/main/signup_popup.cpp index 34531807..9450279d 100644 --- a/src/ui/menu/main/signup_popup.cpp +++ b/src/ui/menu/main/signup_popup.cpp @@ -95,7 +95,6 @@ void GlobedSignupPopup::commentUploadFinished(int _) { nullptr ) ); - } void GlobedSignupPopup::onDelayedChallengeCompleted() { @@ -117,7 +116,7 @@ void GlobedSignupPopup::onChallengeCompleted(const std::string& authcode) { auto gdData = am.gdData.lock(); - auto url = sm.getCentral() + + auto url = sm.getCentral() + fmt::format("/challenge/verify?aid={}&aname={}&answer={}", gdData->accountId, gdData->accountName, @@ -131,7 +130,7 @@ void GlobedSignupPopup::onChallengeCompleted(const std::string& authcode) { // we are good! the authkey has been created and can be saved now. auto colonPos = response.find(':'); auto commentId = response.substr(0, colonPos); - + log::info("Authkey created successfully, saving."); auto authkey = util::crypto::base64Decode(response.substr(colonPos + 1)); am.storeAuthKey(util::crypto::simpleHash(authkey)); @@ -169,4 +168,14 @@ void GlobedSignupPopup::onFailure(const std::string& message) { void GlobedSignupPopup::keyDown(cocos2d::enumKeyCodes key) { // do nothing; the popup should be impossible to close manually +} + +GlobedSignupPopup* GlobedSignupPopup::create() { + auto ret = new GlobedSignupPopup; + if (ret && ret->init(POPUP_WIDTH, POPUP_HEIGHT)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; } \ No newline at end of file diff --git a/src/ui/menu/main/signup_popup.hpp b/src/ui/menu/main/signup_popup.hpp index 57acc388..049b7361 100644 --- a/src/ui/menu/main/signup_popup.hpp +++ b/src/ui/menu/main/signup_popup.hpp @@ -6,6 +6,14 @@ class GlobedSignupPopup : public geode::Popup<>, public CommentUploadDelegate { constexpr static float POPUP_WIDTH = 180.f; constexpr static float POPUP_HEIGHT = 80.f; + static GlobedSignupPopup* create(); + +protected: + cocos2d::CCLabelBMFont* statusMessage; + + std::string storedAuthcode; + int storedLevelId; + bool setup() override; void keyDown(cocos2d::enumKeyCodes key) override; void onFailure(const std::string& message); @@ -19,20 +27,4 @@ class GlobedSignupPopup : public geode::Popup<>, public CommentUploadDelegate { void commentDeleteFailed(int, int) override; void onDelayedChallengeCompleted(); - - static GlobedSignupPopup* create() { - auto ret = new GlobedSignupPopup; - if (ret && ret->init(POPUP_WIDTH, POPUP_HEIGHT)) { - ret->autorelease(); - return ret; - } - CC_SAFE_DELETE(ret); - return nullptr; - } - -private: - cocos2d::CCLabelBMFont* statusMessage; - - std::string storedAuthcode; - int storedLevelId; }; \ No newline at end of file diff --git a/src/ui/menu/player_list/player_list_cell.cpp b/src/ui/menu/player_list/player_list_cell.cpp new file mode 100644 index 00000000..0530b775 --- /dev/null +++ b/src/ui/menu/player_list/player_list_cell.cpp @@ -0,0 +1,67 @@ +#include "player_list_cell.hpp" +#include + +#include "player_list_popup.hpp" + +using namespace geode::prelude; + +bool PlayerListCell::init(const PlayerPreviewAccountData& data) { + if (!CCLayer::init()) return false; + this->data = data; + + Build::create() + .pos({PlayerListPopup::LIST_WIDTH, CELL_HEIGHT}) + .parent(this) + .store(menu); + + auto* label = Build::create(data.name.c_str(), "bigFont.fnt") + .limitLabelWidth(170.f, 0.8f, 0.1f) + .collect(); + + label->setScale(label->getScale() * 0.9f); + + auto* btn = Build::create(label, this, menu_selector(PlayerListCell::onOpenProfile)) + .pos({-PlayerListPopup::LIST_WIDTH + 57.f, -22.f}) + .parent(menu) + .collect(); + + // this achieves the same thing as setting anchor point X to 0.0f, but with the correct bounce + btn->setPositionX(btn->getPositionX() + btn->getScaledContentSize().width / 2); + + auto* gm = GameManager::get(); + + Build::create(data.cube) + .playerFrame(data.cube, IconType::Cube) + .color(gm->colorForIdx(data.color1)) + .secondColor(gm->colorForIdx(data.color2)) + .parent(this) + .pos({25.f, CELL_HEIGHT - 22.f}) + .store(simplePlayer); + + // dont create the button if level id = 0 + auto* cbs = CircleButtonSprite::createWithSpriteFrameName("d_skull01_001.png", 1.f, CircleBaseColor::Green, CircleBaseSize::Medium); + Build(cbs) + .scale(0.75f) + .intoMenuItem([this](auto) { + // TODO OPen the level + log::debug("here you open the level ID {}", this->data.levelId); + }) + .pos({-30.f, -23.f}) + .parent(menu); + + return true; +} + +void PlayerListCell::onOpenProfile(cocos2d::CCObject*) { + ProfilePage::create(data.id, false)->show(); +} + +PlayerListCell* PlayerListCell::create(const PlayerPreviewAccountData& data) { + auto ret = new PlayerListCell; + if (ret && ret->init(data)) { + return ret; + } + + CC_SAFE_DELETE(ret); + return nullptr; +} \ No newline at end of file diff --git a/src/ui/menu/player_list/player_list_cell.hpp b/src/ui/menu/player_list/player_list_cell.hpp new file mode 100644 index 00000000..3da0a004 --- /dev/null +++ b/src/ui/menu/player_list/player_list_cell.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +class PlayerListCell : public cocos2d::CCLayer { +public: + static constexpr float CELL_HEIGHT = 45.0f; + + static PlayerListCell* create(const PlayerPreviewAccountData& data); + +protected: + bool init(const PlayerPreviewAccountData& data); + void onOpenProfile(cocos2d::CCObject*); + + PlayerPreviewAccountData data; + + cocos2d::CCMenu* menu; + CCMenuItemSpriteExtra* btnOpenLevel; + SimplePlayer* simplePlayer; +}; \ No newline at end of file diff --git a/src/ui/menu/player_list/player_list_popup.cpp b/src/ui/menu/player_list/player_list_popup.cpp new file mode 100644 index 00000000..9be28164 --- /dev/null +++ b/src/ui/menu/player_list/player_list_popup.cpp @@ -0,0 +1,95 @@ +#include "player_list_popup.hpp" +#include + +#include "player_list_cell.hpp" +#include +#include +#include + +using namespace geode::prelude; + +bool PlayerListPopup::setup() { + auto& nm = NetworkManager::get(); + if (!nm.established()) { + return false; + } + + nm.addListener([this](PlayerListPacket* packet) { + this->playerList = packet->data; + this->onLoaded(); + }); + + nm.send(RequestPlayerListPacket::create()); + + this->setTitle("Online players"); + + // show an error popup if no response after 2 seconds + timeoutSequence = CCSequence::create( + CCDelayTime::create(2.0f), + CCCallFunc::create(this, callfunc_selector(PlayerListPopup::onLoadTimeout)), + nullptr + ); + + this->runAction(timeoutSequence); + + auto listview = ListView::create(CCArray::create(), PlayerListCell::CELL_HEIGHT, LIST_WIDTH, LIST_HEIGHT); + listLayer = GJCommentListLayer::create(listview, "", {192, 114, 62, 255}, LIST_WIDTH, LIST_HEIGHT, false); + + float xpos = (m_mainLayer->getScaledContentSize().width - LIST_WIDTH) / 2; + listLayer->setPosition({xpos, 50.f}); + m_mainLayer->addChild(listLayer); + + loadingCircle = LoadingCircle::create(); + loadingCircle->setParentLayer(listLayer); + loadingCircle->setPosition(-listLayer->getPosition()); + loadingCircle->show(); + + return true; +} + +void PlayerListPopup::onLoaded() { + if (timeoutSequence) { + this->stopAction(timeoutSequence); + timeoutSequence = nullptr; + } + + this->removeLoadingCircle(); + + auto cells = CCArray::create(); + + for (const auto& pdata : playerList) { + auto* cell = PlayerListCell::create(pdata); + cells->addObject(cell); + } + + listLayer->m_list->removeFromParent(); + listLayer->m_list = Build::create(cells, PlayerListCell::CELL_HEIGHT, LIST_WIDTH, LIST_HEIGHT) + .parent(listLayer) + .collect(); +} + +void PlayerListPopup::onLoadTimeout() { + ErrorQueues::get().error("Failed to fetch the player list! This is most likely a server-side bug or a problem with your connection!"); + this->removeLoadingCircle(); +} + +void PlayerListPopup::removeLoadingCircle() { + if (loadingCircle) { + loadingCircle->fadeAndRemove(); + loadingCircle = nullptr; + } +} + +PlayerListPopup::~PlayerListPopup() { + NetworkManager::get().removeListener(); +} + +PlayerListPopup* PlayerListPopup::create() { + auto ret = new PlayerListPopup; + if (ret && ret->init(POPUP_WIDTH, POPUP_HEIGHT)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} \ No newline at end of file diff --git a/src/ui/menu/player_list/player_list_popup.hpp b/src/ui/menu/player_list/player_list_popup.hpp new file mode 100644 index 00000000..8c0dd321 --- /dev/null +++ b/src/ui/menu/player_list/player_list_popup.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +class PlayerListPopup : public geode::Popup<> { +public: + constexpr static float POPUP_WIDTH = 420.f; + constexpr static float POPUP_HEIGHT = 280.f; + constexpr static float LIST_WIDTH = 338.f; + constexpr static float LIST_HEIGHT = 200.f; + + ~PlayerListPopup(); + + static PlayerListPopup* create(); + +protected: + std::vector playerList; + cocos2d::CCSequence* timeoutSequence = nullptr; + LoadingCircle* loadingCircle = nullptr; + GJCommentListLayer* listLayer = nullptr; + + bool setup() override; + void onLoaded(); + void onLoadTimeout(); + void removeLoadingCircle(); +}; \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index 74f0e97d..00000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,50 +0,0 @@ -cmake_minimum_required(VERSION 3.27) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set up your test target -project(globed_tests VERSION 1.0.0) - -list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}) - -add_definitions(-include ../testdef.hpp) -add_executable(globed_tests tests.cpp - ../src/crypto/box.cpp - ../src/crypto/base_box.cpp - ../src/crypto/secret_box.cpp - ../src/data/bytebuffer.cpp - ../src/util/data.cpp - ../src/util/rng.cpp - ../src/util/time.cpp - ../src/util/debugging.cpp - ../src/util/formatting.cpp - ../src/util/crypto.cpp -) - -enable_testing() - -# Include winsock -if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - if(MSVC) - add_definitions(/FI"winsock2.h") - else() - # GCC or Clang - add_definitions(-include winsock2.h) - endif() -endif() - -# Include libsodium -find_package(Sodium REQUIRED) -if(Sodium_FOUND) - include_directories(${sodium_INCLUDE_DIRS}) - target_link_libraries(globed_tests ${sodium_LIBRARIES}) - message(STATUS "Found sodium: ${Sodium_LIBRARIES}") -else() - message(FATAL_ERROR "libsodium not found. Make sure it's installed.") -endif() - -# done so you can include root files with -target_include_directories(globed_tests PRIVATE ../src/) - -# Register the test with CTest -add_test(NAME GlobedTests COMMAND globed_tests) diff --git a/tests/FindSodium.cmake b/tests/FindSodium.cmake deleted file mode 100644 index c664ccbe..00000000 --- a/tests/FindSodium.cmake +++ /dev/null @@ -1,294 +0,0 @@ -# Written in 2016 by Henrik Steffen Gaßmann -# -# To the extent possible under law, the author(s) have dedicated all -# copyright and related and neighboring rights to this software to the -# public domain worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication -# along with this software. If not, see -# -# http://creativecommons.org/publicdomain/zero/1.0/ -# -######################################################################## -# Tries to find the local libsodium installation. -# -# On Windows the sodium_DIR environment variable is used as a default -# hint which can be overridden by setting the corresponding cmake variable. -# -# Once done the following variables will be defined: -# -# sodium_FOUND -# sodium_INCLUDE_DIR -# sodium_LIBRARY_DEBUG -# sodium_LIBRARY_RELEASE -# -# -# Furthermore an imported "sodium" target is created. -# - -if (CMAKE_C_COMPILER_ID STREQUAL "GNU" - OR CMAKE_C_COMPILER_ID STREQUAL "Clang") - set(_GCC_COMPATIBLE 1) -endif() - -# static library option -if (NOT DEFINED sodium_USE_STATIC_LIBS) - option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) -endif() -if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) - unset(sodium_LIBRARY CACHE) - unset(sodium_LIBRARY_DEBUG CACHE) - unset(sodium_LIBRARY_RELEASE CACHE) - unset(sodium_DLL_DEBUG CACHE) - unset(sodium_DLL_RELEASE CACHE) - set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") -endif() - - -######################################################################## -# UNIX -if (UNIX) - # import pkg-config - find_package(PkgConfig QUIET) - if (PKG_CONFIG_FOUND) - pkg_check_modules(sodium_PKG QUIET libsodium) - endif() - - if(sodium_USE_STATIC_LIBS) - foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) - if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a - list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") - endif() - endforeach() - list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) - - # if pkgconfig for libsodium doesn't provide - # static lib info, then override PKG_STATIC here.. - if (NOT sodium_PKG_STATIC_FOUND) - set(sodium_PKG_STATIC_LIBRARIES libsodium.a) - endif() - - set(XPREFIX sodium_PKG_STATIC) - else() - if (NOT sodium_PKG_FOUND) - set(sodium_PKG_LIBRARIES sodium) - endif() - - set(XPREFIX sodium_PKG) - endif() - - find_path(sodium_INCLUDE_DIR sodium.h - HINTS ${${XPREFIX}_INCLUDE_DIRS} - ) - find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} - HINTS ${${XPREFIX}_LIBRARY_DIRS} - ) - find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} - HINTS ${${XPREFIX}_LIBRARY_DIRS} - ) - - -######################################################################## -# Windows -elseif (WIN32) - set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") - mark_as_advanced(sodium_DIR) - - find_path(sodium_INCLUDE_DIR sodium.h - HINTS ${sodium_DIR} - PATH_SUFFIXES include - ) - - if (MSVC) - # detect target architecture - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ - #if defined _M_IX86 - #error ARCH_VALUE x86_32 - #elif defined _M_X64 - #error ARCH_VALUE x86_64 - #endif - #error ARCH_VALUE unknown - ]=]) - try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" - OUTPUT_VARIABLE _COMPILATION_LOG - ) - string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") - - # construct library path - if (_TARGET_ARCH STREQUAL "x86_32") - string(APPEND _PLATFORM_PATH "Win32") - elseif(_TARGET_ARCH STREQUAL "x86_64") - string(APPEND _PLATFORM_PATH "x64") - else() - message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") - endif() - string(APPEND _PLATFORM_PATH "/$$CONFIG$$") - - if (MSVC_VERSION LESS 1900) - math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") - else() - math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") - endif() - string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") - - if (sodium_USE_STATIC_LIBS) - string(APPEND _PLATFORM_PATH "/static") - else() - string(APPEND _PLATFORM_PATH "/dynamic") - endif() - - string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") - string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") - - find_library(sodium_LIBRARY_DEBUG libsodium.lib - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} - ) - find_library(sodium_LIBRARY_RELEASE libsodium.lib - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} - ) - if (NOT sodium_USE_STATIC_LIBS) - set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") - find_library(sodium_DLL_DEBUG libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} - ) - find_library(sodium_DLL_RELEASE libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} - ) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) - endif() - - elseif(_GCC_COMPATIBLE) - if (sodium_USE_STATIC_LIBS) - find_library(sodium_LIBRARY_DEBUG libsodium.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib - ) - find_library(sodium_LIBRARY_RELEASE libsodium.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib - ) - else() - find_library(sodium_LIBRARY_DEBUG libsodium.dll.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib - ) - find_library(sodium_LIBRARY_RELEASE libsodium.dll.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib - ) - - file(GLOB _DLL - LIST_DIRECTORIES false - RELATIVE "${sodium_DIR}/bin" - "${sodium_DIR}/bin/libsodium*.dll" - ) - find_library(sodium_DLL_DEBUG ${_DLL} libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES bin - ) - find_library(sodium_DLL_RELEASE ${_DLL} libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES bin - ) - endif() - else() - message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") - endif() - - -######################################################################## -# unsupported -else() - message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") -endif() - - -######################################################################## -# common stuff - -# extract sodium version -if (sodium_INCLUDE_DIR) - set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") - if (EXISTS _VERSION_HEADER) - file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) - string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" - sodium_VERSION "${_VERSION_HEADER_CONTENT}") - set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) - endif() -endif() - -# communicate results -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - Sodium # The name must be either uppercase or match the filename case. - REQUIRED_VARS - sodium_LIBRARY_RELEASE - sodium_LIBRARY_DEBUG - sodium_INCLUDE_DIR - VERSION_VAR - sodium_VERSION -) - -if(Sodium_FOUND) - set(sodium_LIBRARIES - optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG}) -endif() - -# mark file paths as advanced -mark_as_advanced(sodium_INCLUDE_DIR) -mark_as_advanced(sodium_LIBRARY_DEBUG) -mark_as_advanced(sodium_LIBRARY_RELEASE) -if (WIN32) - mark_as_advanced(sodium_DLL_DEBUG) - mark_as_advanced(sodium_DLL_RELEASE) -endif() - -# create imported target -if(sodium_USE_STATIC_LIBS) - set(_LIB_TYPE STATIC) -else() - set(_LIB_TYPE SHARED) -endif() -add_library(sodium ${_LIB_TYPE} IMPORTED) - -set_target_properties(sodium PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}" - IMPORTED_LINK_INTERFACE_LANGUAGES "C" -) - -if (sodium_USE_STATIC_LIBS) - set_target_properties(sodium PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" - IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" - IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" - ) -else() - if (UNIX) - set_target_properties(sodium PROPERTIES - IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" - IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" - ) - elseif (WIN32) - set_target_properties(sodium PROPERTIES - IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}" - IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}" - ) - if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) - set_target_properties(sodium PROPERTIES - IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" - ) - endif() - if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) - set_target_properties(sodium PROPERTIES - IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" - IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" - IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" - ) - endif() - endif() -endif() diff --git a/tests/testdef.hpp b/tests/testdef.hpp deleted file mode 100644 index ffdbda03..00000000 --- a/tests/testdef.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include - -#define GLOBED_ASSERT_LOG(content) (std::cerr << content << std::endl); -#define GLOBED_ROOT_NO_GEODE -#define GLOBED_TESTING -#define GLOBED_IGNORE_CONFIG_HPP -#define GLOBED_FORCE_CONSTEVAL 0 -#include \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp deleted file mode 100644 index a225e111..00000000 --- a/tests/tests.cpp +++ /dev/null @@ -1,356 +0,0 @@ -#undef NDEBUG -/* horrible ahh code */ - -#include -#include -#include -#include -#include -#include - -// #define GLOBED_USE_XSALSA20 - -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace util::rng; -using namespace util::data; -using namespace util::debugging; -using namespace util; - -#if 1 -#define CRYPTO_ITERS 4096 -#define RNG_ITERS 65536 -#else // valgrind is sloooooooooooooow -#define CRYPTO_ITERS 128 -#define RNG_ITERS 4096 -#endif - -#define ASSERT(cond, message) if (!(cond)) { throw std::runtime_error(std::string(message)); } - -void tCrypto() { - CryptoBox alice, bob; - alice.setPeerKey(bob.getPublicKey()); - bob.setPeerKey(alice.getPublicKey()); - - // test with random data - for (int i = 0; i < CRYPTO_ITERS; i++) { - Random& rng = Random::get(); - ByteBuffer buf; - - uint32_t length = rng.generate(0, 16384); - uint32_t pushed = 0; - - while (pushed < length) { - uint32_t remaining = length - pushed; - if (remaining >= 4) { - buf.write(rng.generate()); - pushed += 4; - } else if (remaining >= 2) { - buf.write(rng.generate()); - pushed += 2; - } else if (remaining == 1) { - uint8_t num = rng.generate() % 255; - buf.write(num); - pushed += 1; - } - } - - auto contents = buf.getDataRef(); - bytevector decrypted; - - if (rng.generate()) { - auto encrypted = alice.encrypt(contents); - decrypted = bob.decrypt(encrypted); - } else { - auto encrypted = bob.encrypt(contents); - decrypted = alice.decrypt(encrypted); - } - - for (size_t i = 0; i < decrypted.size(); i++) { - ASSERT(contents[i] == decrypted[i], "decrypted data is invalid"); - } - - // now try in place - const int testl = 64; - byte* buf2 = new byte[testl + CryptoBox::PREFIX_LEN]; - randombytes_buf(buf2, testl); - - byte* original = new byte[testl]; - std::memcpy(original, buf2, testl); - - size_t encrypted = alice.encryptInPlace(buf2, testl); - size_t decrypted_ = bob.decryptInPlace(buf2, encrypted); - - for (size_t i = 0; i < testl; i++) { - if (buf2[i] != original[i]) { - delete[] buf2; - delete[] original; - ASSERT(false, "decrypted data is invalid"); - } - } - - delete[] buf2; - delete[] original; - } -} - -void tSecretBox() { - std::string pw = "Extremely based password"; - - SecretBox alice = SecretBox::withPassword(pw); - SecretBox bob = SecretBox::withPassword(pw); - - // test with random data - for (int i = 0; i < CRYPTO_ITERS; i++) { - Random& rng = Random::get(); - ByteBuffer buf; - - uint32_t length = rng.generate(0, 16384); - uint32_t pushed = 0; - - while (pushed < length) { - uint32_t remaining = length - pushed; - if (remaining >= 4) { - buf.write(rng.generate()); - pushed += 4; - } else if (remaining >= 2) { - buf.write(rng.generate()); - pushed += 2; - } else if (remaining == 1) { - uint8_t num = rng.generate() % 255; - buf.write(num); - pushed += 1; - } - } - - auto contents = buf.getDataRef(); - bytevector decrypted; - - if (rng.generate()) { - auto encrypted = alice.encrypt(contents); - decrypted = bob.decrypt(encrypted); - } else { - auto encrypted = bob.encrypt(contents); - decrypted = alice.decrypt(encrypted); - } - - for (size_t i = 0; i < decrypted.size(); i++) { - ASSERT(contents[i] == decrypted[i], "decrypted data is invalid"); - } - - // now try in place - const int testl = 64; - byte* buf2 = new byte[testl + CryptoBox::PREFIX_LEN]; - randombytes_buf(buf2, testl); - - byte* original = new byte[testl]; - std::memcpy(original, buf2, testl); - - size_t encrypted = alice.encryptInPlace(buf2, testl); - size_t decrypted_ = bob.decryptInPlace(buf2, encrypted); - - for (size_t i = 0; i < testl; i++) { - if (buf2[i] != original[i]) { - delete[] buf2; - delete[] original; - ASSERT(false, "decrypted data is invalid"); - } - } - - delete[] buf2; - delete[] original; - } -} - -void tRandom() { - std::vector bools; - Random& rng = Random::get(); - for (int i = 0; i < RNG_ITERS; i++) { - bools.push_back(rng.generate()); - } - - // test entropy - int trues = 0; - int falses = 0; - for (int i = 0; i < bools.size(); i++) { - if (bools[i]) trues++; - else falses++; - } - - auto diff = std::abs(trues - falses); - - ASSERT(diff < RNG_ITERS / 16, "poor random entropy") -} - -void tTotp() { - auto key = util::crypto::base64Decode("hb35940QWUxD7Ss5kw+goP5QLxRn1Sc7/yIIiQ4bdCg="); - auto hashed = util::crypto::simpleHash(key); - // std::cout << "raw key: '" << key << "', encoded: " << util::debugging::hexDumpAddress(hashed.data(), hashed.size()) << std::endl; - auto totp = util::crypto::simpleTOTP(hashed); - - std::cout << "totp: " << totp << std::endl; - ASSERT(util::crypto::simpleTOTPVerify(totp, hashed), "simpleTOTPVerify failed"); -} - -void tEncodings() { - auto src = std::string("holy balls!"); - auto b64 = util::crypto::base64Encode(src); - auto hex = util::crypto::hexEncode(src); - - std::cout << "base64: " << b64 << ", hex: " << hex << std::endl; - - auto decoded = util::crypto::base64Decode(b64); - auto decodedHex = util::crypto::hexDecode(hex); - - std::string decodedS(decoded.begin(), decoded.end()); - std::string decodedH(decodedHex.begin(), decodedHex.end()); - - std::cout << "b64 decoded: '" << decodedS << "', size: " << decodedS.size() << std::endl; - std::cout << "hex decoded: '" << decodedH << "', size: " << decodedH.size() << std::endl; - - ASSERT(decodedS == src, "base64 encode/decode failed"); - ASSERT(decodedH == src, "hex encode/decode failed"); -} - -using ms = std::chrono::microseconds; - -std::map> tests = { - {"Random"s, tRandom}, - {"CryptoBox"s, tCrypto}, - {"SecretBox"s, tSecretBox}, - {"TOTP", tTotp}, - {"encodings", tEncodings}, -}; - -ms bnCrypto(size_t dataLength, size_t iterations) { - CryptoBox alice, bob; - alice.setPeerKey(bob.getPublicKey()); - bob.setPeerKey(alice.getPublicKey()); - - byte* buf = new byte[dataLength]; - randombytes_buf(buf, dataLength); - - bytevector encrypted(buf, buf + dataLength); - - Benchmarker bench; - bench.start("all"); - for (size_t i = 0; i < iterations; i++) { - encrypted = alice.encrypt(encrypted); - } - - bytevector decrypted(encrypted); - for (size_t i = 0; i < iterations; i++) { - decrypted = bob.decrypt(decrypted); - } - auto tooktime = bench.end("all"); - - for (size_t i = 0; i < decrypted.size(); i++) { - if (buf[i] != decrypted[i]) { - delete[] buf; - ASSERT(false, "decrypted data is invalid"); - } - } - - delete[] buf; - - auto totalmicros = (long long)((float)(tooktime.count()) / (float)(iterations)); - return ms(totalmicros); -} - -ms bnCryptoInplace(size_t dataLength, size_t iterations) { - CryptoBox alice, bob; - alice.setPeerKey(bob.getPublicKey()); - bob.setPeerKey(alice.getPublicKey()); - - byte* buf = new byte[dataLength + CryptoBox::PREFIX_LEN]; - randombytes_buf(buf, dataLength); - - byte* original = new byte[dataLength]; - std::memcpy(original, buf, dataLength); - - Benchmarker bench; - bench.start("all"); - - CryptoBox *box1, *box2; - for (size_t i = 0; i < iterations; i++) { - if (Random::get().generate()) { - box1 = &bob; - box2 = &alice; - } else { - box1 = &alice; - box2 = &bob; - } - - size_t encSize = box1->encryptInPlace(buf, dataLength); - dataLength = box2->decryptInPlace(buf, encSize); - } - - auto tooktime = bench.end("all"); - - for (size_t i = 0; i < dataLength; i++) { - if (buf[i] != original[i]) { - delete[] buf; - delete[] original; - ASSERT(false, "decrypted data is invalid"); - } - } - - delete[] original; - delete[] buf; - - auto totalmicros = (long long)((float)(tooktime.count()) / (float)(iterations)); - return ms(totalmicros); -} - -int main(int argc, const char* *argv) { - int passed = 0; - int failed = 0; - - std::string mode = "test"; - if (argc >= 2) { - mode = std::string(argv[1]); - } - - std::cout << "crypto algo: " << CryptoBox::ALGORITHM << std::endl; - - if (mode == "test") { - Benchmarker bench; - for (auto& [name, test] : tests) { - bench.start(name); - try { - test(); - auto time = formatting::formatTime(bench.end(name)); - std::cout << "\033[32mTest passed: " << name << " (took " << time << ")\033[0m" << std::endl; - } catch (const std::runtime_error& e) { - auto time = formatting::formatTime(bench.end(name)); - std::cerr << "\033[31mTest failed: " << name << " - " << e.what() << " (took " << time << ")\033[0m" << std::endl; - } - } - } else if (mode == "benchmark") { - // note - the tests aren't made to compare against each other - // but only among themselves. the in-place crypto is faster - // because the 1st variation does X runs of encryption and then X runs of decryption - // achieving bigger data lengths, when 2nd one runs both every iteration - Benchmarker bench; - int runs = 1024; - size_t lengthRuns[] = {64, 1024, 8192, 65536}; - std::cout << "Running crypto benchmarks, 1024 iterations per test" << std::endl; - - for (size_t len : lengthRuns) { - std::cout << len << " bytes, per iter: " << formatting::formatTime(bnCrypto(len, runs)) << std::endl; - } - - std::cout << "Running in-place crypto benchmarks, 1024 iterations per test" << std::endl; - - for (size_t len : lengthRuns) { - std::cout << len << " bytes, per iter: " << formatting::formatTime(bnCryptoInplace(len, runs)) << std::endl; - } - - } else { - std::cerr << "modes: test, benchmark" << std::endl; - } -} \ No newline at end of file