package net.glowstone.net.query;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import net.glowstone.GlowServer;
import org.bukkit.scheduler.BukkitRunnable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implementation of a server for the minecraft server query protocol.
* @see <a href="http://wiki.vg/Query">Protocol Specifications</a>
*/
public class QueryServer {
/**
* The {@link EventLoopGroup} used by the query server.
*/
private EventLoopGroup group = new NioEventLoopGroup();
/**
* The {@link Bootstrap} used by netty to instantiate the query server.
*/
private Bootstrap bootstrap = new Bootstrap();
/**
* Instance of the GlowServer.
*/
private GlowServer server;
/**
* Maps each {@link InetSocketAddress} of a client to its challenge token.
*/
private Map<InetSocketAddress, Integer> challengeTokens = new ConcurrentHashMap<>();
/**
* The {@link Random} used to generate challenge tokens.
*/
private Random random = new Random();
/**
* The task used to invalidate all challenge tokens every 30 seconds.
*/
private ChallengeTokenFlushTask flushTask;
public QueryServer(GlowServer server, boolean showPlugins) {
this.server = server;
bootstrap
.group(group)
.channel(NioDatagramChannel.class)
.handler(new QueryHandler(this, showPlugins));
}
/**
* Bind the server on the specified address.
* @param address The address.
* @return Netty channel future for bind operation.
*/
public ChannelFuture bind(final SocketAddress address) {
if (flushTask == null) {
flushTask = new ChallengeTokenFlushTask();
flushTask.runTaskTimerAsynchronously(null, 600, 600);
}
return bootstrap.bind(address);
}
/**
* Shut the query server down.
*/
public void shutdown() {
group.shutdownGracefully();
if (flushTask != null) {
flushTask.cancel();
}
}
/**
* Generate a new token.
* @param address The sender address.
* @return The generated valid token.
*/
public int generateChallengeToken(InetSocketAddress address) {
int token = random.nextInt();
challengeTokens.put(address, token);
return token;
}
/**
* Verify that the request is using the correct challenge token.
* @param address The sender address.
* @param token The token.
* @return {@code true} if the token is valid.
*/
public boolean verifyChallengeToken(InetSocketAddress address, int token) {
return Objects.equals(challengeTokens.get(address), token);
}
/**
* Invalidates all challenge tokens.
*/
public void flushChallengeTokens() {
challengeTokens.clear();
}
/**
* Get the Server whose information are distributed by this query server.
* @return The server instance.
*/
public GlowServer getServer() {
return server;
}
/**
* Inner class for resetting the challenge tokens every 30 seconds.
*/
private class ChallengeTokenFlushTask extends BukkitRunnable {
@Override
public void run() {
flushChallengeTokens();
}
}
}