use std::io::Cursor;
use std::time::Duration;
use serde_json::Value;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::timeout;
#[cfg(feature = "proxy")]
use tokio_socks::tcp::Socks5Stream;
use crate::core::time::{start_timer, elapsed_ms};
use crate::error::McError;
use crate::models::{JavaStatus, PingResult, ServerData};
use crate::proxy::ProxyConfig;
#[cfg(feature = "proxy")]
use crate::proxy::ProxyKind;
use super::{PingProtocol, ResolvedTarget};
const PROTOCOL_VERSION: i32 = 47;
const READ_BUFFER_SIZE: usize = 4096;
static STATUS_REQUEST: &[u8] = &[0x00];
#[derive(Debug, Clone, Copy, Default)]
pub struct JavaModernProtocol;
impl PingProtocol for JavaModernProtocol {
fn name(&self) -> &'static str { "java-modern" }
async fn ping(
&self,
target: &ResolvedTarget,
tout: Duration,
proxy: Option<&ProxyConfig>,
) -> Result<PingResult, McError> {
let start = start_timer();
let mut stream = connect_tcp(target, proxy, tout).await?;
stream.set_nodelay(true).map_err(McError::Io)?;
send_packet(&mut stream, &build_handshake(&target.hostname, target.addr.port()), tout).await?;
send_packet(&mut stream, STATUS_REQUEST, tout).await?;
let (json, latency) = read_response(stream, start, tout).await?;
let data = ServerData::Java(JavaStatus::from_json(json)?);
Ok(PingResult::new(data, latency))
}
}
async fn connect_tcp(
target: &ResolvedTarget,
proxy: Option<&ProxyConfig>,
tout: Duration,
) -> Result<TcpStream, McError> {
#[cfg(feature = "proxy")]
if let Some(cfg) = proxy {
let proxy_addr = format!("{}:{}", cfg.addr().host, cfg.addr().port);
return match cfg.kind() {
ProxyKind::Socks5 => {
let target_addr = (target.hostname.as_str(), target.addr.port());
let stream = match cfg.auth() {
None => {
timeout(tout, Socks5Stream::connect(proxy_addr.as_str(), target_addr))
.await
.map_err(|_| McError::timeout())?
.map_err(|e| McError::proxy_error(e.to_string()))?
}
Some(auth) => {
timeout(
tout,
Socks5Stream::connect_with_password(
proxy_addr.as_str(),
target_addr,
&auth.username,
&auth.password,
),
)
.await
.map_err(|_| McError::timeout())?
.map_err(|e| McError::proxy_error(e.to_string()))?
}
};
Ok(stream.into_inner())
}
ProxyKind::Http => {
crate::proxy::http::connect(
&proxy_addr,
&target.hostname,
target.addr.port(),
cfg.auth(),
tout,
)
.await
}
};
}
let _ = proxy;
timeout(tout, TcpStream::connect(target.addr))
.await
.map_err(|_| McError::timeout())?
.map_err(|e| McError::connection(e.to_string()))
}
async fn send_packet(stream: &mut TcpStream, data: &[u8], tout: Duration) -> Result<(), McError> {
let mut packet = Vec::with_capacity(data.len() + 5);
write_varint(&mut packet, data.len() as i32);
packet.extend_from_slice(data);
timeout(tout, stream.write_all(&packet))
.await
.map_err(|_| McError::timeout())?
.map_err(McError::Io)
}
async fn read_response(
mut stream: TcpStream,
start: tokio::time::Instant,
tout: Duration,
) -> Result<(Value, f64), McError> {
let mut resp = Vec::new();
let mut buf = [0u8; READ_BUFFER_SIZE];
let mut expected = None;
loop {
let n = timeout(tout, stream.read(&mut buf))
.await
.map_err(|_| McError::timeout())?
.map_err(McError::Io)?;
if n == 0 { break; }
resp.extend_from_slice(&buf[..n]);
if expected.is_none() && resp.len() >= 5 {
let mut cur = Cursor::new(&resp);
if let Ok(len) = read_varint(&mut cur) {
expected = Some(cur.position() as usize + len as usize);
}
}
if expected.is_some_and(|e| resp.len() >= e) { break; }
}
if resp.is_empty() {
return Err(McError::invalid_response("empty response"));
}
let mut cur = Cursor::new(&resp);
let _pkt_len = read_varint(&mut cur)?;
if read_varint(&mut cur)? != 0x00 {
return Err(McError::invalid_response("unexpected packet id"));
}
let json_len = read_varint(&mut cur)? as usize;
let pos = cur.position() as usize;
let end = pos
.checked_add(json_len)
.filter(|&e| e <= resp.len())
.ok_or_else(|| McError::invalid_response("json length exceeds packet"))?;
let json = serde_json::from_slice(&resp[pos..end]).map_err(|e| McError::from(crate::error::ProtocolError::Json(e)))?;
let latency = elapsed_ms(start);
Ok((json, latency))
}
fn build_handshake(host: &str, port: u16) -> Vec<u8> {
let mut buf = Vec::with_capacity(7 + host.len());
write_varint(&mut buf, 0x00);
write_varint(&mut buf, PROTOCOL_VERSION);
write_varint(&mut buf, host.len() as i32);
buf.extend_from_slice(host.as_bytes());
buf.extend_from_slice(&port.to_be_bytes());
write_varint(&mut buf, 1);
buf
}
pub(crate) fn write_varint(buf: &mut Vec<u8>, v: i32) {
let mut v = v as u32;
while v >= 0x80 {
buf.push((v as u8 & 0x7F) | 0x80);
v >>= 7;
}
buf.push(v as u8);
}
pub(crate) fn read_varint<R: std::io::Read>(r: &mut R) -> Result<i32, McError> {
let (mut res, mut shift) = (0i32, 0u32);
loop {
let mut b = [0u8; 1];
r.read_exact(&mut b)
.map_err(|e| McError::invalid_response(e.to_string()))?;
let val = b[0] as i32;
res |= (val & 0x7F) << shift;
shift += 7;
if shift > 35 {
return Err(McError::invalid_response("varint too long"));
}
if val & 0x80 == 0 { break; }
}
Ok(res)
}