mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-10 13:49:04 +01:00
First NIO attempt
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
e0cfd4c33c
commit
1c3bb5b0ff
@ -107,12 +107,6 @@ dependencies {
|
||||
// Only here to ensure J9 module support for extensions and our classloaders
|
||||
testCompileOnly 'org.mockito:mockito-core:3.11.1'
|
||||
|
||||
// Netty
|
||||
api 'io.netty:netty-handler:4.1.65.Final'
|
||||
api 'io.netty:netty-codec:4.1.65.Final'
|
||||
api 'io.netty:netty-transport-native-epoll:4.1.65.Final:linux-x86_64'
|
||||
api 'io.netty:netty-transport-native-kqueue:4.1.65.Final:osx-x86_64'
|
||||
|
||||
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
|
||||
api 'it.unimi.dsi:fastutil:8.5.4'
|
||||
|
||||
|
@ -21,10 +21,10 @@ import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.monitoring.BenchmarkManager;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
||||
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.ping.ResponseDataConsumer;
|
||||
import net.minestom.server.recipe.RecipeManager;
|
||||
import net.minestom.server.scoreboard.TeamManager;
|
||||
@ -44,6 +44,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* The main server class used to start the server and retrieve all the managers.
|
||||
* <p>
|
||||
@ -85,9 +88,7 @@ public final class MinecraftServer {
|
||||
// Network
|
||||
private static PacketListenerManager packetListenerManager;
|
||||
private static PacketProcessor packetProcessor;
|
||||
private static NettyServer nettyServer;
|
||||
private static int nettyThreadCount = Runtime.getRuntime().availableProcessors();
|
||||
private static boolean processNettyErrors = true;
|
||||
private static Server server;
|
||||
|
||||
private static ExceptionManager exceptionManager;
|
||||
|
||||
@ -122,7 +123,6 @@ public final class MinecraftServer {
|
||||
private static int chunkViewDistance = 8;
|
||||
private static int entityViewDistance = 5;
|
||||
private static int compressionThreshold = 256;
|
||||
private static boolean packetCaching = true;
|
||||
private static boolean groupedPacket = true;
|
||||
private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null;
|
||||
private static ResponseDataConsumer responseDataConsumer;
|
||||
@ -168,7 +168,11 @@ public final class MinecraftServer {
|
||||
|
||||
tagManager = new TagManager();
|
||||
|
||||
nettyServer = new NettyServer(packetProcessor);
|
||||
try {
|
||||
server = new Server(packetProcessor);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
@ -277,16 +281,6 @@ public final class MinecraftServer {
|
||||
return packetListenerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the netty server.
|
||||
*
|
||||
* @return the netty server
|
||||
*/
|
||||
public static NettyServer getNettyServer() {
|
||||
checkInitStatus(nettyServer);
|
||||
return nettyServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manager handling all registered instances.
|
||||
*
|
||||
@ -519,34 +513,6 @@ public final class MinecraftServer {
|
||||
MinecraftServer.compressionThreshold = compressionThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the packet caching feature is enabled.
|
||||
* <p>
|
||||
* This feature allows some packets (implementing the {@link net.minestom.server.utils.cache.CacheablePacket} to be cached
|
||||
* in order to do not have to be written and compressed over and over again), this is especially useful for chunk and light packets.
|
||||
* <p>
|
||||
* It is enabled by default and it is our recommendation,
|
||||
* you should only disable it if you want to focus on low memory usage
|
||||
* at the cost of many packet writing and compression.
|
||||
*
|
||||
* @return true if the packet caching feature is enabled, false otherwise
|
||||
*/
|
||||
public static boolean hasPacketCaching() {
|
||||
return packetCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable packet caching.
|
||||
*
|
||||
* @param packetCaching true to enable packet caching
|
||||
* @throws IllegalStateException if this is called after the server started
|
||||
* @see #hasPacketCaching()
|
||||
*/
|
||||
public static void setPacketCaching(boolean packetCaching) {
|
||||
Check.stateCondition(started, "You cannot change the packet caching value after the server has been started.");
|
||||
MinecraftServer.packetCaching = packetCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the packet caching feature is enabled.
|
||||
* <p>
|
||||
@ -666,45 +632,9 @@ public final class MinecraftServer {
|
||||
return updateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of threads used by Netty.
|
||||
* <p>
|
||||
* Is the number of vCPU by default.
|
||||
*
|
||||
* @return the number of netty threads
|
||||
*/
|
||||
public static int getNettyThreadCount() {
|
||||
return nettyThreadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the number of threads used by Netty.
|
||||
*
|
||||
* @param nettyThreadCount the number of threads
|
||||
* @throws IllegalStateException if the server is already started
|
||||
*/
|
||||
public static void setNettyThreadCount(int nettyThreadCount) {
|
||||
Check.stateCondition(started, "Netty thread count can only be changed before the server starts!");
|
||||
MinecraftServer.nettyThreadCount = nettyThreadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the server should process netty errors and other unnecessary netty events.
|
||||
*
|
||||
* @return should process netty errors
|
||||
*/
|
||||
public static boolean shouldProcessNettyErrors() {
|
||||
return processNettyErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the server should process netty errors and other unnecessary netty events.
|
||||
* false is faster
|
||||
*
|
||||
* @param processNettyErrors should process netty errors
|
||||
*/
|
||||
public static void setShouldProcessNettyErrors(boolean processNettyErrors) {
|
||||
MinecraftServer.processNettyErrors = processNettyErrors;
|
||||
public static Server getServer() {
|
||||
checkInitStatus(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -744,8 +674,11 @@ public final class MinecraftServer {
|
||||
updateManager.start();
|
||||
|
||||
// Init & start the TCP server
|
||||
nettyServer.init();
|
||||
nettyServer.start(address, port);
|
||||
try {
|
||||
server.start(new InetSocketAddress(address, port));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (extensionManager.shouldLoadOnStartup()) {
|
||||
final long loadStartTime = System.nanoTime();
|
||||
@ -779,7 +712,7 @@ public final class MinecraftServer {
|
||||
updateManager.stop();
|
||||
schedulerManager.shutdown();
|
||||
connectionManager.shutdown();
|
||||
nettyServer.stop();
|
||||
server.stop();
|
||||
storageManager.getLoadedLocations().forEach(StorageLocation::close);
|
||||
LOGGER.info("Unloading all extensions.");
|
||||
extensionManager.shutdown();
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import net.minestom.server.command.builder.NodeMaker;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
@ -48,11 +47,10 @@ public class ArgumentString extends Argument<String> {
|
||||
*/
|
||||
@Deprecated
|
||||
public static String staticParse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
|
||||
// Return if not quoted
|
||||
if (!input.contains(String.valueOf(DOUBLE_QUOTE)) &&
|
||||
!input.contains(String.valueOf(QUOTE)) &&
|
||||
!input.contains(String.valueOf(StringUtil.SPACE))) {
|
||||
!input.contains(StringUtils.SPACE)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ public class OpenToLAN {
|
||||
* Performs the ping.
|
||||
*/
|
||||
private static void ping() {
|
||||
if (MinecraftServer.getNettyServer().getPort() != 0) {
|
||||
if (MinecraftServer.getServer().getPort() != 0) {
|
||||
if (packet == null || eventCooldown.isReady(System.currentTimeMillis())) {
|
||||
final ServerListPingEvent event = new ServerListPingEvent(OPEN_TO_LAN);
|
||||
EventDispatcher.call(event);
|
||||
|
@ -1,54 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
public class CipherBase {
|
||||
|
||||
private final Cipher cipher;
|
||||
private byte[] inTempArray = new byte[0];
|
||||
private byte[] outTempArray = new byte[0];
|
||||
|
||||
protected CipherBase(@NotNull Cipher cipher) {
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
private byte[] bufToByte(ByteBuf buffer) {
|
||||
int remainingBytes = buffer.readableBytes();
|
||||
|
||||
// Need to resize temp array
|
||||
if (inTempArray.length < remainingBytes) {
|
||||
inTempArray = new byte[remainingBytes];
|
||||
}
|
||||
|
||||
buffer.readBytes(inTempArray, 0, remainingBytes);
|
||||
return inTempArray;
|
||||
}
|
||||
|
||||
protected ByteBuf decrypt(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn) throws ShortBufferException {
|
||||
int remainingBytes = byteBufIn.readableBytes();
|
||||
byte[] bytes = bufToByte(byteBufIn);
|
||||
|
||||
ByteBuf outputBuffer = channelHandlerContext.alloc().heapBuffer(cipher.getOutputSize(remainingBytes));
|
||||
outputBuffer.writerIndex(cipher.update(bytes, 0, remainingBytes, outputBuffer.array(), outputBuffer.arrayOffset()));
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
protected void encrypt(ByteBuf byteBufIn, ByteBuf byteBufOut) throws ShortBufferException {
|
||||
int remainingBytes = byteBufIn.readableBytes();
|
||||
byte[] bytes = bufToByte(byteBufIn);
|
||||
int newSize = cipher.getOutputSize(remainingBytes);
|
||||
|
||||
// Need to resize temp array
|
||||
if (outTempArray.length < newSize) {
|
||||
outTempArray = new byte[newSize];
|
||||
}
|
||||
|
||||
byteBufOut.writeBytes(outTempArray, 0, cipher.update(bytes, 0, remainingBytes, outTempArray));
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.util.List;
|
||||
|
||||
public class Decrypter extends MessageToMessageDecoder<ByteBuf> {
|
||||
private final CipherBase cipher;
|
||||
|
||||
public Decrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
|
||||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
|
||||
list.add(this.cipher.decrypt(channelHandlerContext, byteBuf));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class Encrypter extends MessageToByteEncoder<ByteBuf> {
|
||||
private final CipherBase cipher;
|
||||
|
||||
public Encrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception {
|
||||
this.cipher.encrypt(byteBufIn, byteBufOut);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.extras.query;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@ -23,6 +21,7 @@ import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
@ -151,18 +150,18 @@ public class Query {
|
||||
}
|
||||
|
||||
// get the contents
|
||||
ByteBuf data = Unpooled.wrappedBuffer(packet.getData());
|
||||
ByteBuffer data = ByteBuffer.wrap(packet.getData());
|
||||
|
||||
// check the magic field
|
||||
if (data.readUnsignedShort() != 0xFEFD) {
|
||||
if ((data.getShort() & 0xFFFF) != 0xFEFD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// now check the query type
|
||||
byte type = data.readByte();
|
||||
byte type = data.get();
|
||||
|
||||
if (type == 9) { // handshake
|
||||
int sessionID = data.readInt();
|
||||
int sessionID = data.getInt();
|
||||
int challengeToken = RANDOM.nextInt();
|
||||
|
||||
CHALLENGE_TOKENS.put(challengeToken, packet.getSocketAddress());
|
||||
@ -184,12 +183,12 @@ public class Query {
|
||||
}
|
||||
}
|
||||
} else if (type == 0) { // stat
|
||||
int sessionID = data.readInt();
|
||||
int challengeToken = data.readInt();
|
||||
int sessionID = data.getInt();
|
||||
int challengeToken = data.getInt();
|
||||
SocketAddress sender = packet.getSocketAddress();
|
||||
|
||||
if (CHALLENGE_TOKENS.containsKey(challengeToken) && CHALLENGE_TOKENS.get(challengeToken).equals(sender)) {
|
||||
int remaining = data.readableBytes();
|
||||
int remaining = data.remaining();
|
||||
|
||||
if (remaining == 0) { // basic
|
||||
BasicQueryEvent event = new BasicQueryEvent(sender, sessionID);
|
||||
|
@ -142,7 +142,7 @@ public class BasicQueryResponse implements Writeable {
|
||||
writer.writeNullTerminatedString(this.map, Query.CHARSET);
|
||||
writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET);
|
||||
writer.writeNullTerminatedString(this.maxPlayers, Query.CHARSET);
|
||||
writer.getBuffer().writeShortLE(MinecraftServer.getNettyServer().getPort());
|
||||
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), ""), Query.CHARSET);
|
||||
writer.getBuffer().putShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
|
||||
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ public enum QueryKey {
|
||||
MAP(() -> "world"),
|
||||
NUM_PLAYERS("numplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size())),
|
||||
MAX_PLAYERS("maxplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size() + 1)),
|
||||
HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getNettyServer().getPort())),
|
||||
HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), "localhost"));
|
||||
HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getServer().getPort())),
|
||||
HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), "localhost"));
|
||||
|
||||
static QueryKey[] VALUES = QueryKey.values();
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.extras.velocity;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.PlayerSkin;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
@ -10,6 +9,7 @@ import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -54,9 +54,11 @@ public final class VelocityProxy {
|
||||
|
||||
final byte[] signature = reader.readBytes(32);
|
||||
|
||||
ByteBuf buf = reader.getBuffer();
|
||||
final byte[] data = new byte[buf.readableBytes()];
|
||||
buf.getBytes(buf.readerIndex(), data);
|
||||
ByteBuffer buf = reader.getBuffer();
|
||||
buf.mark();
|
||||
final byte[] data = new byte[buf.remaining()];
|
||||
buf.get(data);
|
||||
buf.reset();
|
||||
|
||||
try {
|
||||
final Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
@ -256,7 +256,7 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
List<byte[]> skyLights = new ArrayList<>();
|
||||
List<byte[]> blockLights = new ArrayList<>();
|
||||
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime());
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
|
||||
updateLightPacket.chunkX = getChunkX();
|
||||
updateLightPacket.chunkZ = getChunkZ();
|
||||
|
||||
@ -392,7 +392,5 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
*/
|
||||
protected void unload() {
|
||||
this.loaded = false;
|
||||
ChunkDataPacket.CACHE.invalidate(getIdentifier());
|
||||
UpdateLightPacket.CACHE.invalidate(getIdentifier());
|
||||
}
|
||||
}
|
@ -124,7 +124,7 @@ public class DynamicChunk extends Chunk {
|
||||
if (packet != null && cachedPacketTime == getLastChangeTime()) {
|
||||
return packet;
|
||||
}
|
||||
packet = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
|
||||
packet = new ChunkDataPacket();
|
||||
packet.biomes = biomes;
|
||||
packet.chunkX = chunkX;
|
||||
packet.chunkZ = chunkZ;
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.item;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.attribute.ItemAttribute;
|
||||
@ -13,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -36,7 +36,7 @@ public class ItemMeta implements TagReadable, Writeable {
|
||||
private final NBTCompound nbt;
|
||||
|
||||
private String cachedSNBT;
|
||||
private ByteBuf cachedBuffer;
|
||||
private ByteBuffer cachedBuffer;
|
||||
|
||||
protected ItemMeta(@NotNull ItemMetaBuilder metaBuilder) {
|
||||
this.damage = metaBuilder.damage;
|
||||
@ -154,6 +154,6 @@ public class ItemMeta implements TagReadable, Writeable {
|
||||
this.cachedBuffer = w.getBuffer();
|
||||
}
|
||||
writer.write(cachedBuffer);
|
||||
this.cachedBuffer.resetReaderIndex();
|
||||
this.cachedBuffer.position(0);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.network;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -24,6 +23,7 @@ import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@ -294,7 +294,7 @@ public final class ConnectionManager {
|
||||
// Close the player channel if he has been disconnected (kick)
|
||||
if (!player.isOnline()) {
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).getChannel().flush();
|
||||
((NettyPlayerConnection) playerConnection).flush();
|
||||
}
|
||||
//playerConnection.disconnect();
|
||||
return;
|
||||
@ -350,9 +350,12 @@ public final class ConnectionManager {
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
|
||||
final Channel channel = nettyPlayerConnection.getChannel();
|
||||
channel.writeAndFlush(disconnectPacket);
|
||||
channel.close();
|
||||
nettyPlayerConnection.writeAndFlush(disconnectPacket);
|
||||
try {
|
||||
nettyPlayerConnection.getChannel().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.players.clear();
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.network;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
@ -16,13 +14,9 @@ import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.Readable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Responsible for processing client packets.
|
||||
* <p>
|
||||
@ -34,11 +28,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* the same meaning as it is a login or play packet).
|
||||
*/
|
||||
public final class PacketProcessor {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class);
|
||||
|
||||
private final Map<ChannelHandlerContext, PlayerConnection> connectionPlayerConnectionMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Protocols state
|
||||
private final ClientStatusPacketsHandler statusPacketsHandler;
|
||||
private final ClientLoginPacketsHandler loginPacketsHandler;
|
||||
@ -50,33 +41,15 @@ public final class PacketProcessor {
|
||||
this.playPacketsHandler = new ClientPlayPacketsHandler();
|
||||
}
|
||||
|
||||
public void process(@NotNull ChannelHandlerContext context, @NotNull InboundPacket packet) {
|
||||
final SocketChannel socketChannel = (SocketChannel) context.channel();
|
||||
|
||||
// Create the netty player connection object if not existing
|
||||
PlayerConnection playerConnection = connectionPlayerConnectionMap.get(context);
|
||||
if (playerConnection == null) {
|
||||
// Should never happen
|
||||
context.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the client from sending packets when disconnected (kick)
|
||||
if (!playerConnection.isOnline() || !socketChannel.isActive()) {
|
||||
playerConnection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
public void process(@NotNull NettyPlayerConnection playerConnection, @NotNull InboundPacket packet) {
|
||||
// Increment packet count (checked in PlayerConnection#update)
|
||||
if (MinecraftServer.getRateLimit() > 0) {
|
||||
playerConnection.getPacketCounter().incrementAndGet();
|
||||
}
|
||||
|
||||
final ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
final int packetId = packet.getPacketId();
|
||||
BinaryReader binaryReader = new BinaryReader(packet.getBody());
|
||||
|
||||
final ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
if (connectionState == ConnectionState.UNKNOWN) {
|
||||
// Should be handshake packet
|
||||
if (packetId == 0) {
|
||||
@ -86,7 +59,6 @@ public final class PacketProcessor {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectionState) {
|
||||
case PLAY:
|
||||
final Player player = playerConnection.getPlayer();
|
||||
@ -108,34 +80,13 @@ public final class PacketProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a player connection from its channel.
|
||||
*
|
||||
* @param context the connection context
|
||||
* @return the connection of this channel, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public PlayerConnection getPlayerConnection(ChannelHandlerContext context) {
|
||||
return connectionPlayerConnectionMap.get(context);
|
||||
}
|
||||
|
||||
public void createPlayerConnection(@NotNull ChannelHandlerContext context) {
|
||||
final PlayerConnection playerConnection = new NettyPlayerConnection((SocketChannel) context.channel());
|
||||
connectionPlayerConnectionMap.put(context, playerConnection);
|
||||
}
|
||||
|
||||
public PlayerConnection removePlayerConnection(@NotNull ChannelHandlerContext context) {
|
||||
return connectionPlayerConnectionMap.remove(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler for client status packets.
|
||||
*
|
||||
* @return the status packets handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Status">Status packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientStatusPacketsHandler getStatusPacketsHandler() {
|
||||
public @NotNull ClientStatusPacketsHandler getStatusPacketsHandler() {
|
||||
return statusPacketsHandler;
|
||||
}
|
||||
|
||||
@ -145,8 +96,7 @@ public final class PacketProcessor {
|
||||
* @return the status login handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Login">Login packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientLoginPacketsHandler getLoginPacketsHandler() {
|
||||
public @NotNull ClientLoginPacketsHandler getLoginPacketsHandler() {
|
||||
return loginPacketsHandler;
|
||||
}
|
||||
|
||||
@ -156,8 +106,7 @@ public final class PacketProcessor {
|
||||
* @return the play packets handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Play">Play packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientPlayPacketsHandler getPlayPacketsHandler() {
|
||||
public @NotNull ClientPlayPacketsHandler getPlayPacketsHandler() {
|
||||
return playPacketsHandler;
|
||||
}
|
||||
|
||||
@ -170,12 +119,10 @@ public final class PacketProcessor {
|
||||
*/
|
||||
private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) {
|
||||
final int readableBytes = reader.available();
|
||||
|
||||
// Check if there is anything to read
|
||||
if (readableBytes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
readable.read(reader);
|
||||
} catch (Exception e) {
|
||||
|
@ -1,202 +0,0 @@
|
||||
package net.minestom.server.network.netty;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueue;
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.channel.ClientChannel;
|
||||
import net.minestom.server.network.netty.codec.*;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class NettyServer {
|
||||
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);
|
||||
public static final int BUFFER_SIZE = Integer.getInteger("minestom.channel-buffer-size", 65535);
|
||||
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
|
||||
1 << 21);
|
||||
|
||||
public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read
|
||||
|
||||
public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write
|
||||
public static final String DECRYPT_HANDLER_NAME = "decrypt"; // Read
|
||||
|
||||
public static final String GROUPED_PACKET_HANDLER_NAME = "grouped-packet"; // Write
|
||||
public static final String FRAMER_HANDLER_NAME = "framer"; // Read/write
|
||||
|
||||
public static final String COMPRESSOR_HANDLER_NAME = "compressor"; // Read/write
|
||||
|
||||
public static final String DECODER_HANDLER_NAME = "decoder"; // Read
|
||||
public static final String ENCODER_HANDLER_NAME = "encoder"; // Write
|
||||
public static final String CLIENT_CHANNEL_NAME = "handler"; // Read
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
private EventLoopGroup boss, worker;
|
||||
private ServerBootstrap bootstrap;
|
||||
|
||||
private ServerSocketChannel serverChannel;
|
||||
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public NettyServer(@NotNull PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the server by choosing which transport layer to use, number of threads, pipeline order, etc...
|
||||
* <p>
|
||||
* Called just before {@link #start(String, int)}.
|
||||
*/
|
||||
public void init() {
|
||||
Check.stateCondition(initialized, "Netty server has already been initialized!");
|
||||
this.initialized = true;
|
||||
|
||||
Class<? extends ServerChannel> channel;
|
||||
final int workerThreadCount = MinecraftServer.getNettyThreadCount();
|
||||
|
||||
// Find boss/worker event group
|
||||
{
|
||||
if (Epoll.isAvailable()) {
|
||||
boss = new EpollEventLoopGroup(2);
|
||||
worker = new EpollEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = EpollServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using epoll");
|
||||
} else if (KQueue.isAvailable()) {
|
||||
boss = new KQueueEventLoopGroup(2);
|
||||
worker = new KQueueEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = KQueueServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using kqueue");
|
||||
} else {
|
||||
boss = new NioEventLoopGroup(2);
|
||||
worker = new NioEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = NioServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using NIO");
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap = new ServerBootstrap()
|
||||
.group(boss, worker)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.channel(channel);
|
||||
|
||||
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
protected void initChannel(@NotNull SocketChannel ch) {
|
||||
ChannelConfig config = ch.config();
|
||||
config.setOption(ChannelOption.TCP_NODELAY, true);
|
||||
config.setOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
config.setOption(ChannelOption.SO_SNDBUF, BUFFER_SIZE);
|
||||
config.setAllocator(ByteBufAllocator.DEFAULT);
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// First check should verify if the packet is a legacy ping (from 1.6 version and earlier)
|
||||
// Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6)
|
||||
pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler());
|
||||
|
||||
// Used to bypass all the previous handlers by directly sending a framed buffer
|
||||
pipeline.addLast(GROUPED_PACKET_HANDLER_NAME, new GroupedPacketHandler());
|
||||
|
||||
// Adds packetLength at start | Reads framed buffer
|
||||
pipeline.addLast(FRAMER_HANDLER_NAME, new PacketFramer(packetProcessor));
|
||||
|
||||
// Reads buffer and create inbound packet
|
||||
pipeline.addLast(DECODER_HANDLER_NAME, new PacketDecoder());
|
||||
|
||||
// Writes packet to buffer
|
||||
pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder());
|
||||
|
||||
pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the address to start the server.
|
||||
*
|
||||
* @param address the server address
|
||||
* @param port the server port
|
||||
*/
|
||||
public void start(@NotNull String address, int port) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
|
||||
// Bind address
|
||||
try {
|
||||
ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync();
|
||||
|
||||
if (!cf.isSuccess()) {
|
||||
throw new IllegalStateException("Unable to bind server at " + address + ":" + port);
|
||||
}
|
||||
|
||||
this.serverChannel = (ServerSocketChannel) cf.channel();
|
||||
} catch (InterruptedException ex) {
|
||||
MinecraftServer.getExceptionManager().handleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public ServerSocketChannel getServerChannel() {
|
||||
return serverChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address of the server.
|
||||
*
|
||||
* @return the server address, null if the address isn't bound yet
|
||||
*/
|
||||
@Nullable
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port used by the server.
|
||||
*
|
||||
* @return the server port, 0 if the address isn't bound yet
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the server.
|
||||
*/
|
||||
public void stop() {
|
||||
try {
|
||||
this.boss.shutdownGracefully().sync();
|
||||
this.worker.shutdownGracefully().sync();
|
||||
this.serverChannel.closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package net.minestom.server.network.netty.channel;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(ClientChannel.class);
|
||||
|
||||
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public ClientChannel(@NotNull PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(@NotNull ChannelHandlerContext ctx) {
|
||||
//System.out.println("CONNECTION");
|
||||
packetProcessor.createPlayerConnection(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, InboundPacket packet) {
|
||||
try {
|
||||
packetProcessor.process(ctx, packet);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
} finally {
|
||||
// Check remaining
|
||||
final ByteBuf body = packet.getBody();
|
||||
final int packetId = packet.getPacketId();
|
||||
|
||||
final int availableBytes = body.readableBytes();
|
||||
|
||||
if (availableBytes > 0) {
|
||||
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||
|
||||
LOGGER.warn("WARNING: Packet 0x{} not fully read ({} bytes left), {}",
|
||||
Integer.toHexString(packetId),
|
||||
availableBytes,
|
||||
playerConnection);
|
||||
|
||||
body.skipBytes(availableBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(@NotNull ChannelHandlerContext ctx) {
|
||||
PlayerConnection playerConnection = packetProcessor.removePlayerConnection(ctx);
|
||||
if (playerConnection != null) {
|
||||
// Remove the connection
|
||||
playerConnection.refreshOnline(false);
|
||||
Player player = playerConnection.getPlayer();
|
||||
if (player != null) {
|
||||
player.remove();
|
||||
CONNECTION_MANAGER.removePlayer(playerConnection);
|
||||
}
|
||||
|
||||
// Release tick buffer
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).releaseTickBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (!ctx.channel().isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
MinecraftServer.getExceptionManager().handleException(cause);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
|
||||
public class GroupedPacketHandler extends MessageToByteEncoder<FramedPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FramedPacket msg, ByteBuf out) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, FramedPacket msg, boolean preferDirect) {
|
||||
return msg.getBody().retainedSlice();
|
||||
}
|
||||
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.ping.ServerListPingType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class LegacyPingHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private ByteBuf buf;
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object object) {
|
||||
final ByteBuf buf = (ByteBuf) object;
|
||||
|
||||
if (this.buf != null) {
|
||||
try {
|
||||
handle1_6(ctx, buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buf.markReaderIndex();
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
try {
|
||||
if (buf.readUnsignedByte() == 0xFE) {
|
||||
int length = buf.readableBytes();
|
||||
|
||||
switch (length) {
|
||||
case 0:
|
||||
if (trySendResponse(ServerListPingType.LEGACY_UNVERSIONED, ctx)) return;
|
||||
break;
|
||||
case 1:
|
||||
if (buf.readUnsignedByte() != 1) return;
|
||||
|
||||
if (trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx)) return;
|
||||
break;
|
||||
default:
|
||||
if (buf.readUnsignedByte() != 0x01 || buf.readUnsignedByte() != 0xFA) return;
|
||||
|
||||
handle1_6(ctx, buf);
|
||||
break;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
flag = false;
|
||||
}
|
||||
} finally {
|
||||
if (flag) {
|
||||
buf.resetReaderIndex();
|
||||
ctx.channel().pipeline().remove("legacy-ping");
|
||||
ctx.fireChannelRead(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle1_6(ChannelHandlerContext ctx, ByteBuf part) {
|
||||
ByteBuf buf = this.buf;
|
||||
|
||||
if (buf == null) {
|
||||
this.buf = buf = ctx.alloc().buffer();
|
||||
buf.markReaderIndex();
|
||||
} else {
|
||||
buf.resetReaderIndex();
|
||||
}
|
||||
|
||||
buf.writeBytes(part);
|
||||
|
||||
if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String s = readLegacyString(buf);
|
||||
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s.equals("MC|PingHost")) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int protocolVersion = buf.readByte();
|
||||
|
||||
if (readLegacyString(buf) == null) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.skipBytes(4); // port
|
||||
|
||||
if (buf.isReadable()) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
|
||||
this.buf = null;
|
||||
|
||||
trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx);
|
||||
}
|
||||
|
||||
private void removeHandler(ChannelHandlerContext ctx) {
|
||||
ByteBuf buf = this.buf;
|
||||
this.buf = null;
|
||||
|
||||
buf.resetReaderIndex();
|
||||
ctx.pipeline().remove(this);
|
||||
ctx.fireChannelRead(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
if (this.buf != null) {
|
||||
this.buf.release();
|
||||
this.buf = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a {@link ServerListPingEvent} and sends the response, if the event was not cancelled.
|
||||
*
|
||||
* @param version the version
|
||||
* @param ctx the context
|
||||
* @return {@code true} if the response was cancelled, {@code false} otherwise
|
||||
*/
|
||||
private static boolean trySendResponse(@NotNull ServerListPingType version, @NotNull ChannelHandlerContext ctx) {
|
||||
final ServerListPingEvent event = new ServerListPingEvent(version);
|
||||
EventDispatcher.call(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return true;
|
||||
} else {
|
||||
// get the response string
|
||||
String s = version.getPingResponse(event.getResponseData());
|
||||
|
||||
// create the buffer
|
||||
ByteBuf response = Unpooled.buffer();
|
||||
response.writeByte(255);
|
||||
|
||||
final char[] chars = s.toCharArray();
|
||||
|
||||
response.writeShort(chars.length);
|
||||
|
||||
for (char c : chars) {
|
||||
response.writeChar(c);
|
||||
}
|
||||
|
||||
// write the buffer
|
||||
ctx.pipeline().firstContext().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String readLegacyString(ByteBuf buf) {
|
||||
int size = buf.readShort() * Character.BYTES;
|
||||
if (!buf.isReadable(size)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE);
|
||||
buf.skipBytes(size);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (2020) [artem]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
private final static int MAX_SIZE = 2097152;
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final Deflater deflater = new Deflater();
|
||||
private final Inflater inflater = new Inflater();
|
||||
|
||||
public PacketCompressor(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
PacketUtils.compressBuffer(deflater, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.readableBytes() != 0) {
|
||||
final int claimedUncompressedSize = Utils.readVarInt(in);
|
||||
|
||||
if (claimedUncompressedSize == 0) {
|
||||
out.add(in.readRetainedSlice(in.readableBytes()));
|
||||
} else {
|
||||
if (claimedUncompressedSize < this.threshold) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold);
|
||||
}
|
||||
|
||||
if (claimedUncompressedSize > MAX_SIZE) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than protocol maximum of " + MAX_SIZE);
|
||||
}
|
||||
|
||||
// TODO optimize to do not initialize arrays each time
|
||||
|
||||
byte[] input = new byte[in.readableBytes()];
|
||||
in.readBytes(input);
|
||||
|
||||
inflater.setInput(input);
|
||||
byte[] output = new byte[claimedUncompressedSize];
|
||||
inflater.inflate(output);
|
||||
inflater.reset();
|
||||
|
||||
out.add(Unpooled.wrappedBuffer(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() > 0) {
|
||||
final int packetId = Utils.readVarInt(buf);
|
||||
list.add(new InboundPacket(packetId, buf));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
|
||||
public class PacketEncoder extends MessageToByteEncoder<ServerPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ServerPacket packet, ByteBuf buf) {
|
||||
PacketUtils.writePacket(buf, packet);
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
public final static Logger LOGGER = LoggerFactory.getLogger(PacketFramer.class);
|
||||
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public PacketFramer(PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
PacketUtils.frameBuffer(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) {
|
||||
buf.markReaderIndex();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!buf.isReadable()) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
final byte b = buf.readByte();
|
||||
|
||||
if (b >= 0) {
|
||||
buf.resetReaderIndex();
|
||||
|
||||
final int packetSize = Utils.readVarInt(buf);
|
||||
|
||||
// Max packet size check
|
||||
if (packetSize >= MinecraftServer.getMaxPacketSize()) {
|
||||
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||
if (playerConnection != null) {
|
||||
final String identifier = playerConnection.getIdentifier();
|
||||
LOGGER.warn("An user ({}) sent a packet over the maximum size ({})",
|
||||
identifier, packetSize);
|
||||
} else {
|
||||
LOGGER.warn("An unregistered user sent a packet over the maximum size ({})", packetSize);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
if (buf.readableBytes() < packetSize) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(buf.readRetainedSlice(packetSize));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CorruptedFrameException("length wider than 21-bit");
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a packet which is already framed. (packet id+payload) + optional compression
|
||||
* Can be used if you want to send the exact same buffer to multiple clients without processing it more than once.
|
||||
*/
|
||||
public class FramedPacket {
|
||||
|
||||
private final ByteBuf body;
|
||||
private final ByteBuffer body;
|
||||
|
||||
public FramedPacket(@NotNull ByteBuf body) {
|
||||
public FramedPacket(@NotNull ByteBuffer body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBody() {
|
||||
public @NotNull ByteBuffer getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class InboundPacket {
|
||||
|
||||
private final int packetId;
|
||||
private final ByteBuf body;
|
||||
private final ByteBuffer body;
|
||||
|
||||
public InboundPacket(int id, @NotNull ByteBuf body) {
|
||||
public InboundPacket(int id, @NotNull ByteBuffer body) {
|
||||
this.packetId = id;
|
||||
this.body = body;
|
||||
}
|
||||
@ -17,8 +17,7 @@ public class InboundPacket {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBody() {
|
||||
public @NotNull ByteBuffer getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.network.packet.server.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2LongRBTreeMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -15,22 +13,17 @@ import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES);
|
||||
public class ChunkDataPacket implements ServerPacket {
|
||||
|
||||
public Biome[] biomes;
|
||||
public int chunkX, chunkZ;
|
||||
@ -42,22 +35,13 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
private static final int MAX_BITS_PER_ENTRY = 16;
|
||||
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
|
||||
|
||||
// Cacheable data
|
||||
private final UUID identifier;
|
||||
private final long timestamp;
|
||||
/**
|
||||
* Heightmaps NBT, as read from raw packet data.
|
||||
* Only filled by #read, and unused at the moment.
|
||||
*/
|
||||
public NBTCompound heightmapsNBT;
|
||||
|
||||
private ChunkDataPacket() {
|
||||
this(new UUID(0, 0), 0);
|
||||
}
|
||||
|
||||
public ChunkDataPacket(@Nullable UUID identifier, long timestamp) {
|
||||
this.identifier = identifier;
|
||||
this.timestamp = timestamp;
|
||||
public ChunkDataPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +49,7 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
writer.writeInt(chunkX);
|
||||
writer.writeInt(chunkZ);
|
||||
|
||||
ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
|
||||
ByteBuffer blocks = ByteBuffer.allocate(MAX_BUFFER_SIZE);
|
||||
|
||||
Int2LongRBTreeMap maskMap = new Int2LongRBTreeMap();
|
||||
|
||||
@ -120,9 +104,8 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
}
|
||||
|
||||
// Data
|
||||
writer.writeVarInt(blocks.writerIndex());
|
||||
writer.write(blocks);
|
||||
blocks.release();
|
||||
writer.writeVarInt(blocks.position());
|
||||
writer.write(blocks.flip());
|
||||
|
||||
// Block entities
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
@ -244,19 +227,4 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.CHUNK_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
@ -4,19 +4,12 @@ import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES);
|
||||
public class UpdateLightPacket implements ServerPacket {
|
||||
|
||||
public int chunkX;
|
||||
public int chunkZ;
|
||||
@ -32,21 +25,10 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
public List<byte[]> skyLight = new ArrayList<>();
|
||||
public List<byte[]> blockLight = new ArrayList<>();
|
||||
|
||||
// Cacheable data
|
||||
private final UUID identifier;
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* Default constructor, required for reflection operations.
|
||||
* This one will make a packet that is not meant to be cached
|
||||
*/
|
||||
public UpdateLightPacket() {
|
||||
this(UUID.randomUUID(), Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public UpdateLightPacket(@Nullable UUID identifier, long timestamp) {
|
||||
this.identifier = identifier;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,19 +100,4 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.UPDATE_LIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,36 @@
|
||||
package net.minestom.server.network.player;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.adventure.MinestomAdventure;
|
||||
import net.minestom.server.entity.PlayerSkin;
|
||||
import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.network.netty.codec.PacketCompressor;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.utils.BufUtils;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.network.socket.Worker;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
* Represents a networking connection with Netty.
|
||||
@ -43,8 +43,8 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
private SocketAddress remoteAddress;
|
||||
|
||||
private boolean encrypted = false;
|
||||
private boolean compressed = false;
|
||||
private volatile boolean encrypted = false;
|
||||
private volatile boolean compressed = false;
|
||||
|
||||
//Could be null. Only used for Mojang Auth
|
||||
private byte[] nonce = new byte[4];
|
||||
@ -64,12 +64,89 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
private PlayerSkin bungeeSkin;
|
||||
|
||||
private final Object tickBufferLock = new Object();
|
||||
private volatile ByteBuf tickBuffer = BufUtils.direct();
|
||||
private final ByteBuffer tickBuffer = ByteBuffer.allocateDirect(Server.SOCKET_BUFFER_SIZE);
|
||||
private ByteBuffer cacheBuffer;
|
||||
|
||||
public NettyPlayerConnection(@NotNull SocketChannel channel) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
try {
|
||||
this.remoteAddress = channel.getRemoteAddress();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void processPackets(Worker.Context workerContext, PacketProcessor packetProcessor) {
|
||||
final var readBuffer = workerContext.readBuffer;
|
||||
final int limit = readBuffer.limit();
|
||||
// Read all packets
|
||||
while (readBuffer.remaining() > 0) {
|
||||
readBuffer.mark(); // Mark the beginning of the packet
|
||||
try {
|
||||
// Read packet
|
||||
final int packetLength = Utils.readVarInt(readBuffer);
|
||||
final int packetEnd = readBuffer.position() + packetLength;
|
||||
if (packetEnd > readBuffer.limit()) {
|
||||
// Integrity fail
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
|
||||
readBuffer.limit(packetEnd); // Ensure that the reader doesn't exceed packet bound
|
||||
|
||||
// Read protocol
|
||||
var content = workerContext.contentBuffer.clear();
|
||||
{
|
||||
if (!compressed) {
|
||||
// Compression disabled, payload is following
|
||||
content = readBuffer;
|
||||
} else {
|
||||
final int dataLength = Utils.readVarInt(readBuffer);
|
||||
if (dataLength == 0) {
|
||||
// Data is too small to be compressed, payload is following
|
||||
content = readBuffer;
|
||||
} else {
|
||||
// Decompress to content buffer
|
||||
try {
|
||||
final var inflater = workerContext.inflater;
|
||||
inflater.setInput(readBuffer);
|
||||
inflater.inflate(content);
|
||||
inflater.reset();
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
content.flip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process packet
|
||||
final int packetId = Utils.readVarInt(content);
|
||||
try {
|
||||
packetProcessor.process(this, new InboundPacket(packetId, content));
|
||||
} catch (Exception e) {
|
||||
// Error while reading the packet
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
// Return to original state (before writing)
|
||||
readBuffer.limit(limit).position(packetEnd);
|
||||
} catch (BufferUnderflowException e) {
|
||||
readBuffer.reset();
|
||||
this.cacheBuffer = ByteBuffer.allocateDirect(readBuffer.remaining());
|
||||
this.cacheBuffer.put(readBuffer).flip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void consumeCache(ByteBuffer buffer) {
|
||||
if (cacheBuffer == null) {
|
||||
return;
|
||||
}
|
||||
buffer.put(cacheBuffer);
|
||||
this.cacheBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,10 +158,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
||||
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
||||
this.encrypted = true;
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME,
|
||||
new Decrypter(MojangCrypt.getCipher(2, secretKey)));
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME,
|
||||
new Encrypter(MojangCrypt.getCipher(1, secretKey)));
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,11 +170,8 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
Check.stateCondition(compressed, "Compression is already enabled!");
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
|
||||
|
||||
this.compressed = true;
|
||||
writeAndFlush(new SetCompressionPacket(threshold));
|
||||
channel.pipeline().addAfter(NettyServer.FRAMER_HANDLER_NAME, NettyServer.COMPRESSOR_HANDLER_NAME,
|
||||
new PacketCompressor(threshold));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,21 +183,12 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*/
|
||||
@Override
|
||||
public void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating) {
|
||||
if (!channel.isActive())
|
||||
if (!channel.isConnected())
|
||||
return;
|
||||
|
||||
if (shouldSendPacket(serverPacket)) {
|
||||
if (getPlayer() != null) {
|
||||
// Flush happen during #update()
|
||||
if (serverPacket instanceof CacheablePacket && MinecraftServer.hasPacketCaching()) {
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
CacheablePacket.writeCache(tickBuffer, serverPacket);
|
||||
}
|
||||
} else {
|
||||
write(serverPacket, skipTranslating);
|
||||
}
|
||||
} else {
|
||||
// Player is probably not logged yet
|
||||
writeAndFlush(serverPacket);
|
||||
@ -141,93 +203,65 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
public void write(@NotNull Object message, boolean skipTranslating) {
|
||||
if (message instanceof FramedPacket) {
|
||||
final FramedPacket framedPacket = (FramedPacket) message;
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
final ByteBuf body = framedPacket.getBody();
|
||||
tickBuffer.writeBytes(body, body.readerIndex(), body.readableBytes());
|
||||
}
|
||||
attemptWrite(framedPacket.getBody());
|
||||
return;
|
||||
} else if (message instanceof ServerPacket) {
|
||||
ServerPacket serverPacket = (ServerPacket) message;
|
||||
|
||||
if ((MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && !skipTranslating) && getPlayer() != null && serverPacket instanceof ComponentHoldingServerPacket) {
|
||||
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component ->
|
||||
GlobalTranslator.render(component, Objects.requireNonNullElseGet(getPlayer().getLocale(), MinestomAdventure::getDefaultLocale)));
|
||||
}
|
||||
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
PacketUtils.writeFramedPacket(tickBuffer, serverPacket);
|
||||
}
|
||||
return;
|
||||
} else if (message instanceof ByteBuf) {
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
tickBuffer.writeBytes((ByteBuf) message);
|
||||
}
|
||||
} else if (message instanceof ByteBuffer) {
|
||||
attemptWrite((ByteBuffer) message);
|
||||
return;
|
||||
}
|
||||
throw new UnsupportedOperationException("type " + message.getClass() + " is not supported");
|
||||
}
|
||||
|
||||
public void writeAndFlush(@NotNull Object message) {
|
||||
writeWaitingPackets();
|
||||
ChannelFuture channelFuture = channel.writeAndFlush(message);
|
||||
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
channelFuture.addListener(future -> {
|
||||
if (!future.isSuccess() && channel.isActive()) {
|
||||
MinecraftServer.getExceptionManager().handleException(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
public void writeAndFlush(@NotNull ServerPacket packet) {
|
||||
attemptWrite(PacketUtils.createFramedPacket(packet));
|
||||
flush();
|
||||
}
|
||||
|
||||
public void writeWaitingPackets() {
|
||||
if (tickBuffer.writerIndex() == 0) {
|
||||
// Nothing to write
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve safe copy
|
||||
final ByteBuf copy;
|
||||
public void attemptWrite(ByteBuffer buffer) {
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
copy = tickBuffer;
|
||||
tickBuffer = tickBuffer.alloc().buffer(tickBuffer.writerIndex());
|
||||
try {
|
||||
this.tickBuffer.put(buffer);
|
||||
} catch (BufferOverflowException e) {
|
||||
try {
|
||||
this.channel.write(tickBuffer);
|
||||
this.channel.write(buffer);
|
||||
} catch (IOException ex) {
|
||||
MinecraftServer.getExceptionManager().handleException(ex);
|
||||
} finally {
|
||||
this.tickBuffer.clear();
|
||||
}
|
||||
|
||||
// Write copied buffer to netty
|
||||
ChannelFuture channelFuture = channel.write(new FramedPacket(copy));
|
||||
channelFuture.addListener(future -> copy.release());
|
||||
|
||||
// Netty debug
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
channelFuture.addListener(future -> {
|
||||
if (!future.isSuccess() && channel.isActive()) {
|
||||
MinecraftServer.getExceptionManager().handleException(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
final int bufferSize = tickBuffer.writerIndex();
|
||||
if (bufferSize > 0) {
|
||||
if (channel.isActive()) {
|
||||
writeWaitingPackets();
|
||||
channel.flush();
|
||||
if (tickBuffer.remaining() == 0) {
|
||||
// Nothing to write
|
||||
return;
|
||||
}
|
||||
// Retrieve safe copy
|
||||
synchronized (tickBufferLock) {
|
||||
try {
|
||||
channel.write(tickBuffer);
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
this.tickBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
public @NotNull SocketAddress getRemoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
@ -246,11 +280,14 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
@Override
|
||||
public void disconnect() {
|
||||
refreshOnline(false);
|
||||
try {
|
||||
this.channel.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Channel getChannel() {
|
||||
public @NotNull SocketChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@ -261,8 +298,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*
|
||||
* @return the username given by the client, unchecked
|
||||
*/
|
||||
@Nullable
|
||||
public String getLoginUsername() {
|
||||
public @Nullable String getLoginUsername() {
|
||||
return loginUsername;
|
||||
}
|
||||
|
||||
@ -323,9 +359,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public UUID getBungeeUuid() {
|
||||
public @Nullable UUID getBungeeUuid() {
|
||||
return bungeeUuid;
|
||||
}
|
||||
|
||||
@ -333,8 +367,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
this.bungeeUuid = bungeeUuid;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PlayerSkin getBungeeSkin() {
|
||||
public @Nullable PlayerSkin getBungeeSkin() {
|
||||
return bungeeSkin;
|
||||
}
|
||||
|
||||
@ -367,8 +400,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
* @param messageId the message id
|
||||
* @return the channel linked to the message id, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public String getPluginRequestChannel(int messageId) {
|
||||
public @Nullable String getPluginRequestChannel(int messageId) {
|
||||
return pluginRequestMap.get(messageId);
|
||||
}
|
||||
|
||||
@ -382,9 +414,6 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
public void releaseTickBuffer() {
|
||||
synchronized (tickBufferLock) {
|
||||
tickBuffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
|
@ -135,7 +135,7 @@ public abstract class PlayerConnection {
|
||||
* @return the server address used
|
||||
*/
|
||||
public @Nullable String getServerAddress() {
|
||||
return MinecraftServer.getNettyServer().getAddress();
|
||||
return MinecraftServer.getServer().getAddress();
|
||||
}
|
||||
|
||||
|
||||
@ -147,7 +147,7 @@ public abstract class PlayerConnection {
|
||||
* @return the server port used
|
||||
*/
|
||||
public int getServerPort() {
|
||||
return MinecraftServer.getNettyServer().getPort();
|
||||
return MinecraftServer.getServer().getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
|
97
src/main/java/net/minestom/server/network/socket/Server.java
Normal file
97
src/main/java/net/minestom/server/network/socket/Server.java
Normal file
@ -0,0 +1,97 @@
|
||||
package net.minestom.server.network.socket;
|
||||
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Server {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(Server.class);
|
||||
public static final int WORKER_COUNT = Integer.getInteger("minestom.workers",
|
||||
Runtime.getRuntime().availableProcessors() * 2);
|
||||
public static final int SOCKET_BUFFER_SIZE = Integer.getInteger("minestom.buffer-size", 262143);
|
||||
public static final int MAX_PACKET_SIZE = 2097151; // 3 bytes var-int
|
||||
public static final boolean NO_DELAY = true;
|
||||
|
||||
private volatile boolean stop;
|
||||
|
||||
private final List<Worker> workers = new ArrayList<>(WORKER_COUNT);
|
||||
private int index;
|
||||
|
||||
private ServerSocketChannel serverSocket;
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public Server(PacketProcessor packetProcessor) throws IOException {
|
||||
// Create all workers
|
||||
for (int i = 0; i < WORKER_COUNT; i++) {
|
||||
this.workers.add(new Worker(this, packetProcessor));
|
||||
}
|
||||
}
|
||||
|
||||
public void start(SocketAddress address) throws IOException {
|
||||
Selector selector = Selector.open();
|
||||
this.serverSocket = ServerSocketChannel.open();
|
||||
serverSocket.bind(address);
|
||||
serverSocket.configureBlocking(false);
|
||||
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
|
||||
serverSocket.socket().setReceiveBufferSize(SOCKET_BUFFER_SIZE);
|
||||
|
||||
LOGGER.info("Server starting, wait for connections");
|
||||
new Thread(() -> {
|
||||
while (!stop) {
|
||||
// Busy wait for connections
|
||||
try {
|
||||
serverTick(selector, serverSocket);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return !stop;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.stop = true;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
private void serverTick(Selector selector, ServerSocketChannel socketChannel) throws IOException {
|
||||
selector.select();
|
||||
Set<SelectionKey> selectedKeys = selector.selectedKeys();
|
||||
for (SelectionKey key : selectedKeys) {
|
||||
if (key.isAcceptable()) {
|
||||
// Register socket and forward to thread
|
||||
Worker thread = findWorker();
|
||||
final SocketChannel client = socketChannel.accept();
|
||||
thread.receiveConnection(client);
|
||||
LOGGER.info("new connection: " + client);
|
||||
}
|
||||
}
|
||||
selectedKeys.clear();
|
||||
}
|
||||
|
||||
private Worker findWorker() {
|
||||
this.index = ++index % WORKER_COUNT;
|
||||
return workers.get(index);
|
||||
}
|
||||
}
|
149
src/main/java/net/minestom/server/network/socket/Worker.java
Normal file
149
src/main/java/net/minestom/server/network/socket/Worker.java
Normal file
@ -0,0 +1,149 @@
|
||||
package net.minestom.server.network.socket;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
public class Worker {
|
||||
private final Map<SocketChannel, NettyPlayerConnection> connectionMap = new ConcurrentHashMap<>();
|
||||
private final Selector selector = Selector.open();
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public Worker(Server server, PacketProcessor packetProcessor) throws IOException {
|
||||
this.packetProcessor = packetProcessor;
|
||||
Thread.start(server, this::threadTick);
|
||||
}
|
||||
|
||||
private void threadTick(Context workerContext) {
|
||||
try {
|
||||
selector.select();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
Set<SelectionKey> selectedKeys = selector.selectedKeys();
|
||||
for (SelectionKey key : selectedKeys) {
|
||||
SocketChannel channel = (SocketChannel) key.channel();
|
||||
if (!channel.isOpen()) {
|
||||
continue;
|
||||
}
|
||||
if (!key.isReadable()) {
|
||||
// We only care about read
|
||||
continue;
|
||||
}
|
||||
var connection = connectionMap.get(channel);
|
||||
try {
|
||||
ByteBuffer readBuffer = workerContext.readBuffer;
|
||||
// Consume last incomplete packet
|
||||
connection.consumeCache(readBuffer);
|
||||
|
||||
// Read socket
|
||||
if (channel.read(readBuffer) == -1) {
|
||||
// EOS
|
||||
throw new IOException("Disconnected");
|
||||
}
|
||||
// Process data
|
||||
readBuffer.flip();
|
||||
connection.processPackets(workerContext, packetProcessor);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
disconnect(connection, channel);
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
workerContext.clearBuffers();
|
||||
}
|
||||
}
|
||||
selectedKeys.clear();
|
||||
}
|
||||
|
||||
public void receiveConnection(SocketChannel channel) throws IOException {
|
||||
var connection = new NettyPlayerConnection(channel);
|
||||
this.connectionMap.put(channel, connection);
|
||||
register(channel);
|
||||
this.selector.wakeup();
|
||||
}
|
||||
|
||||
private void register(SocketChannel channel) throws IOException {
|
||||
channel.configureBlocking(false);
|
||||
channel.register(selector, SelectionKey.OP_READ);
|
||||
var socket = channel.socket();
|
||||
socket.setSendBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setReceiveBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setTcpNoDelay(Server.NO_DELAY);
|
||||
}
|
||||
|
||||
private void disconnect(NettyPlayerConnection connection, SocketChannel channel) throws IOException {
|
||||
// Client close
|
||||
channel.close();
|
||||
connectionMap.remove(channel);
|
||||
// Remove the connection
|
||||
connection.refreshOnline(false);
|
||||
Player player = connection.getPlayer();
|
||||
if (player != null) {
|
||||
player.remove();
|
||||
MinecraftServer.getConnectionManager().removePlayer(connection);
|
||||
}
|
||||
}
|
||||
|
||||
static class Thread extends java.lang.Thread {
|
||||
private static final AtomicInteger COUNTER = new AtomicInteger();
|
||||
|
||||
private Thread(Runnable runnable) {
|
||||
super(null, runnable, "worker-" + COUNTER.getAndIncrement());
|
||||
}
|
||||
|
||||
protected static void start(Server server, Consumer<Context> runnable) {
|
||||
new Thread(() -> {
|
||||
Context workerContext = new Context();
|
||||
while (server.isOpen()) {
|
||||
try {
|
||||
runnable.accept(workerContext);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains objects that we can be shared across all the connection of a {@link Worker worker}.
|
||||
*/
|
||||
public static final class Context {
|
||||
public final ByteBuffer readBuffer = allocate(Server.SOCKET_BUFFER_SIZE);
|
||||
public final ByteBuffer writeBuffer = allocate(Server.SOCKET_BUFFER_SIZE);
|
||||
/**
|
||||
* Stores a single packet payload to be read.
|
||||
*/
|
||||
public final ByteBuffer contentBuffer = allocate(Server.MAX_PACKET_SIZE);
|
||||
public final Deflater deflater = new Deflater();
|
||||
public final Inflater inflater = new Inflater();
|
||||
|
||||
public void clearBuffers() {
|
||||
this.readBuffer.clear();
|
||||
this.writeBuffer.clear();
|
||||
}
|
||||
|
||||
private static ByteBuffer allocate(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
}
|
@ -75,7 +75,7 @@ public enum ServerListPingType {
|
||||
* @see OpenToLAN
|
||||
*/
|
||||
public static @NotNull String getOpenToLANPing(@NotNull ResponseData data) {
|
||||
return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getNettyServer().getPort());
|
||||
return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getServer().getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,13 +0,0 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
|
||||
public class BufUtils {
|
||||
|
||||
private static final PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
|
||||
|
||||
public static ByteBuf direct() {
|
||||
return alloc.ioBuffer();
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.ForwardingAudience;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -23,11 +22,10 @@ import java.util.Collection;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/**
|
||||
* Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuf}
|
||||
* Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuffer}
|
||||
* for network processing.
|
||||
*/
|
||||
public final class PacketUtils {
|
||||
|
||||
private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
||||
private static final ThreadLocal<Deflater> COMPRESSOR = ThreadLocal.withInitial(Deflater::new);
|
||||
|
||||
@ -91,9 +89,8 @@ public final class PacketUtils {
|
||||
// Send grouped packet...
|
||||
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
|
||||
if (success) {
|
||||
final ByteBuf finalBuffer = createFramedPacket(packet);
|
||||
final ByteBuffer finalBuffer = createFramedPacket(packet);
|
||||
final FramedPacket framedPacket = new FramedPacket(finalBuffer);
|
||||
|
||||
// Send packet to all players
|
||||
for (Player player : players) {
|
||||
if (!player.isOnline())
|
||||
@ -110,7 +107,6 @@ public final class PacketUtils {
|
||||
playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
finalBuffer.release(); // Release last reference
|
||||
}
|
||||
} else {
|
||||
// Write the same packet for each individual players
|
||||
@ -118,7 +114,6 @@ public final class PacketUtils {
|
||||
// Verify if the player should receive the packet
|
||||
if (playerValidator != null && !playerValidator.isValid(player))
|
||||
continue;
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
playerConnection.sendPacket(packet, false);
|
||||
}
|
||||
@ -136,23 +131,14 @@ public final class PacketUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link ServerPacket} into a {@link ByteBuf}.
|
||||
* Writes a {@link ServerPacket} into a {@link ByteBuffer}.
|
||||
*
|
||||
* @param buf the recipient of {@code packet}
|
||||
* @param packet the packet to write into {@code buf}
|
||||
*/
|
||||
public static void writePacket(@NotNull ByteBuf buf, @NotNull ServerPacket packet) {
|
||||
public static void writePacket(@NotNull ByteBuffer buf, @NotNull ServerPacket packet) {
|
||||
Utils.writeVarInt(buf, packet.getId());
|
||||
writePacketPayload(buf, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param packet the packet to write
|
||||
*/
|
||||
private static void writePacketPayload(@NotNull ByteBuf buffer, @NotNull ServerPacket packet) {
|
||||
BinaryWriter writer = new BinaryWriter(buffer);
|
||||
BinaryWriter writer = new BinaryWriter(buf);
|
||||
try {
|
||||
packet.write(writer);
|
||||
} catch (Exception e) {
|
||||
@ -160,96 +146,42 @@ public final class PacketUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a buffer for it to be understood by a Minecraft client.
|
||||
* <p>
|
||||
* The content of {@code packetBuffer} can be either a compressed or uncompressed packet buffer,
|
||||
* it depends of it the client did receive a {@link net.minestom.server.network.packet.server.login.SetCompressionPacket} packet before.
|
||||
*
|
||||
* @param packetBuffer the buffer containing compressed or uncompressed packet data
|
||||
* @param frameTarget the buffer which will receive the framed version of {@code from}
|
||||
*/
|
||||
public static void frameBuffer(@NotNull ByteBuf packetBuffer, @NotNull ByteBuf frameTarget) {
|
||||
final int packetSize = packetBuffer.readableBytes();
|
||||
final int headerSize = Utils.getVarIntSize(packetSize);
|
||||
if (headerSize > 3) {
|
||||
throw new IllegalStateException("Unable to fit " + headerSize + " into 3");
|
||||
}
|
||||
|
||||
frameTarget.ensureWritable(packetSize + headerSize);
|
||||
|
||||
Utils.writeVarInt(frameTarget, packetSize);
|
||||
frameTarget.writeBytes(packetBuffer, packetBuffer.readerIndex(), packetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress using zlib the content of a packet.
|
||||
* <p>
|
||||
* {@code packetBuffer} needs to be the packet content without any header (if you want to use it to write a Minecraft packet).
|
||||
*
|
||||
* @param deflater the deflater for zlib compression
|
||||
* @param packetBuffer the buffer containing all the packet fields
|
||||
* @param compressionTarget the buffer which will receive the compressed version of {@code packetBuffer}
|
||||
*/
|
||||
public static void compressBuffer(@NotNull Deflater deflater, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) {
|
||||
final int packetLength = packetBuffer.readableBytes();
|
||||
final boolean compression = packetLength > MinecraftServer.getCompressionThreshold();
|
||||
Utils.writeVarInt(compressionTarget, compression ? packetLength : 0);
|
||||
if (compression) {
|
||||
compress(deflater, packetBuffer, compressionTarget);
|
||||
} else {
|
||||
compressionTarget.writeBytes(packetBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void compress(@NotNull Deflater deflater, @NotNull ByteBuf uncompressed, @NotNull ByteBuf compressed) {
|
||||
deflater.setInput(uncompressed.nioBuffer());
|
||||
deflater.finish();
|
||||
|
||||
while (!deflater.finished()) {
|
||||
ByteBuffer nioBuffer = compressed.nioBuffer(compressed.writerIndex(), compressed.writableBytes());
|
||||
compressed.writerIndex(deflater.deflate(nioBuffer) + compressed.writerIndex());
|
||||
|
||||
if (compressed.writableBytes() == 0) {
|
||||
compressed.ensureWritable(8192);
|
||||
}
|
||||
}
|
||||
|
||||
deflater.reset();
|
||||
}
|
||||
|
||||
public static void writeFramedPacket(@NotNull ByteBuf buffer,
|
||||
public static void writeFramedPacket(@NotNull ByteBuffer buffer,
|
||||
@NotNull ServerPacket serverPacket) {
|
||||
final int compressionThreshold = MinecraftServer.getCompressionThreshold();
|
||||
|
||||
// Index of the var-int containing the complete packet length
|
||||
final int packetLengthIndex = Utils.writeEmpty3BytesVarInt(buffer);
|
||||
final int startIndex = buffer.writerIndex(); // Index where the content starts (after length)
|
||||
final int packetLengthIndex = Utils.writeEmptyVarIntHeader(buffer);
|
||||
final int startIndex = buffer.position(); // Index where the content starts (after length)
|
||||
if (compressionThreshold > 0) {
|
||||
// Index of the uncompressed payload length
|
||||
final int dataLengthIndex = Utils.writeEmpty3BytesVarInt(buffer);
|
||||
final int dataLengthIndex = Utils.writeEmptyVarIntHeader(buffer);
|
||||
|
||||
// Write packet
|
||||
final int contentIndex = buffer.writerIndex();
|
||||
final int contentIndex = buffer.position();
|
||||
writePacket(buffer, serverPacket);
|
||||
final int packetSize = buffer.writerIndex() - contentIndex;
|
||||
final int packetSize = buffer.position() - contentIndex;
|
||||
|
||||
final int uncompressedLength = packetSize >= compressionThreshold ? packetSize : 0;
|
||||
Utils.write3BytesVarInt(buffer, dataLengthIndex, uncompressedLength);
|
||||
Utils.writeVarIntHeader(buffer, dataLengthIndex, uncompressedLength);
|
||||
if (uncompressedLength > 0) {
|
||||
// Packet large enough, compress
|
||||
ByteBuf uncompressedCopy = buffer.copy(contentIndex, packetSize);
|
||||
buffer.writerIndex(contentIndex);
|
||||
compress(COMPRESSOR.get(), uncompressedCopy, buffer);
|
||||
uncompressedCopy.release();
|
||||
ByteBuffer uncompressedCopy = buffer.duplicate().position(contentIndex).limit(contentIndex + packetSize);
|
||||
buffer.position(contentIndex);
|
||||
|
||||
var deflater = COMPRESSOR.get();
|
||||
deflater.setInput(uncompressedCopy);
|
||||
deflater.finish();
|
||||
deflater.deflate(buffer);
|
||||
deflater.reset();
|
||||
}
|
||||
} else {
|
||||
// No compression, write packet id + payload
|
||||
writePacket(buffer, serverPacket);
|
||||
}
|
||||
// Total length
|
||||
final int totalPacketLength = buffer.writerIndex() - startIndex;
|
||||
Utils.write3BytesVarInt(buffer, packetLengthIndex, totalPacketLength);
|
||||
final int totalPacketLength = buffer.position() - startIndex;
|
||||
Utils.writeVarIntHeader(buffer, packetLengthIndex, totalPacketLength);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,8 +191,8 @@ public final class PacketUtils {
|
||||
* Can be used if you want to store a raw buffer and send it later without the additional writing cost.
|
||||
* Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0.
|
||||
*/
|
||||
public static @NotNull ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
|
||||
ByteBuf packetBuf = BufUtils.direct();
|
||||
public static @NotNull ByteBuffer createFramedPacket(@NotNull ServerPacket serverPacket) {
|
||||
ByteBuffer packetBuf = ByteBuffer.allocate(2_000_000);
|
||||
writeFramedPacket(packetBuf, serverPacket);
|
||||
return packetBuf;
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap;
|
||||
import net.minestom.server.instance.palette.Palette;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class Utils {
|
||||
|
||||
private Utils() {
|
||||
@ -21,43 +23,46 @@ public final class Utils {
|
||||
? 4 : 5;
|
||||
}
|
||||
|
||||
public static void writeVarInt(@NotNull ByteBuf buf, int value) {
|
||||
// Took from velocity
|
||||
public static void writeVarInt(ByteBuffer buf, int value) {
|
||||
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||
buf.writeByte(value);
|
||||
buf.put((byte) value);
|
||||
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
|
||||
buf.writeShort(w);
|
||||
buf.putShort((short) ((value & 0x7F | 0x80) << 8 | (value >>> 7)));
|
||||
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
|
||||
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
buf.writeMedium(w);
|
||||
buf.put((byte) (value & 0x7F | 0x80));
|
||||
buf.put((byte) ((value >>> 7) & 0x7F | 0x80));
|
||||
buf.put((byte) (value >>> 14));
|
||||
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
|
||||
buf.putInt((value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21));
|
||||
} else {
|
||||
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
|
||||
buf.writeInt(w);
|
||||
buf.writeByte(value >>> 28);
|
||||
buf.putInt((value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80));
|
||||
buf.put((byte) (value >>> 28));
|
||||
}
|
||||
}
|
||||
|
||||
public static void write3BytesVarInt(@NotNull ByteBuf buffer, int startIndex, int value) {
|
||||
final int indexCache = buffer.writerIndex();
|
||||
buffer.writerIndex(startIndex);
|
||||
final int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
buffer.writeMedium(w);
|
||||
buffer.writerIndex(indexCache);
|
||||
public static void writeVarIntHeader(@NotNull ByteBuffer buffer, int startIndex, int value) {
|
||||
final int indexCache = buffer.position();
|
||||
buffer.position(startIndex);
|
||||
buffer.put((byte) (value & 0x7F | 0x80));
|
||||
buffer.put((byte) ((value >>> 7) & 0x7F | 0x80));
|
||||
buffer.put((byte) (value >>> 14));
|
||||
buffer.position(indexCache);
|
||||
}
|
||||
|
||||
public static int writeEmpty3BytesVarInt(@NotNull ByteBuf buffer) {
|
||||
final int index = buffer.writerIndex();
|
||||
buffer.writeMedium(0);
|
||||
public static int writeEmptyVarIntHeader(@NotNull ByteBuffer buffer) {
|
||||
final int index = buffer.position();
|
||||
buffer.putShort((short) 0);
|
||||
buffer.put((byte) 0);
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
public static int readVarInt(ByteBuffer buf) {
|
||||
int i = 0;
|
||||
final int maxRead = Math.min(5, buf.readableBytes());
|
||||
final int maxRead = Math.min(5, buf.remaining());
|
||||
for (int j = 0; j < maxRead; j++) {
|
||||
final int k = buf.readByte();
|
||||
final int k = buf.get();
|
||||
i |= (k & 0x7F) << j * 7;
|
||||
if ((k & 0x80) != 128) {
|
||||
return i;
|
||||
@ -66,12 +71,12 @@ public final class Utils {
|
||||
throw new RuntimeException("VarInt is too big");
|
||||
}
|
||||
|
||||
public static long readVarLong(@NotNull ByteBuf buffer) {
|
||||
public static long readVarLong(@NotNull ByteBuffer buffer) {
|
||||
int numRead = 0;
|
||||
long result = 0;
|
||||
byte read;
|
||||
do {
|
||||
read = buffer.readByte();
|
||||
read = buffer.get();
|
||||
long value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
@ -117,13 +122,12 @@ public final class Utils {
|
||||
return new UUID(uuidMost, uuidLeast);
|
||||
}
|
||||
|
||||
public static void writePaletteBlocks(ByteBuf buffer, Palette palette) {
|
||||
|
||||
public static void writePaletteBlocks(ByteBuffer buffer, Palette palette) {
|
||||
final short blockCount = palette.getBlockCount();
|
||||
final int bitsPerEntry = palette.getBitsPerEntry();
|
||||
|
||||
buffer.writeShort(blockCount);
|
||||
buffer.writeByte((byte) bitsPerEntry);
|
||||
buffer.putShort(blockCount);
|
||||
buffer.put((byte) bitsPerEntry);
|
||||
|
||||
// Palette
|
||||
if (bitsPerEntry < 9) {
|
||||
@ -138,7 +142,7 @@ public final class Utils {
|
||||
final long[] blocks = palette.getBlocks();
|
||||
writeVarInt(buffer, blocks.length);
|
||||
for (long datum : blocks) {
|
||||
buffer.writeLong(datum);
|
||||
buffer.putLong(datum);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
@ -17,6 +15,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
@ -27,16 +26,15 @@ import java.util.function.Supplier;
|
||||
* WARNING: not thread-safe.
|
||||
*/
|
||||
public class BinaryReader extends InputStream {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final ByteBuffer buffer;
|
||||
private final NBTReader nbtReader = new NBTReader(this, false);
|
||||
|
||||
public BinaryReader(@NotNull ByteBuf buffer) {
|
||||
public BinaryReader(@NotNull ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public BinaryReader(byte[] bytes) {
|
||||
this(Unpooled.wrappedBuffer(bytes));
|
||||
this(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
public int readVarInt() {
|
||||
@ -48,49 +46,49 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public boolean readBoolean() {
|
||||
return buffer.readBoolean();
|
||||
return buffer.get() == 1;
|
||||
}
|
||||
|
||||
public byte readByte() {
|
||||
return buffer.readByte();
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
public short readShort() {
|
||||
return buffer.readShort();
|
||||
return buffer.getShort();
|
||||
}
|
||||
|
||||
public char readChar() {
|
||||
return buffer.readChar();
|
||||
return buffer.getChar();
|
||||
}
|
||||
|
||||
public int readUnsignedShort() {
|
||||
return buffer.readUnsignedShort();
|
||||
return buffer.getShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as readInt
|
||||
*/
|
||||
public int readInteger() {
|
||||
return buffer.readInt();
|
||||
return buffer.getInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as readInteger, created for parity with BinaryWriter
|
||||
*/
|
||||
public int readInt() {
|
||||
return buffer.readInt();
|
||||
return buffer.getInt();
|
||||
}
|
||||
|
||||
public long readLong() {
|
||||
return buffer.readLong();
|
||||
return buffer.getLong();
|
||||
}
|
||||
|
||||
public float readFloat() {
|
||||
return buffer.readFloat();
|
||||
return buffer.getFloat();
|
||||
}
|
||||
|
||||
public double readDouble() {
|
||||
return buffer.readDouble();
|
||||
return buffer.getDouble();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,12 +103,9 @@ public class BinaryReader extends InputStream {
|
||||
*/
|
||||
public String readSizedString(int maxLength) {
|
||||
final int length = readVarInt();
|
||||
Check.stateCondition(!buffer.isReadable(length),
|
||||
"Trying to read a string that is too long (wanted {0}, only have {1})",
|
||||
length,
|
||||
buffer.readableBytes());
|
||||
final String str = buffer.toString(buffer.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buffer.skipBytes(length);
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
final String str = new String(bytes, StandardCharsets.UTF_8);
|
||||
Check.stateCondition(str.length() > maxLength,
|
||||
"String length ({0}) was higher than the max length of {1}", length, maxLength);
|
||||
return str;
|
||||
@ -121,10 +116,8 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public byte[] readBytes(int length) {
|
||||
ByteBuf buf = buffer.readBytes(length);
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
buf.release();
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@ -164,14 +157,11 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public Point readBlockPosition() {
|
||||
final long value = buffer.readLong();
|
||||
return SerializerUtils.longToBlockPosition(value);
|
||||
return SerializerUtils.longToBlockPosition(buffer.getLong());
|
||||
}
|
||||
|
||||
public UUID readUuid() {
|
||||
final long most = readLong();
|
||||
final long least = readLong();
|
||||
return new UUID(most, least);
|
||||
return new UUID(readLong(), readLong());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,7 +215,7 @@ public class BinaryReader extends InputStream {
|
||||
return (T[]) result;
|
||||
}
|
||||
|
||||
public ByteBuf getBuffer() {
|
||||
public ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -236,7 +226,7 @@ public class BinaryReader extends InputStream {
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return buffer.readableBytes();
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
public NBT readTag() throws IOException, NBTException {
|
||||
@ -251,11 +241,12 @@ public class BinaryReader extends InputStream {
|
||||
* @param extractor the extraction code, simply call the reader's read* methods here.
|
||||
*/
|
||||
public byte[] extractBytes(Runnable extractor) {
|
||||
int startingPosition = getBuffer().readerIndex();
|
||||
int startingPosition = buffer.position();
|
||||
extractor.run();
|
||||
int endingPosition = getBuffer().readerIndex();
|
||||
int endingPosition = getBuffer().position();
|
||||
byte[] output = new byte[endingPosition - startingPosition];
|
||||
getBuffer().getBytes(startingPosition, output);
|
||||
buffer.get(output, 0, output.length);
|
||||
//buffer.get(startingPosition, output);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -16,8 +13,11 @@ import org.jglrxavpok.hephaistos.nbt.NBTWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -26,8 +26,7 @@ import java.util.function.Consumer;
|
||||
* WARNING: not thread-safe.
|
||||
*/
|
||||
public class BinaryWriter extends OutputStream {
|
||||
|
||||
private ByteBuf buffer;
|
||||
private ByteBuffer buffer;
|
||||
private NBTWriter nbtWriter; // Lazily initialized
|
||||
|
||||
/**
|
||||
@ -36,7 +35,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param initialCapacity the initial capacity of the binary writer
|
||||
*/
|
||||
public BinaryWriter(int initialCapacity) {
|
||||
this.buffer = Unpooled.buffer(initialCapacity);
|
||||
this.buffer = ByteBuffer.allocate(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,24 +43,15 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param buffer the writer buffer
|
||||
*/
|
||||
public BinaryWriter(@NotNull ByteBuf buffer) {
|
||||
public BinaryWriter(@NotNull ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BinaryWriter} from multiple buffers.
|
||||
*
|
||||
* @param buffers the buffers making this
|
||||
*/
|
||||
public BinaryWriter(@NotNull ByteBuf... buffers) {
|
||||
this.buffer = Unpooled.wrappedBuffer(buffers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BinaryWriter} with a "reasonably small initial capacity".
|
||||
*/
|
||||
public BinaryWriter() {
|
||||
this.buffer = Unpooled.buffer();
|
||||
this(500); // TODO prevent OOB
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +69,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param b the boolean to write
|
||||
*/
|
||||
public void writeBoolean(boolean b) {
|
||||
buffer.writeBoolean(b);
|
||||
buffer.put((byte) (b ? 1 : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +78,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param b the byte to write
|
||||
*/
|
||||
public void writeByte(byte b) {
|
||||
buffer.writeByte(b);
|
||||
buffer.put(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +87,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param c the char to write
|
||||
*/
|
||||
public void writeChar(char c) {
|
||||
buffer.writeChar(c);
|
||||
buffer.putChar(c);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +96,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param s the short to write
|
||||
*/
|
||||
public void writeShort(short s) {
|
||||
buffer.writeShort(s);
|
||||
buffer.putShort(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,7 +105,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param i the int to write
|
||||
*/
|
||||
public void writeInt(int i) {
|
||||
buffer.writeInt(i);
|
||||
buffer.putInt(i);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +114,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param l the long to write
|
||||
*/
|
||||
public void writeLong(long l) {
|
||||
buffer.writeLong(l);
|
||||
buffer.putLong(l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +123,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param f the float to write
|
||||
*/
|
||||
public void writeFloat(float f) {
|
||||
buffer.writeFloat(f);
|
||||
buffer.putFloat(f);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,7 +132,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param d the double to write
|
||||
*/
|
||||
public void writeDouble(double d) {
|
||||
buffer.writeDouble(d);
|
||||
buffer.putDouble(d);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,9 +161,9 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param string the string to write
|
||||
*/
|
||||
public void writeSizedString(@NotNull String string) {
|
||||
final int utf8Bytes = ByteBufUtil.utf8Bytes(string);
|
||||
writeVarInt(utf8Bytes);
|
||||
buffer.writeCharSequence(string, StandardCharsets.UTF_8);
|
||||
final var bytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(bytes.length);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +174,8 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param charset the charset to encode in
|
||||
*/
|
||||
public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
|
||||
buffer.writeCharSequence(string + '\0', charset);
|
||||
final var bytes = (string + '\0').getBytes(charset);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,8 +214,8 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param bytes the byte array to write
|
||||
*/
|
||||
public void writeBytes(@NotNull byte[] bytes) {
|
||||
buffer.writeBytes(bytes);
|
||||
public void writeBytes(byte @NotNull [] bytes) {
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,12 +287,12 @@ public class BinaryWriter extends OutputStream {
|
||||
writeable.write(this);
|
||||
}
|
||||
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
this.buffer.writeBytes(writer.getBuffer());
|
||||
public void write(@NotNull ByteBuffer buffer) {
|
||||
this.buffer.put(buffer);
|
||||
}
|
||||
|
||||
public void write(@NotNull ByteBuf buffer) {
|
||||
this.buffer.writeBytes(buffer);
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
write(writer.getBuffer());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,46 +314,51 @@ public class BinaryWriter extends OutputStream {
|
||||
* @return the byte array containing all the {@link BinaryWriter} data
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
byte[] bytes = new byte[buffer.readableBytes()];
|
||||
final int readerIndex = buffer.readerIndex();
|
||||
buffer.getBytes(readerIndex, bytes);
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuf} at the beginning of this writer.
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the beginning of this writer.
|
||||
*
|
||||
* @param headerWriter the {@link BinaryWriter} to add at the beginning
|
||||
*/
|
||||
public void writeAtStart(@NotNull BinaryWriter headerWriter) {
|
||||
// Get the buffer of the header
|
||||
final ByteBuf headerBuf = headerWriter.getBuffer();
|
||||
final var headerBuf = headerWriter.getBuffer();
|
||||
// Merge both the headerBuf and this buffer
|
||||
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(headerBuf, buffer);
|
||||
final var finalBuffer = concat(headerBuf, buffer);
|
||||
// Change the buffer used by this writer
|
||||
setBuffer(finalBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuf} at the end of this writer.
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the end of this writer.
|
||||
*
|
||||
* @param footerWriter the {@link BinaryWriter} to add at the end
|
||||
*/
|
||||
public void writeAtEnd(@NotNull BinaryWriter footerWriter) {
|
||||
// Get the buffer of the footer
|
||||
final ByteBuf footerBuf = footerWriter.getBuffer();
|
||||
final var footerBuf = footerWriter.getBuffer();
|
||||
// Merge both this buffer and the footerBuf
|
||||
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(buffer, footerBuf);
|
||||
final var finalBuffer = concat(buffer, footerBuf);
|
||||
// Change the buffer used by this writer
|
||||
setBuffer(finalBuffer);
|
||||
}
|
||||
|
||||
public static ByteBuffer concat(final ByteBuffer... buffers) {
|
||||
final ByteBuffer combined = ByteBuffer.allocate(Arrays.stream(buffers).mapToInt(Buffer::remaining).sum());
|
||||
Arrays.stream(buffers).forEach(b -> combined.put(b.duplicate()));
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw buffer used by this binary writer.
|
||||
*
|
||||
* @return the raw buffer
|
||||
*/
|
||||
public @NotNull ByteBuf getBuffer() {
|
||||
public @NotNull ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -371,7 +367,7 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param buffer the new buffer used by this binary writer
|
||||
*/
|
||||
public void setBuffer(ByteBuf buffer) {
|
||||
public void setBuffer(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@ -381,7 +377,8 @@ public class BinaryWriter extends OutputStream {
|
||||
}
|
||||
|
||||
public void writeUnsignedShort(int yourShort) {
|
||||
buffer.writeShort(yourShort & 0xFFFF);
|
||||
// FIXME unsigned
|
||||
buffer.putShort((short) (yourShort & 0xFFFF));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,91 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implemented by {@link ServerPacket server packets} which can be temporary cached in memory to be re-sent later
|
||||
* without having to go through all the writing and compression.
|
||||
* <p>
|
||||
* {@link #getIdentifier()} is to differentiate this packet from the others of the same type.
|
||||
*/
|
||||
public interface CacheablePacket {
|
||||
|
||||
/**
|
||||
* Gets the cache linked to this packet.
|
||||
* <p>
|
||||
* WARNING: the cache needs to be shared between all the object instances, tips is to make it static.
|
||||
*
|
||||
* @return the temporary packet cache
|
||||
*/
|
||||
@NotNull TemporaryPacketCache getCache();
|
||||
|
||||
/**
|
||||
* Gets the identifier of this packet.
|
||||
* <p>
|
||||
* Used to verify if this packet is already cached or not.
|
||||
*
|
||||
* @return this packet identifier, null to prevent caching
|
||||
*/
|
||||
@Nullable UUID getIdentifier();
|
||||
|
||||
/**
|
||||
* Gets the last time this packet changed.
|
||||
*
|
||||
* @return the last packet update time in milliseconds
|
||||
*/
|
||||
long getTimestamp();
|
||||
|
||||
static @Nullable FramedPacket getCache(@NotNull ServerPacket serverPacket) {
|
||||
if (!(serverPacket instanceof CacheablePacket))
|
||||
return null;
|
||||
|
||||
final CacheablePacket cacheablePacket = (CacheablePacket) serverPacket;
|
||||
final UUID identifier = cacheablePacket.getIdentifier();
|
||||
if (identifier == null) {
|
||||
// This packet explicitly asks to do not retrieve the cache
|
||||
return null;
|
||||
} else {
|
||||
final long timestamp = cacheablePacket.getTimestamp();
|
||||
// Try to retrieve the cached buffer
|
||||
TemporaryCache<TimedBuffer> temporaryCache = cacheablePacket.getCache();
|
||||
TimedBuffer timedBuffer = temporaryCache.retrieve(identifier);
|
||||
|
||||
// Update the buffer if non-existent or outdated
|
||||
final boolean shouldUpdate = timedBuffer == null ||
|
||||
timestamp > timedBuffer.getTimestamp();
|
||||
|
||||
if (shouldUpdate) {
|
||||
// Buffer freed by guava cache #removalListener
|
||||
final ByteBuf buffer = PacketUtils.createFramedPacket(serverPacket);
|
||||
timedBuffer = new TimedBuffer(buffer, timestamp);
|
||||
temporaryCache.cache(identifier, timedBuffer);
|
||||
}
|
||||
|
||||
return new FramedPacket(timedBuffer.getBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
static void writeCache(@NotNull ByteBuf buffer, @NotNull ServerPacket serverPacket) {
|
||||
FramedPacket framedPacket = CacheablePacket.getCache(serverPacket);
|
||||
if (framedPacket == null) {
|
||||
PacketUtils.writeFramedPacket(buffer, serverPacket);
|
||||
return;
|
||||
}
|
||||
final ByteBuf body = framedPacket.getBody();
|
||||
synchronized (body) {
|
||||
if (framedPacket.getBody().refCnt() != 0) {
|
||||
buffer.writeBytes(body, body.readerIndex(), body.readableBytes());
|
||||
} else {
|
||||
PacketUtils.writeFramedPacket(buffer, serverPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Cache objects with a timeout.
|
||||
*
|
||||
* @param <T> the object type to cache
|
||||
*/
|
||||
public class TemporaryCache<T> {
|
||||
|
||||
private final Cache<UUID, T> cache;
|
||||
|
||||
/**
|
||||
* Creates a new temporary cache.
|
||||
*
|
||||
* @param duration the time before considering an object unused
|
||||
*/
|
||||
public TemporaryCache(long duration, TimeUnit timeUnit, RemovalListener<UUID, T> removalListener) {
|
||||
this.cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(duration, timeUnit)
|
||||
.removalListener(removalListener)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches an object.
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @param value the object to cache
|
||||
*/
|
||||
public void cache(@NotNull UUID identifier, T value) {
|
||||
this.cache.put(identifier, value);
|
||||
}
|
||||
|
||||
public void invalidate(@NotNull UUID identifier) {
|
||||
this.cache.invalidate(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an object from cache.
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @return the retrieved object or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public T retrieve(@NotNull UUID identifier) {
|
||||
return cache.getIfPresent(identifier);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TemporaryPacketCache extends TemporaryCache<TimedBuffer> {
|
||||
public TemporaryPacketCache(long duration, TimeUnit timeUnit) {
|
||||
super(duration, timeUnit, (key, value, cause) -> {
|
||||
if (value == null)
|
||||
return;
|
||||
final ByteBuf buffer = value.getBuffer();
|
||||
synchronized (buffer) {
|
||||
buffer.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Object containing a {@link ByteBuf buffer} and its timestamp.
|
||||
* Used for packet-caching to use the most recent.
|
||||
*/
|
||||
public class TimedBuffer {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final long timestamp;
|
||||
|
||||
public TimedBuffer(@NotNull ByteBuf buffer, long timestamp) {
|
||||
this.buffer = buffer;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
MinecraftServer minecraftServer = MinecraftServer.init();
|
||||
MinecraftServer.setCompressionThreshold(0);
|
||||
|
||||
BlockManager blockManager = MinecraftServer.getBlockManager();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user