use crate::error::{Result, LateJavaCoreError};
use serde::{Deserialize, Serialize};
use std::net::{TcpStream, SocketAddr};
use std::time::Duration;
use std::io::{Read, Write};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub version: ServerVersion,
pub players: Players,
pub description: ServerDescription,
pub favicon: Option<String>,
pub modinfo: Option<ModInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerVersion {
pub name: String,
pub protocol: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Players {
pub max: i32,
pub online: i32,
pub sample: Option<Vec<Player>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Player {
pub name: String,
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerDescription {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModInfo {
pub r#type: String,
pub mod_list: Vec<Mod>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mod {
pub modid: String,
pub version: String,
}
#[derive(Debug, Clone)]
pub struct PingResult {
pub latency: u64,
pub server_info: ServerInfo,
}
pub struct StatusClient;
impl StatusClient {
pub fn ping(host: &str, port: u16) -> Result<PingResult> {
let addr: SocketAddr = format!("{}:{}", host, port).parse()
.map_err(|_| LateJavaCoreError::Network(reqwest::Error::from(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid address"))))?;
let start = std::time::Instant::now();
let mut stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5))
.map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;
let handshake = Self::create_handshake(host, port);
stream.write_all(&handshake)
.map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;
let status_request = Self::create_status_request();
stream.write_all(&status_request)
.map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;
let mut buffer = Vec::new();
stream.read_to_end(&mut buffer)
.map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;
let latency = start.elapsed().as_millis() as u64;
let server_info = Self::parse_response(&buffer)?;
Ok(PingResult {
latency,
server_info,
})
}
fn create_handshake(host: &str, port: u16) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.push(0x00);
buffer.extend_from_slice(&754i32.to_be_bytes());
let host_bytes = host.as_bytes();
buffer.extend_from_slice(&(host_bytes.len() as i32).to_be_bytes());
buffer.extend_from_slice(host_bytes);
buffer.extend_from_slice(&port.to_be_bytes());
buffer.push(0x01);
let packet_length = buffer.len() as i32;
let mut packet = Vec::new();
packet.extend_from_slice(&packet_length.to_be_bytes());
packet.extend(buffer);
packet
}
fn create_status_request() -> Vec<u8> {
let mut buffer = Vec::new();
buffer.push(0x00);
let packet_length = buffer.len() as i32;
let mut packet = Vec::new();
packet.extend_from_slice(&packet_length.to_be_bytes());
packet.extend(buffer);
packet
}
fn parse_response(buffer: &[u8]) -> Result<ServerInfo> {
if buffer.len() < 5 {
return Err(LateJavaCoreError::Minecraft("Response too short".to_string()));
}
let packet_length = i32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) as usize;
if buffer.len() < packet_length + 4 {
return Err(LateJavaCoreError::Minecraft("Incomplete packet".to_string()));
}
let packet_id = buffer[4];
if packet_id != 0x00 {
return Err(LateJavaCoreError::Minecraft("Invalid packet ID".to_string()));
}
let json_length = i32::from_be_bytes([buffer[5], buffer[6], buffer[7], buffer[8]]) as usize;
if buffer.len() < 9 + json_length {
return Err(LateJavaCoreError::Minecraft("Incomplete JSON".to_string()));
}
let json_bytes = &buffer[9..9 + json_length];
let json_str = std::str::from_utf8(json_bytes)
.map_err(|_| LateJavaCoreError::Minecraft("Invalid UTF-8 in JSON".to_string()))?;
let server_info: ServerInfo = serde_json::from_str(json_str)
.map_err(|e| LateJavaCoreError::Json(e))?;
Ok(server_info)
}
}
pub fn ping_server(host: &str, port: u16) -> Result<PingResult> {
StatusClient::ping(host, port)
}