use anyhow::{Context, Result};
use serde::Deserialize;
use thiserror::Error;
use tokio::net::TcpStream;
use crate::protocol::{self, AsyncReadRawPacket, AsyncWriteRawPacket};
#[derive(Error, Debug)]
pub enum ServerError {
#[error("error reading or writing data")]
ProtocolError,
#[error("failed to connect to server")]
FailedToConnect,
#[error("invalid JSON response: \"{0}\"")]
InvalidJson(String),
}
impl From<protocol::ProtocolError> for ServerError {
fn from(_err: protocol::ProtocolError) -> Self {
ServerError::ProtocolError
}
}
#[derive(Debug, Deserialize)]
pub struct ServerVersion {
pub name: String,
pub protocol: u32,
}
#[derive(Debug, Deserialize)]
pub struct ServerPlayer {
pub name: String,
pub id: String,
}
#[derive(Debug, Deserialize)]
pub struct ServerPlayers {
pub max: u32,
pub online: u32,
pub sample: Option<Vec<ServerPlayer>>,
}
#[derive(Debug, Deserialize)]
pub struct ServerDescription {
pub text: String,
}
#[derive(Debug, Deserialize)]
pub struct StatusResponse {
pub version: ServerVersion,
pub players: ServerPlayers,
pub description: ServerDescription,
pub favicon: Option<String>,
}
const LATEST_PROTOCOL_VERSION: usize = 578;
const DEFAULT_PORT: u16 = 25565;
pub struct ConnectionConfig {
protocol_version: usize,
address: String,
port: u16,
}
impl ConnectionConfig {
pub fn build(address: String) -> Self {
ConnectionConfig {
protocol_version: LATEST_PROTOCOL_VERSION,
address,
port: DEFAULT_PORT,
}
}
pub fn with_protocol_version(mut self, protocol_version: usize) -> Self {
self.protocol_version = protocol_version;
self
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub async fn connect(self) -> Result<StatusConnection> {
let stream = TcpStream::connect(format!("{}:{}", self.address, self.port))
.await
.map_err(|_| ServerError::FailedToConnect)?;
Ok(StatusConnection {
stream,
protocol_version: self.protocol_version,
address: self.address,
port: self.port,
})
}
}
pub async fn connect(address: String) -> Result<StatusConnection> {
ConnectionConfig::build(address).connect().await
}
pub struct StatusConnection {
stream: TcpStream,
protocol_version: usize,
address: String,
port: u16,
}
impl StatusConnection {
pub async fn status(&mut self) -> Result<StatusResponse> {
let handshake = protocol::HandshakePacket::new(
self.protocol_version,
self.address.to_string(),
self.port,
);
self.stream
.write_packet(handshake)
.await
.context("failed to write handshake packet")?;
self.stream
.write_packet(protocol::RequestPacket::new())
.await
.context("failed to write request packet")?;
let response: protocol::ResponsePacket = self
.stream
.read_packet()
.await
.context("failed to read response packet")?;
Ok(serde_json::from_str(&response.body)
.map_err(|_| ServerError::InvalidJson(response.body))?)
}
}