From 8cca0c2b51ccc09aa8f71a908edecce01315e561 Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Mon, 8 Apr 2024 23:32:45 -0400 Subject: [PATCH] Add Bedrock server pinger --- .../me/braydon/mc/service/MojangService.java | 11 ++--- .../impl/BedrockMinecraftServerPinger.java | 47 ++++++++++++++++++- .../impl/JavaMinecraftServerPinger.java | 9 ++-- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index b8e440b..d89bdc5 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -911,22 +911,21 @@ public final class MojangService { } /** - * Get the favicon of a server on the - * given platform with the given hostname. + * Get the favicon of a Java + * server with the given hostname. *

* If the favicon of the server cannot be * retrieved, the default icon will be used. *

* - * @param platform the platform of the server * @param hostname the hostname of the server * @return the server favicon * @see #DEFAULT_SERVER_ICON for the default server icon */ - public byte[] getServerFavicon(@NonNull String platform, @NonNull String hostname) { + public byte[] getServerFavicon(@NonNull String hostname) { String icon = null; // The server base64 icon try { - MinecraftServer.Favicon favicon = getMinecraftServer(platform, hostname).getValue().getFavicon(); + JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getMinecraftServer(MinecraftServer.Platform.JAVA.name(), hostname).getValue()).getFavicon(); if (favicon != null) { // Use the server's favicon icon = favicon.getBase64(); icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon @@ -1014,7 +1013,7 @@ public final class MojangService { return cached.get(); } - InetSocketAddress address = DNSUtils.resolveSRV(hostname); // Resolve the SRV record + InetSocketAddress address = platform == MinecraftServer.Platform.JAVA ? DNSUtils.resolveSRV(hostname) : null; // Resolve the SRV record int port = platform.getDefaultPort(); // Port to ping if (address != null) { // SRV was resolved, use the hostname and port hostname = address.getHostName(); diff --git a/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java index 9349f05..c33d571 100644 --- a/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java +++ b/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java @@ -678,9 +678,17 @@ package me.braydon.mc.service.pinger.impl; import lombok.NonNull; import lombok.extern.log4j.Log4j2; +import me.braydon.mc.common.DNSUtils; +import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPing; +import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPong; +import me.braydon.mc.exception.impl.BadRequestException; +import me.braydon.mc.exception.impl.ResourceNotFoundException; import me.braydon.mc.model.server.BedrockMinecraftServer; import me.braydon.mc.service.pinger.MinecraftServerPinger; +import java.io.IOException; +import java.net.*; + /** * The {@link MinecraftServerPinger} for * pinging {@link BedrockMinecraftServer}'s. @@ -689,6 +697,8 @@ import me.braydon.mc.service.pinger.MinecraftServerPinger; */ @Log4j2(topic = "Bedrock MC Server Pinger") public final class BedrockMinecraftServerPinger implements MinecraftServerPinger { + private static final int TIMEOUT = 3000; // The timeout for the socket + /** * Ping the server with the given hostname and port. * @@ -698,6 +708,41 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger */ @Override public BedrockMinecraftServer ping(@NonNull String hostname, int port) { - throw new UnsupportedOperationException("Not yet implemented."); + InetAddress inetAddress = DNSUtils.resolveA(hostname); // Resolve the hostname to an IP address + String ip = inetAddress == null ? null : inetAddress.getHostAddress(); // Get the IP address + if (ip != null) { // Was the IP resolved? + log.info("Resolved hostname: {} -> {}", hostname, ip); + } + log.info("Pinging {}:{}...", hostname, port); + long before = System.currentTimeMillis(); // Timestamp before pinging + + // Open a socket connection to the server + try (DatagramSocket socket = new DatagramSocket()) { + socket.setSoTimeout(TIMEOUT); + socket.connect(new InetSocketAddress(hostname, port)); + + long ping = System.currentTimeMillis() - before; // Calculate the ping + log.info("Pinged {}:{} in {}ms", hostname, port, ping); + + // Send the unconnected ping packet + new BedrockPacketUnconnectedPing().process(socket); + + // Handle the received unconnected pong packet + BedrockPacketUnconnectedPong unconnectedPong = new BedrockPacketUnconnectedPong(); + unconnectedPong.process(socket); + String response = unconnectedPong.getResponse(); + if (response == null) { // No pong response + throw new ResourceNotFoundException("Server didn't respond to ping"); + } + return BedrockMinecraftServer.create(hostname, ip, port, response); // Return the server + } catch (IOException ex) { + if (ex instanceof UnknownHostException) { + throw new BadRequestException("Unknown hostname: %s".formatted(hostname)); + } else if (ex instanceof SocketTimeoutException) { + throw new ResourceNotFoundException(ex); + } + log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex); + } + return null; } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java index 74fa5e3..b3f5522 100644 --- a/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java +++ b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java @@ -680,8 +680,8 @@ import lombok.NonNull; import lombok.extern.log4j.Log4j2; import me.braydon.mc.RESTfulMC; import me.braydon.mc.common.DNSUtils; -import me.braydon.mc.common.packet.impl.PacketHandshakingInSetProtocol; -import me.braydon.mc.common.packet.impl.PacketStatusInStart; +import me.braydon.mc.common.packet.impl.java.JavaPacketHandshakingInSetProtocol; +import me.braydon.mc.common.packet.impl.java.JavaPacketStatusInStart; import me.braydon.mc.exception.impl.BadRequestException; import me.braydon.mc.exception.impl.ResourceNotFoundException; import me.braydon.mc.model.server.JavaMinecraftServer; @@ -726,6 +726,7 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger