From e7fc40632d78ebc392fd6a8bfa8a1e76def239d5 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Mon, 9 Sep 2024 21:43:01 -0400 Subject: [PATCH 1/5] extract authentication client v2 --- pumpkin/src/client/authentication.rs | 8 +-- pumpkin/src/client/client_packet.rs | 33 +++-------- pumpkin/src/server/bikeshed_key_store.rs | 74 ++++++++++++++++++++++++ pumpkin/src/server/mod.rs | 52 ++++++++--------- 4 files changed, 108 insertions(+), 59 deletions(-) create mode 100644 pumpkin/src/server/bikeshed_key_store.rs diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 9af1020ce..0d3a537c4 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, net::IpAddr, sync::Arc}; use base64::{engine::general_purpose, Engine}; -use num_bigint::BigInt; use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG}; use pumpkin_core::ProfileAction; use pumpkin_protocol::Property; @@ -93,15 +92,10 @@ pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), for texture in textures.textures { let url = Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; - is_texture_url_valid(url, config)? - } + is_texture_url_valid(url, config)? } Ok(()) } -pub fn auth_digest(bytes: &[u8]) -> String { - BigInt::from_signed_bytes_be(bytes).to_str_radix(16) -} - pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), TextureError> { let scheme = url.scheme(); if !config.allowed_url_schemes.contains(&scheme.to_string()) { diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index c8aa6a8bd..817adb245 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -6,7 +6,7 @@ use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ client::{ config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, - login::{CEncryptionRequest, CLoginSuccess, CSetCompression}, + login::{CLoginSuccess, CSetCompression}, status::{CPingResponse, CStatusResponse}, }, server::{ @@ -17,8 +17,6 @@ use pumpkin_protocol::{ }, ConnectionState, KnownPack, CURRENT_MC_PROTOCOL, }; -use rsa::Pkcs1v15Encrypt; -use sha1::{Digest, Sha1}; use crate::{ client::authentication::{self, GameProfile}, @@ -27,10 +25,7 @@ use crate::{ server::{Server, CURRENT_MC_VERSION}, }; -use super::{ - authentication::{auth_digest, unpack_textures}, - Client, EncryptionError, PlayerConfig, -}; +use super::{authentication::unpack_textures, Client, PlayerConfig}; /// Processes incoming Packets from the Client to the Server /// Implements the `Client` Packets @@ -101,14 +96,7 @@ impl Client { // TODO: check config for encryption let verify_token: [u8; 4] = rand::random(); - let public_key_der = &server.public_key_der; - let packet = CEncryptionRequest::new( - "", - public_key_der, - &verify_token, - BASIC_CONFIG.online_mode, // TODO - ); - self.send_packet(&packet); + self.send_packet(&server.encryption_request(&verify_token, BASIC_CONFIG.online_mode)); } pub async fn handle_encryption_response( @@ -116,22 +104,15 @@ impl Client { server: &Arc, encryption_response: SEncryptionResponse, ) { - let shared_secret = server - .private_key - .decrypt(Pkcs1v15Encrypt, &encryption_response.shared_secret) - .map_err(|_| EncryptionError::FailedDecrypt) - .unwrap(); + let shared_secret = server.decrypt(&encryption_response.shared_secret).unwrap(); + self.enable_encryption(&shared_secret) .unwrap_or_else(|e| self.kick(&e.to_string())); let mut gameprofile = self.gameprofile.lock(); if BASIC_CONFIG.online_mode { - let hash = Sha1::new() - .chain_update(&shared_secret) - .chain_update(&server.public_key_der) - .finalize(); - let hash = auth_digest(&hash); + let hash = server.digest_secret(&shared_secret); let ip = self.address.lock().ip(); match authentication::authenticate( &gameprofile.as_ref().unwrap().name, @@ -231,7 +212,7 @@ impl Client { id: "core", version: "1.21", }])); - dbg!("login achnowlaged"); + dbg!("login acknowledged"); } pub fn handle_client_information_config( &self, diff --git a/pumpkin/src/server/bikeshed_key_store.rs b/pumpkin/src/server/bikeshed_key_store.rs new file mode 100644 index 000000000..e3b447af6 --- /dev/null +++ b/pumpkin/src/server/bikeshed_key_store.rs @@ -0,0 +1,74 @@ +use num_bigint::BigInt; +use pumpkin_protocol::client::login::CEncryptionRequest; +use rsa::{traits::PublicKeyParts as _, Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; +use sha1::Sha1; +use sha2::Digest; + +use crate::client::EncryptionError; + +pub struct BikeShedKeyStore { + pub _public_key: RsaPublicKey, + pub private_key: RsaPrivateKey, + pub public_key_der: Box<[u8]>, +} + +impl BikeShedKeyStore { + pub fn new() -> Self { + log::debug!("Creating encryption keys..."); + let (public_key, private_key) = Self::generate_keys(); + + let public_key_der = rsa_der::public_key_to_der( + &private_key.n().to_bytes_be(), + &private_key.e().to_bytes_be(), + ) + .into_boxed_slice(); + BikeShedKeyStore { + _public_key: public_key, + private_key, + public_key_der, + } + } + + fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) { + let mut rng = rand::thread_rng(); + + let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate a key"); + let pub_key = RsaPublicKey::from(&priv_key); + (pub_key, priv_key) + } + + pub fn encryption_request<'a>( + &'a self, + server_id: &'a str, + verification_token: &'a [u8; 4], + should_authenticate: bool, + ) -> CEncryptionRequest<'_> { + CEncryptionRequest::new( + server_id, + &self.public_key_der, + verification_token, + should_authenticate, + ) + } + + pub fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { + let decrypted = self + .private_key + .decrypt(Pkcs1v15Encrypt, data) + .map_err(|_| EncryptionError::FailedDecrypt)?; + Ok(decrypted) + } + + pub fn get_digest(&self, secret: &[u8]) -> String { + auth_digest( + &Sha1::new() + .chain_update(secret) + .chain_update(&self.public_key_der) + .finalize(), + ) + } +} + +pub fn auth_digest(bytes: &[u8]) -> String { + BigInt::from_signed_bytes_be(bytes).to_str_radix(16) +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 86c18851b..9569e14c4 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,4 +1,5 @@ use base64::{engine::general_purpose, Engine}; +use bikeshed_key_store::BikeShedKeyStore; use image::GenericImageView; use mio::Token; use parking_lot::{Mutex, RwLock}; @@ -6,6 +7,7 @@ use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_plugin::PluginLoader; +use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{ client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, @@ -25,8 +27,8 @@ use std::{ use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_registry::Registry; -use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; +use crate::client::EncryptionError; use crate::{ client::Client, commands::{default_dispatcher, dispatcher::CommandDispatcher}, @@ -34,13 +36,11 @@ use crate::{ world::World, }; +mod bikeshed_key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { - pub public_key: RsaPublicKey, - pub private_key: RsaPrivateKey, - pub public_key_der: Box<[u8]>, - + key_store: BikeShedKeyStore, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -74,14 +74,7 @@ impl Server { let cached_server_brand = Self::build_brand(); // TODO: only create when needed - log::debug!("Creating encryption keys..."); - let (public_key, private_key) = Self::generate_keys(); - - let public_key_der = rsa_der::public_key_to_der( - &private_key.n().to_bytes_be(), - &private_key.e().to_bytes_be(), - ) - .into_boxed_slice(); + let key_store = BikeShedKeyStore::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -110,14 +103,12 @@ impl Server { // 0 is invalid entity_id: 2.into(), worlds: vec![Arc::new(world)], - public_key, - cached_server_brand, - private_key, command_dispatcher: Arc::new(command_dispatcher), + auth_client, + key_store, status_response, status_response_json, - public_key_der, - auth_client, + cached_server_brand, } } @@ -174,6 +165,23 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + pub fn encryption_request<'a>( + &'a self, + verification_token: &'a [u8; 4], + should_authenticate: bool, + ) -> CEncryptionRequest<'_> { + self.key_store + .encryption_request("", verification_token, should_authenticate) + } + + pub fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { + self.key_store.decrypt(data) + } + + pub fn digest_secret(&self, secret: &[u8]) -> String { + self.key_store.get_digest(secret) + } + pub fn build_brand() -> Vec { let brand = "Pumpkin"; let mut buf = vec![]; @@ -233,12 +241,4 @@ impl Server { general_purpose::STANDARD.encode_string(image, &mut result); result } - - pub fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) { - let mut rng = rand::thread_rng(); - - let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate a key"); - let pub_key = RsaPublicKey::from(&priv_key); - (pub_key, priv_key) - } } From c4dc376bc94ac4e9824350e462baab0c26997080 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Mon, 9 Sep 2024 22:00:54 -0400 Subject: [PATCH 2/5] extract server status publishing into its own file --- .../src/client/config/c_add_resource_pack.rs | 4 +- pumpkin-protocol/src/uuid.rs | 6 + pumpkin/src/client/authentication.rs | 3 +- pumpkin/src/client/client_packet.rs | 28 ++--- pumpkin/src/server/bikeshed_server_listing.rs | 94 ++++++++++++++++ pumpkin/src/server/mod.rs | 103 +++--------------- 6 files changed, 136 insertions(+), 102 deletions(-) create mode 100644 pumpkin/src/server/bikeshed_server_listing.rs diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs index 824141e25..926fb6e39 100644 --- a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -16,14 +16,14 @@ pub struct CConfigAddResourcePack<'a> { impl<'a> CConfigAddResourcePack<'a> { pub fn new( - uuid: UUID, + uuid: uuid::Uuid, url: &'a str, hash: &'a str, forced: bool, prompt_message: Option>, ) -> Self { Self { - uuid, + uuid: UUID(uuid), url, hash, forced, diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs index a187a083a..39988ae40 100644 --- a/pumpkin-protocol/src/uuid.rs +++ b/pumpkin-protocol/src/uuid.rs @@ -13,3 +13,9 @@ impl Serialize for UUID { serializer.serialize_bytes(self.0.as_bytes()) } } + +impl UUID { + pub fn new(data: &[u8]) -> Self { + Self(uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, data)) + } +} diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 0d3a537c4..20a3c28e7 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -92,7 +92,8 @@ pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), for texture in textures.textures { let url = Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; - is_texture_url_valid(url, config)? } + is_texture_url_valid(url, config)? + } Ok(()) } diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 817adb245..9c4391405 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -7,7 +7,7 @@ use pumpkin_protocol::{ client::{ config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, login::{CLoginSuccess, CSetCompression}, - status::{CPingResponse, CStatusResponse}, + status::CPingResponse, }, server::{ config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, @@ -17,6 +17,7 @@ use pumpkin_protocol::{ }, ConnectionState, KnownPack, CURRENT_MC_PROTOCOL, }; +use uuid::Uuid; use crate::{ client::authentication::{self, GameProfile}, @@ -54,7 +55,7 @@ impl Client { } pub fn handle_status_request(&self, server: &Arc, _status_request: SStatusRequest) { - self.send_packet(&CStatusResponse::new(&server.status_response_json)); + self.send_packet(&server.get_status()); } pub fn handle_ping_request(&self, _server: &Arc, ping_request: SStatusPingRequest) { @@ -185,25 +186,26 @@ impl Client { _login_acknowledged: SLoginAcknowledged, ) { self.connection_state.store(ConnectionState::Config); - server.send_brand(self); + self.send_packet(&server.get_branding()); let resource_config = &ADVANCED_CONFIG.resource_pack; if resource_config.enabled { - let prompt_message = if resource_config.prompt_message.is_empty() { - None - } else { - Some(TextComponent::text(&resource_config.prompt_message)) - }; - self.send_packet(&CConfigAddResourcePack::new( - pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3( + let resource_pack = CConfigAddResourcePack::new( + Uuid::new_v3( &uuid::Uuid::NAMESPACE_DNS, resource_config.resource_pack_url.as_bytes(), - )), + ), &resource_config.resource_pack_url, &resource_config.resource_pack_sha1, resource_config.force, - prompt_message, - )); + if !resource_config.prompt_message.is_empty() { + Some(TextComponent::text(&resource_config.prompt_message)) + } else { + None + }, + ); + + self.send_packet(&resource_pack); } // known data packs diff --git a/pumpkin/src/server/bikeshed_server_listing.rs b/pumpkin/src/server/bikeshed_server_listing.rs new file mode 100644 index 000000000..57ca00fc7 --- /dev/null +++ b/pumpkin/src/server/bikeshed_server_listing.rs @@ -0,0 +1,94 @@ +use std::{io::Cursor, path::Path}; + +use base64::{engine::general_purpose, Engine as _}; +use image::GenericImageView as _; +use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_protocol::{ + client::{config::CPluginMessage, status::CStatusResponse}, + Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, +}; + +use super::CURRENT_MC_VERSION; + +pub struct BikeShedServerListing { + _status_response: StatusResponse, + // We cache the json response here so we don't parse it every time someone makes a Status request. + // Keep in mind that we must parse this again, when the StatusResponse changes which usually happen when a player joins or leaves + status_response_json: String, + /// Cached Server brand buffer so we don't have to rebuild them every time a player joins + cached_server_brand: Vec, +} + +impl BikeShedServerListing { + pub fn new() -> Self { + let status_response = Self::build_response(&BASIC_CONFIG); + let status_response_json = serde_json::to_string(&status_response) + .expect("Failed to parse Status response into JSON"); + let cached_server_brand = Self::build_brand(); + + BikeShedServerListing { + _status_response: status_response, + status_response_json, + cached_server_brand, + } + } + + pub fn get_branding(&self) -> CPluginMessage { + CPluginMessage::new("minecraft:brand", &self.cached_server_brand) + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + CStatusResponse::new(&self.status_response_json) + } + + pub fn build_response(config: &BasicConfiguration) -> StatusResponse { + let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); + let icon = if Path::new(icon_path).exists() { + Some(Self::load_icon(icon_path)) + } else { + None + }; + + StatusResponse { + version: Some(Version { + name: CURRENT_MC_VERSION.into(), + protocol: CURRENT_MC_PROTOCOL, + }), + players: Some(Players { + max: config.max_players, + online: 0, + sample: vec![Sample { + name: "".into(), + id: "".into(), + }], + }), + description: config.motd.clone(), + favicon: icon, + enforece_secure_chat: false, + } + } + + fn load_icon(path: &str) -> String { + let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { + Ok(icon) => icon, + Err(_) => return "".into(), + }; + let dimension = icon.dimensions(); + assert!(dimension.0 == 64, "Icon width must be 64"); + assert!(dimension.1 == 64, "Icon height must be 64"); + let mut image = Vec::with_capacity(64 * 64 * 4); + icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) + .unwrap(); + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(image, &mut result); + result + } + + fn build_brand() -> Vec { + let brand = "Pumpkin"; + let mut buf = vec![]; + let _ = VarInt(brand.len() as i32).encode(&mut buf); + buf.extend_from_slice(brand.as_bytes()); + buf + } +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 9569e14c4..ca43f5211 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,22 +1,17 @@ -use base64::{engine::general_purpose, Engine}; use bikeshed_key_store::BikeShedKeyStore; -use image::GenericImageView; +use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; -use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_config::BASIC_CONFIG; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_plugin::PluginLoader; use pumpkin_protocol::client::login::CEncryptionRequest; -use pumpkin_protocol::{ - client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, - CURRENT_MC_PROTOCOL, -}; +use pumpkin_protocol::client::status::CStatusResponse; +use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; use std::{ - io::Cursor, - path::Path, sync::{ atomic::{AtomicI32, Ordering}, Arc, @@ -37,22 +32,16 @@ use crate::{ }; mod bikeshed_key_store; +mod bikeshed_server_listing; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { key_store: BikeShedKeyStore, + server_listing: BikeShedServerListing, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, - pub worlds: Vec>, - pub status_response: StatusResponse, - // We cache the json response here so we don't parse it every time someone makes a Status request. - // Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves - pub status_response_json: String, - - /// Cache the Server brand buffer so we don't have to rebuild them every time a player joins - pub cached_server_brand: Vec, /// Cache the registry so we don't have to parse it every time a player joins pub cached_registry: Vec, @@ -68,13 +57,9 @@ pub struct Server { impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Self { - let status_response = Self::build_response(&BASIC_CONFIG); - let status_response_json = serde_json::to_string(&status_response) - .expect("Failed to parse Status response into JSON"); - let cached_server_brand = Self::build_brand(); - // TODO: only create when needed let key_store = BikeShedKeyStore::new(); + let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -106,9 +91,7 @@ impl Server { command_dispatcher: Arc::new(command_dispatcher), auth_client, key_store, - status_response, - status_response_json, - cached_server_brand, + server_listing, } } @@ -118,7 +101,7 @@ impl Server { GameMode::Undefined => GameMode::Survival, game_mode => game_mode, }; - // Basicly the default world + // Basically the default world // TODO: select default from config let world = self.worlds[0].clone(); @@ -165,6 +148,14 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + pub fn get_branding(&self) -> CPluginMessage<'_> { + self.server_listing.get_branding() + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + self.server_listing.get_status() + } + pub fn encryption_request<'a>( &'a self, verification_token: &'a [u8; 4], @@ -181,64 +172,4 @@ impl Server { pub fn digest_secret(&self, secret: &[u8]) -> String { self.key_store.get_digest(secret) } - - pub fn build_brand() -> Vec { - let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); - buf - } - - pub fn send_brand(&self, client: &Client) { - // send server brand - client.send_packet(&CPluginMessage::new( - "minecraft:brand", - &self.cached_server_brand, - )); - } - - pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); - let icon = if Path::new(icon_path).exists() { - Some(Self::load_icon(icon_path)) - } else { - None - }; - - StatusResponse { - version: Some(Version { - name: CURRENT_MC_VERSION.into(), - protocol: CURRENT_MC_PROTOCOL, - }), - players: Some(Players { - max: config.max_players, - online: 0, - sample: vec![Sample { - name: "".into(), - id: "".into(), - }], - }), - description: config.motd.clone(), - favicon: icon, - // TODO - enforece_secure_chat: false, - } - } - - pub fn load_icon(path: &str) -> String { - let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { - Ok(icon) => icon, - Err(_) => return "".into(), - }; - let dimension = icon.dimensions(); - assert!(dimension.0 == 64, "Icon width must be 64"); - assert!(dimension.1 == 64, "Icon height must be 64"); - let mut image = Vec::with_capacity(64 * 64 * 4); - icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) - .unwrap(); - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(image, &mut result); - result - } } From c13261ba2020a9de753ead464e3ef0011c467aaa Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Tue, 10 Sep 2024 23:54:21 -0400 Subject: [PATCH 3/5] standardize use of compact serialization for UUIDs. --- .../src/client/config/c_add_resource_pack.rs | 6 ++---- .../src/client/play/c_player_chat_message.rs | 7 ++++--- .../src/client/play/c_player_remove.rs | 20 ++++++++++++++---- .../src/client/play/c_spawn_player.rs | 7 ++++--- pumpkin-protocol/src/lib.rs | 1 - pumpkin-protocol/src/uuid.rs | 21 ------------------- pumpkin/src/client/player_packet.rs | 2 +- pumpkin/src/world/mod.rs | 7 +++---- 8 files changed, 30 insertions(+), 41 deletions(-) delete mode 100644 pumpkin-protocol/src/uuid.rs diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs index 926fb6e39..48b011faa 100644 --- a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -2,12 +2,10 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; use serde::Serialize; -use crate::uuid::UUID; - #[derive(Serialize)] #[packet(0x09)] pub struct CConfigAddResourcePack<'a> { - uuid: UUID, + uuid: uuid::Uuid, url: &'a str, hash: &'a str, // max 40 forced: bool, @@ -23,7 +21,7 @@ impl<'a> CConfigAddResourcePack<'a> { prompt_message: Option>, ) -> Self { Self { - uuid: UUID(uuid), + uuid, url, hash, forced, diff --git a/pumpkin-protocol/src/client/play/c_player_chat_message.rs b/pumpkin-protocol/src/client/play/c_player_chat_message.rs index 8ec95ddca..7d5e7c7ce 100644 --- a/pumpkin-protocol/src/client/play/c_player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_player_chat_message.rs @@ -2,11 +2,12 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; use serde::Serialize; -use crate::{uuid::UUID, BitSet, VarInt}; +use crate::{BitSet, VarInt}; #[derive(Serialize)] #[packet(0x39)] pub struct CPlayerChatMessage<'a> { - sender: UUID, + #[serde(with = "uuid::serde::compact")] + sender: uuid::Uuid, index: VarInt, message_signature: Option<&'a [u8]>, message: &'a str, @@ -24,7 +25,7 @@ pub struct CPlayerChatMessage<'a> { impl<'a> CPlayerChatMessage<'a> { #[expect(clippy::too_many_arguments)] pub fn new( - sender: UUID, + sender: uuid::Uuid, index: VarInt, message_signature: Option<&'a [u8]>, message: &'a str, diff --git a/pumpkin-protocol/src/client/play/c_player_remove.rs b/pumpkin-protocol/src/client/play/c_player_remove.rs index 4d128a816..20b7ba218 100644 --- a/pumpkin-protocol/src/client/play/c_player_remove.rs +++ b/pumpkin-protocol/src/client/play/c_player_remove.rs @@ -1,20 +1,32 @@ use pumpkin_macros::packet; -use serde::Serialize; +use serde::{ser::SerializeSeq, Serialize}; -use crate::{uuid::UUID, VarInt}; +use crate::VarInt; #[derive(Serialize)] #[packet(0x3D)] pub struct CRemovePlayerInfo<'a> { players_count: VarInt, - players: &'a [UUID], + #[serde(serialize_with = "serialize_slice_uuids")] + players: &'a [uuid::Uuid], } impl<'a> CRemovePlayerInfo<'a> { - pub fn new(players_count: VarInt, players: &'a [UUID]) -> Self { + pub fn new(players_count: VarInt, players: &'a [uuid::Uuid]) -> Self { Self { players_count, players, } } } + +fn serialize_slice_uuids( + uuids: &[uuid::Uuid], + serializer: S, +) -> Result { + let mut seq = serializer.serialize_seq(Some(uuids.len()))?; + for uuid in uuids { + seq.serialize_element(uuid.as_bytes())?; + } + seq.end() +} diff --git a/pumpkin-protocol/src/client/play/c_spawn_player.rs b/pumpkin-protocol/src/client/play/c_spawn_player.rs index 73d0f8541..c9c65b92d 100644 --- a/pumpkin-protocol/src/client/play/c_spawn_player.rs +++ b/pumpkin-protocol/src/client/play/c_spawn_player.rs @@ -1,13 +1,14 @@ use pumpkin_macros::packet; use serde::Serialize; -use crate::{uuid::UUID, VarInt}; +use crate::VarInt; #[derive(Serialize)] #[packet(0x01)] pub struct CSpawnEntity { entity_id: VarInt, - entity_uuid: UUID, + #[serde(with = "uuid::serde::compact")] + entity_uuid: uuid::Uuid, typ: VarInt, x: f64, y: f64, @@ -25,7 +26,7 @@ impl CSpawnEntity { #[expect(clippy::too_many_arguments)] pub fn new( entity_id: VarInt, - entity_uuid: UUID, + entity_uuid: uuid::Uuid, typ: VarInt, x: f64, y: f64, diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index c2fee6f50..4381934b4 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -10,7 +10,6 @@ pub mod packet_decoder; pub mod packet_encoder; pub mod server; pub mod slot; -pub mod uuid; /// To current Minecraft protocol /// Don't forget to change this when porting diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs deleted file mode 100644 index 39988ae40..000000000 --- a/pumpkin-protocol/src/uuid.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Serialize; - -#[derive(Clone)] -/// Wrapper around uuid::UUID, Please use this in every Packet containing a UUID -/// We use this to we can do own Serializing -pub struct UUID(pub uuid::Uuid); - -impl Serialize for UUID { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(self.0.as_bytes()) - } -} - -impl UUID { - pub fn new(data: &[u8]) -> Self { - Self(uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, data)) - } -} diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index f61457467..139c0b862 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -316,7 +316,7 @@ impl Player { let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CPlayerChatMessage::new( - pumpkin_protocol::uuid::UUID(gameprofile.id), + gameprofile.id, 1.into(), chat_message.signature.as_deref(), &message, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 18a21bec5..b61ddc868 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -13,7 +13,6 @@ use pumpkin_protocol::{ CChunkData, CGameEvent, CLogin, CPlayerAbilities, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, }, - uuid::UUID, ClientPacket, VarInt, }; use pumpkin_world::level::Level; @@ -170,7 +169,7 @@ impl World { // TODO: add velo &CSpawnEntity::new( entity_id.into(), - UUID(gameprofile.id), + gameprofile.id, (EntityType::Player as i32).into(), x, y, @@ -192,7 +191,7 @@ impl World { let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( existing_player.entity_id().into(), - UUID(gameprofile.id), + gameprofile.id, (EntityType::Player as i32).into(), pos.x, pos.y, @@ -292,7 +291,7 @@ impl World { let uuid = player.gameprofile.id; self.broadcast_packet_expect( &[player.client.token], - &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), + &CRemovePlayerInfo::new(1.into(), &[uuid]), ); self.remove_entity(&player.entity); } From 8bf8ba14dacfe7f6351cc7fd4ffc5a7520cde103 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 12 Sep 2024 18:48:22 -0400 Subject: [PATCH 4/5] settle on key_store for a name --- .../server/{bikeshed_key_store.rs => key_store.rs} | 6 +++--- pumpkin/src/server/mod.rs | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) rename pumpkin/src/server/{bikeshed_key_store.rs => key_store.rs} (96%) diff --git a/pumpkin/src/server/bikeshed_key_store.rs b/pumpkin/src/server/key_store.rs similarity index 96% rename from pumpkin/src/server/bikeshed_key_store.rs rename to pumpkin/src/server/key_store.rs index e3b447af6..54393dccf 100644 --- a/pumpkin/src/server/bikeshed_key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -6,13 +6,13 @@ use sha2::Digest; use crate::client::EncryptionError; -pub struct BikeShedKeyStore { +pub struct KeyStore { pub _public_key: RsaPublicKey, pub private_key: RsaPrivateKey, pub public_key_der: Box<[u8]>, } -impl BikeShedKeyStore { +impl KeyStore { pub fn new() -> Self { log::debug!("Creating encryption keys..."); let (public_key, private_key) = Self::generate_keys(); @@ -22,7 +22,7 @@ impl BikeShedKeyStore { &private_key.e().to_bytes_be(), ) .into_boxed_slice(); - BikeShedKeyStore { + KeyStore { _public_key: public_key, private_key, public_key_der, diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index ca43f5211..364101500 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,4 +1,4 @@ -use bikeshed_key_store::BikeShedKeyStore; +use key_store::KeyStore; use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; @@ -31,13 +31,11 @@ use crate::{ world::World, }; -mod bikeshed_key_store; -mod bikeshed_server_listing; +mod key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { - key_store: BikeShedKeyStore, - server_listing: BikeShedServerListing, + key_store: KeyStore, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -58,7 +56,7 @@ impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Self { // TODO: only create when needed - let key_store = BikeShedKeyStore::new(); + let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( @@ -90,7 +88,7 @@ impl Server { worlds: vec![Arc::new(world)], command_dispatcher: Arc::new(command_dispatcher), auth_client, - key_store, + key_store: KeyStore::new(), server_listing, } } From d68a25b169786dcc703e5885598eda3a4aee51ba Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 12 Sep 2024 18:50:26 -0400 Subject: [PATCH 5/5] adopt proposed alternative name for cached data --- ..._server_listing.rs => connection_cache.rs} | 42 +++++++++++-------- pumpkin/src/server/mod.rs | 11 +++-- 2 files changed, 32 insertions(+), 21 deletions(-) rename pumpkin/src/server/{bikeshed_server_listing.rs => connection_cache.rs} (94%) diff --git a/pumpkin/src/server/bikeshed_server_listing.rs b/pumpkin/src/server/connection_cache.rs similarity index 94% rename from pumpkin/src/server/bikeshed_server_listing.rs rename to pumpkin/src/server/connection_cache.rs index 57ca00fc7..a8268ba3a 100644 --- a/pumpkin/src/server/bikeshed_server_listing.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -10,33 +10,49 @@ use pumpkin_protocol::{ use super::CURRENT_MC_VERSION; -pub struct BikeShedServerListing { +pub struct CachedStatus { _status_response: StatusResponse, // We cache the json response here so we don't parse it every time someone makes a Status request. // Keep in mind that we must parse this again, when the StatusResponse changes which usually happen when a player joins or leaves status_response_json: String, +} + +pub struct CachedBranding { /// Cached Server brand buffer so we don't have to rebuild them every time a player joins cached_server_brand: Vec, } -impl BikeShedServerListing { +impl CachedBranding { + pub fn new() -> CachedBranding { + let cached_server_brand = Self::build_brand(); + CachedBranding { + cached_server_brand, + } + } + pub fn get_branding(&self) -> CPluginMessage { + CPluginMessage::new("minecraft:brand", &self.cached_server_brand) + } + fn build_brand() -> Vec { + let brand = "Pumpkin"; + let mut buf = vec![]; + let _ = VarInt(brand.len() as i32).encode(&mut buf); + buf.extend_from_slice(brand.as_bytes()); + buf + } +} + +impl CachedStatus { pub fn new() -> Self { let status_response = Self::build_response(&BASIC_CONFIG); let status_response_json = serde_json::to_string(&status_response) .expect("Failed to parse Status response into JSON"); - let cached_server_brand = Self::build_brand(); - BikeShedServerListing { + CachedStatus { _status_response: status_response, status_response_json, - cached_server_brand, } } - pub fn get_branding(&self) -> CPluginMessage { - CPluginMessage::new("minecraft:brand", &self.cached_server_brand) - } - pub fn get_status(&self) -> CStatusResponse<'_> { CStatusResponse::new(&self.status_response_json) } @@ -83,12 +99,4 @@ impl BikeShedServerListing { general_purpose::STANDARD.encode_string(image, &mut result); result } - - fn build_brand() -> Vec { - let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); - buf - } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 364101500..68dcd9501 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,5 +1,5 @@ +use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; -use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; @@ -31,11 +31,14 @@ use crate::{ world::World, }; +mod connection_cache; mod key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { key_store: KeyStore, + server_listing: CachedStatus, + server_branding: CachedBranding, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -57,7 +60,6 @@ impl Server { pub fn new() -> Self { // TODO: only create when needed - let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -89,7 +91,8 @@ impl Server { command_dispatcher: Arc::new(command_dispatcher), auth_client, key_store: KeyStore::new(), - server_listing, + server_listing: CachedStatus::new(), + server_branding: CachedBranding::new(), } } @@ -147,7 +150,7 @@ impl Server { } pub fn get_branding(&self) -> CPluginMessage<'_> { - self.server_listing.get_branding() + self.server_branding.get_branding() } pub fn get_status(&self) -> CStatusResponse<'_> {