#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod builder;
pub mod facade;
pub use builder::{JavaPingBuilder, BedrockPingBuilder, ServerPingBuilder};
pub use facade::{ping_java, ping_bedrock};
use std::time::Duration;
use crate::core::{dns::DnsResolver, cache::{ResponseCache, DEFAULT_RESPONSE_CACHE_SIZE}, address};
use crate::error::McError;
use crate::models::*;
use crate::protocol::{
bedrock::BedrockProtocol,
java_modern::JavaModernProtocol,
PingProtocol, ResolvedTarget,
};
use crate::proxy::ProxyConfig;
use crate::status::{BedrockServerStatus, JavaServerStatus};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_MAX_PARALLEL: usize = 10;
const JAVA_DEFAULT_PORT: u16 = 25565;
const BEDROCK_DEFAULT_PORT: u16 = 19132;
#[derive(Clone)]
pub struct McClient {
pub(crate) timeout: Duration,
pub(crate) max_parallel: usize,
pub(crate) dns: DnsResolver,
pub(crate) response_cache: ResponseCache,
#[cfg(feature = "proxy")]
pub(crate) proxy: Option<ProxyConfig>,
}
impl McClient {
pub fn builder() -> McClientBuilder { McClientBuilder::new() }
pub fn timeout(&self) -> Duration { self.timeout }
pub fn max_parallel(&self) -> usize { self.max_parallel }
#[cfg(feature = "tower")]
#[cfg_attr(docsrs, doc(cfg(feature = "tower")))]
pub fn into_service(self) -> crate::service::McService {
crate::service::McService::new(self)
}
pub async fn clear_caches(&self) { self.dns.clear().await; }
pub async fn clear_response_cache(&self) { self.response_cache.clear().await; }
pub async fn cache_stats(&self) -> CacheStats {
CacheStats {
dns_entries: self.dns.dns_len().await,
srv_entries: self.dns.srv_len().await,
response_entries: self.response_cache.len().await,
}
}
pub fn java(&self, address: impl Into<String>) -> JavaPingBuilder {
JavaPingBuilder::new(self.clone(), address)
}
pub fn bedrock(&self, address: impl Into<String>) -> BedrockPingBuilder {
BedrockPingBuilder::new(self.clone(), address)
}
pub fn server(&self, address: impl Into<String>, edition: ServerEdition) -> ServerPingBuilder {
ServerPingBuilder::new(self.clone(), address, edition)
}
pub async fn ping_many(
&self,
servers: &[ServerInfo],
) -> Vec<(ServerInfo, Result<ServerStatus, McError>)> {
use std::sync::Arc;
use tokio::sync::Semaphore;
use tokio::task::JoinSet;
let sem = Arc::new(Semaphore::new(self.max_parallel));
let mut set = JoinSet::new();
for s in servers {
let s = s.clone();
let c = self.clone();
let sem = Arc::clone(&sem);
set.spawn(async move {
let _permit = sem.acquire().await;
let result = match s.edition {
ServerEdition::Java => c.ping_java_inner(&s.address).await.map(|j| j.0),
ServerEdition::Bedrock => c.ping_bedrock_inner(&s.address).await.map(|b| b.0),
};
(s, result)
});
}
let mut out = Vec::with_capacity(servers.len());
while let Some(Ok(item)) = set.join_next().await {
out.push(item);
}
out
}
#[inline]
fn proxy_ref(&self) -> Option<&ProxyConfig> {
#[cfg(feature = "proxy")]
return self.proxy.as_ref();
#[cfg(not(feature = "proxy"))]
return None;
}
pub(crate) async fn ping_java_inner(&self, address: &str) -> Result<JavaServerStatus, McError> {
let s = self.ping_with(address, JAVA_DEFAULT_PORT, &JavaModernProtocol, true).await?;
Ok(JavaServerStatus(s))
}
pub(crate) async fn ping_bedrock_inner(&self, address: &str) -> Result<BedrockServerStatus, McError> {
let s = self.ping_with(address, BEDROCK_DEFAULT_PORT, &BedrockProtocol, false).await?;
Ok(BedrockServerStatus(s))
}
pub(crate) async fn ping_with<P: PingProtocol>(
&self,
addr: &str,
default_port: u16,
protocol: &P,
try_srv: bool,
) -> Result<ServerStatus, McError> {
let cache_key = format!("{}:{}", protocol.name(), addr);
if let Some(cached) = self.response_cache.get(&cache_key).await {
return Ok(cached);
}
if let Some(mut rx) = self.response_cache.join_or_register(&cache_key).await {
return match rx.recv().await {
Ok(Ok(status)) => {
let mut s = status;
s.cached = true;
s.latency = 0.0;
Ok(s)
}
Ok(Err(msg)) => Err(McError::invalid_response(msg)),
Err(_) => self.do_ping(addr, default_port, protocol, try_srv, &cache_key).await,
};
}
self.do_ping(addr, default_port, protocol, try_srv, &cache_key).await
}
async fn do_ping<P: PingProtocol>(
&self,
addr: &str,
default_port: u16,
protocol: &P,
try_srv: bool,
cache_key: &str,
) -> Result<ServerStatus, McError> {
let (host, port, explicit_port) = address::parse(addr, default_port)?;
let (actual_host, actual_port) = if try_srv && !explicit_port {
self.dns.lookup_srv(host, self.timeout).await
.unwrap_or_else(|| (host.to_string(), port))
} else {
(host.to_string(), port)
};
let (resolved_addr, dns_info) = self.dns.resolve(&actual_host, actual_port).await?;
let target = ResolvedTarget {
addr: resolved_addr,
hostname: host.to_string(),
dns_info,
};
match protocol.ping(&target, self.timeout, self.proxy_ref()).await {
Ok(result) => {
let status = ServerStatus {
online: true,
ip: resolved_addr.ip().to_string(),
port: resolved_addr.port(),
hostname: host.to_string(),
latency: result.latency,
dns: Some(target.dns_info),
data: result.data,
cached: false,
meta: result.meta,
};
self.response_cache.insert(cache_key.to_string(), status.clone()).await;
Ok(status)
}
Err(e) => {
self.response_cache.insert_error(cache_key, &e).await;
Err(e)
}
}
}
}
#[must_use = "call .build() to create the McClient"]
pub struct McClientBuilder {
timeout: Duration,
max_parallel: usize,
dns_cache_size: Option<usize>,
response_cache_ttl: Option<Duration>,
response_cache_size: usize,
#[cfg(feature = "proxy")]
proxy: Option<ProxyConfig>,
}
impl Default for McClientBuilder {
fn default() -> Self {
Self {
timeout: DEFAULT_TIMEOUT,
max_parallel: DEFAULT_MAX_PARALLEL,
dns_cache_size: None,
response_cache_ttl: None,
response_cache_size: DEFAULT_RESPONSE_CACHE_SIZE,
#[cfg(feature = "proxy")]
proxy: None,
}
}
}
impl McClientBuilder {
pub(crate) fn new() -> Self { Self::default() }
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn max_parallel(mut self, n: usize) -> Self {
self.max_parallel = n;
self
}
pub fn dns_cache_size(mut self, size: usize) -> Self {
self.dns_cache_size = Some(size);
self
}
pub fn response_cache(mut self, ttl: Duration, size: usize) -> Self {
self.response_cache_ttl = Some(ttl);
self.response_cache_size = size;
self
}
pub fn response_cache_ttl(mut self, ttl: Duration) -> Self {
self.response_cache_ttl = Some(ttl);
self
}
#[cfg(feature = "proxy")]
#[cfg_attr(docsrs, doc(cfg(feature = "proxy")))]
pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
self.proxy = Some(proxy);
self
}
pub fn build(self) -> McClient {
let dns = match self.dns_cache_size {
Some(size) => DnsResolver::with_cache_size(size),
None => DnsResolver::new(),
};
let response_cache = match self.response_cache_ttl {
Some(ttl) => ResponseCache::new(ttl, self.response_cache_size),
None => ResponseCache::disabled(),
};
McClient {
timeout: self.timeout,
max_parallel: self.max_parallel,
dns,
response_cache,
#[cfg(feature = "proxy")]
proxy: self.proxy,
}
}
}