use arc_swap::ArcSwap; use bytes::{BufMut, BytesMut}; use hmac::{Hmac, KeyInit, Mac}; use pumpkin_config::networking::proxy::VelocityConfig; use pumpkin_protocol::{ Property, java::client::login::CLoginPluginRequest, java::server::login::SLoginPluginResponse, ser::NetworkReadExt, }; use rand::RngExt; use sha2::Sha256; use std::sync::Arc; /// Proxy implementation for Velocity by `PaperMC` /// Sadly, `PaperMC` does not care about 3rd parties providing support for Velocity. There is no documentation. /// I had to understand the code logic by looking at `PaperMC`'s Velocity implementation: use std::{ io::Read, net::{IpAddr, SocketAddr}, }; use thiserror::Error; use tracing::debug; use crate::net::{GameProfile, java::JavaClient}; type HmacSha256 = Hmac; const MAX_SUPPORTED_FORWARDING_VERSION: u8 = 4; const PLAYER_INFO_CHANNEL: &str = "velocity:player_info"; #[derive(Error, Debug)] pub enum VelocityError { #[error("No response data received")] NoData, #[error("Unable to verify player details")] FailedVerifyIntegrity, #[error("Failed to read forward version")] FailedReadForwardVersion, #[error("Unsupported forwarding version {0}. Maximum supported version is {1}")] UnsupportedForwardVersion(u8, u8), #[error("Failed to read address")] FailedReadAddress, #[error("Failed to parse address")] FailedParseAddress, #[error("Failed to read game profile name")] FailedReadProfileName, #[error("Failed to read game profile UUID")] FailedReadProfileUUID, #[error("Failed to read game profile properties")] FailedReadProfileProperties, } pub async fn velocity_login(client: &JavaClient) { // TODO: Validate the packet transaction id from the plugin response with this let velocity_message_id: i32 = rand::rng().random(); let mut buf = BytesMut::new(); buf.put_u8(MAX_SUPPORTED_FORWARDING_VERSION); client .enqueue_packet(&CLoginPluginRequest::new( velocity_message_id.into(), PLAYER_INFO_CHANNEL, &buf, )) .await; } #[must_use] pub fn check_integrity(data: (&[u8], &[u8]), secret: &str) -> bool { let (signature, data_without_signature) = data; // Our fault, we can panic/expect? let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size"); mac.update(data_without_signature); mac.verify_slice(signature).is_ok() } fn read_game_profile(read: impl Read) -> Result { let mut read = read; let id = read .get_uuid() .map_err(|_| VelocityError::FailedReadProfileUUID)?; let name = read .get_str() .map_err(|_| VelocityError::FailedReadProfileName)?; let properties = read .get_list(|data| { let name = data.get_str()?; let value = data.get_str()?; let signature = data.get_option(NetworkReadExt::get_str)?; Ok(Property { name, value, signature, }) }) .map_err(|_| VelocityError::FailedReadProfileProperties)?; Ok(GameProfile { id, name: name.into_string(), properties: ArcSwap::new(Arc::from(properties)), profile_actions: None, }) } pub fn receive_velocity_plugin_response( port: u16, config: &VelocityConfig, response: SLoginPluginResponse, ) -> Result<(GameProfile, SocketAddr), VelocityError> { debug!("Received velocity response"); if let Some(data) = response.data { let (signature, mut data_without_signature) = data.split_at(32); if !check_integrity((signature, data_without_signature), &config.secret) { return Err(VelocityError::FailedVerifyIntegrity); } // Check velocity version let version = data_without_signature .get_var_int() .map_err(|_| VelocityError::FailedReadForwardVersion)?; let version = version.0 as u8; if version > MAX_SUPPORTED_FORWARDING_VERSION { return Err(VelocityError::UnsupportedForwardVersion( version, MAX_SUPPORTED_FORWARDING_VERSION, )); } let addr = data_without_signature .get_str() .map_err(|_| VelocityError::FailedReadAddress)?; let socket_addr: SocketAddr = SocketAddr::new( addr.parse::() .map_err(|_| VelocityError::FailedParseAddress)?, port, ); let profile = read_game_profile(&mut data_without_signature)?; return Ok((profile, socket_addr)); } Err(VelocityError::NoData) }