diff --git a/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java b/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java
index 144c30064..fd4e2d512 100644
--- a/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java
+++ b/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java
@@ -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().putShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
+ writer.writeShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
}
}
diff --git a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java
index c0a2e3c7e..53e61842c 100644
--- a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java
+++ b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java
@@ -1,19 +1,17 @@
package net.minestom.server.extras.velocity;
-import net.minestom.server.MinecraftServer;
-import net.minestom.server.utils.binary.BinaryReader;
+import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
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.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import static net.minestom.server.network.NetworkBuffer.*;
+
/**
* Support for Velocity modern forwarding.
*
@@ -47,12 +45,14 @@ public final class VelocityProxy {
return enabled;
}
- public static boolean checkIntegrity(@NotNull BinaryReader reader) {
- final byte[] signature = reader.readBytes(32);
- ByteBuffer buf = reader.getBuffer();
- buf.mark();
- final byte[] data = reader.readRemainingBytes();
- buf.reset();
+ public static boolean checkIntegrity(NetworkBuffer buffer) {
+ final byte[] signature = new byte[32];
+ for (int i = 0; i < signature.length; i++) {
+ signature[i] = buffer.read(BYTE);
+ }
+ final int index = buffer.readIndex();
+ final byte[] data = buffer.read(RAW_BYTES);
+ buffer.readIndex(index);
try {
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(key);
@@ -63,16 +63,7 @@ public final class VelocityProxy {
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
- final int version = reader.readVarInt();
+ final int version = buffer.read(VAR_INT);
return version == SUPPORTED_FORWARDING_VERSION;
}
-
- public static InetAddress readAddress(@NotNull BinaryReader reader) {
- try {
- return InetAddress.getByName(reader.readSizedString());
- } catch (UnknownHostException e) {
- MinecraftServer.getExceptionManager().handleException(e);
- return null;
- }
- }
}
diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java
index 6b7230f3d..95a62ec9d 100644
--- a/src/main/java/net/minestom/server/instance/DynamicChunk.java
+++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java
@@ -20,7 +20,6 @@ import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.ObjectPool;
-import net.minestom.server.utils.Utils;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
@@ -200,8 +199,8 @@ public class DynamicChunk extends Chunk {
}
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
heightmapsNBT = NBT.Compound(Map.of(
- "MOTION_BLOCKING", NBT.LongArray(Utils.encodeBlocks(motionBlocking, bitsForHeight)),
- "WORLD_SURFACE", NBT.LongArray(Utils.encodeBlocks(worldSurface, bitsForHeight))));
+ "MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
+ "WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
}
// Data
final byte[] data = ObjectPool.PACKET_POOL.use(buffer -> {
@@ -265,4 +264,47 @@ public class DynamicChunk extends Chunk {
private void assertLock() {
assert Thread.holdsLock(this) : "Chunk must be locked before access";
}
+
+ private static final int[] MAGIC = {
+ -1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
+ 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
+ 0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
+ 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
+ 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135,
+ 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
+ 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
+ 178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
+ 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
+ 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
+ 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
+ 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
+ 104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
+ 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
+ 0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
+ 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
+ 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
+ 0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
+ 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
+ 0, 5};
+
+ private static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
+ final long maxEntryValue = (1L << bitsPerEntry) - 1;
+ final char valuesPerLong = (char) (64 / bitsPerEntry);
+ final int magicIndex = 3 * (valuesPerLong - 1);
+ final long divideMul = Integer.toUnsignedLong(MAGIC[magicIndex]);
+ final long divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1]);
+ final int divideShift = MAGIC[magicIndex + 2];
+ final int size = (blocks.length + valuesPerLong - 1) / valuesPerLong;
+
+ long[] data = new long[size];
+
+ for (int i = 0; i < blocks.length; i++) {
+ final long value = blocks[i];
+ final int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
+ final int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
+ data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
+ }
+
+ return data;
+ }
}
diff --git a/src/main/java/net/minestom/server/item/ItemMetaImpl.java b/src/main/java/net/minestom/server/item/ItemMetaImpl.java
index 192f2e490..4cde432d8 100644
--- a/src/main/java/net/minestom/server/item/ItemMetaImpl.java
+++ b/src/main/java/net/minestom/server/item/ItemMetaImpl.java
@@ -42,10 +42,7 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
writer.writeByte((byte) 0);
return;
}
- BinaryWriter w = new BinaryWriter();
- w.writeNBT("", nbt);
- var cachedBuffer = w.getBuffer();
- writer.write(cachedBuffer.flip());
+ writer.writeNBT("", nbt);
}
@Override
diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java
index 1d79c83a3..1642dbf93 100644
--- a/src/main/java/net/minestom/server/item/ItemStack.java
+++ b/src/main/java/net/minestom/server/item/ItemStack.java
@@ -1,13 +1,14 @@
package net.minestom.server.item;
+import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
+import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.TagWritable;
-import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.*;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
@@ -167,8 +168,8 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource asHoverEvent(@NotNull UnaryOperator op) {
- return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(),
- NBTUtils.asBinaryTagHolder(meta().toNBT()))));
+ final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC);
+ return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(), tagHolder)));
}
/**
diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java
new file mode 100644
index 000000000..c7e1fae75
--- /dev/null
+++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java
@@ -0,0 +1,158 @@
+package net.minestom.server.network;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.coordinate.Point;
+import net.minestom.server.item.ItemStack;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jglrxavpok.hephaistos.nbt.NBT;
+import org.jglrxavpok.hephaistos.nbt.NBTReader;
+import org.jglrxavpok.hephaistos.nbt.NBTWriter;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+@ApiStatus.Experimental
+public final class NetworkBuffer {
+ public static final Type BOOLEAN = NetworkBufferTypes.BOOLEAN;
+ public static final Type BYTE = NetworkBufferTypes.BYTE;
+ public static final Type SHORT = NetworkBufferTypes.SHORT;
+ public static final Type INT = NetworkBufferTypes.INT;
+ public static final Type LONG = NetworkBufferTypes.LONG;
+ public static final Type FLOAT = NetworkBufferTypes.FLOAT;
+ public static final Type DOUBLE = NetworkBufferTypes.DOUBLE;
+ public static final Type VAR_INT = NetworkBufferTypes.VAR_INT;
+ public static final Type VAR_LONG = NetworkBufferTypes.VAR_LONG;
+ public static final Type RAW_BYTES = NetworkBufferTypes.RAW_BYTES;
+ public static final Type STRING = NetworkBufferTypes.STRING;
+ public static final Type NBT = NetworkBufferTypes.NBT;
+ public static final Type BLOCK_POSITION = NetworkBufferTypes.BLOCK_POSITION;
+ public static final Type COMPONENT = NetworkBufferTypes.COMPONENT;
+ public static final Type UUID = NetworkBufferTypes.UUID;
+ public static final Type ITEM = NetworkBufferTypes.ITEM;
+
+ ByteBuffer nioBuffer;
+ final boolean resizable;
+ int writeIndex;
+ int readIndex;
+
+ NBTWriter nbtWriter;
+ NBTReader nbtReader;
+
+ public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) {
+ this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN);
+ this.resizable = resizable;
+
+ this.writeIndex = buffer.position();
+ this.readIndex = buffer.position();
+ }
+
+ public NetworkBuffer(@NotNull ByteBuffer buffer) {
+ this(buffer, true);
+ }
+
+ public NetworkBuffer(int initialCapacity) {
+ this(ByteBuffer.allocateDirect(initialCapacity), true);
+ }
+
+ public NetworkBuffer() {
+ this(1024);
+ }
+
+ public void write(@NotNull Type type, @NotNull T value) {
+ var impl = (NetworkBufferTypes.TypeImpl) type;
+ final long length = impl.writer().write(this, value);
+ if (length != -1) this.writeIndex += length;
+ }
+
+ public @NotNull T read(@NotNull Type type) {
+ var impl = (NetworkBufferTypes.TypeImpl) type;
+ return impl.reader().read(this);
+ }
+
+ public void writeOptional(@NotNull Type type, @Nullable T value) {
+ write(BOOLEAN, value != null);
+ if (value != null) write(type, value);
+ }
+
+ public @Nullable T readOptional(@NotNull Type type) {
+ return read(BOOLEAN) ? read(type) : null;
+ }
+
+ public void writeCollection(@NotNull Type type, @Nullable Collection<@NotNull T> values) {
+ if (values == null) {
+ write(BYTE, (byte) 0);
+ return;
+ }
+ write(VAR_INT, values.size());
+ for (T value : values) {
+ write(type, value);
+ }
+ }
+
+ @SafeVarargs
+ public final void writeCollection(@NotNull Type type, @NotNull T @Nullable ... values) {
+ writeCollection(type, values == null ? null : List.of(values));
+ }
+
+ public @NotNull Collection<@NotNull T> readCollection(@NotNull Type type) {
+ final int size = read(VAR_INT);
+ final Collection values = new java.util.ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ values.add(read(type));
+ }
+ return values;
+ }
+
+ public void copyTo(int srcOffset, byte @NotNull [] dest, int destOffset, int length) {
+ this.nioBuffer.get(srcOffset, dest, destOffset, length);
+ }
+
+ public void clear() {
+ this.writeIndex = 0;
+ this.readIndex = 0;
+ }
+
+ public int writeIndex() {
+ return writeIndex;
+ }
+
+ public int readIndex() {
+ return readIndex;
+ }
+
+ public void writeIndex(int writeIndex) {
+ this.writeIndex = writeIndex;
+ }
+
+ public void readIndex(int readIndex) {
+ this.readIndex = readIndex;
+ }
+
+ public int skipWrite(int length) {
+ final int oldWriteIndex = writeIndex;
+ writeIndex += length;
+ return oldWriteIndex;
+ }
+
+ public int readableBytes() {
+ return writeIndex - readIndex;
+ }
+
+ void ensureSize(int length) {
+ if (!resizable) return;
+ if (nioBuffer.capacity() < writeIndex + length) {
+ ByteBuffer newBuffer = ByteBuffer.allocateDirect(nioBuffer.capacity() * 2);
+ nioBuffer.position(0);
+ newBuffer.put(nioBuffer);
+ nioBuffer = newBuffer.clear();
+ }
+ }
+
+ public sealed interface Type permits NetworkBufferTypes.TypeImpl {
+ }
+}
diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypes.java b/src/main/java/net/minestom/server/network/NetworkBufferTypes.java
new file mode 100644
index 000000000..419df3ecc
--- /dev/null
+++ b/src/main/java/net/minestom/server/network/NetworkBufferTypes.java
@@ -0,0 +1,322 @@
+package net.minestom.server.network;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.minestom.server.coordinate.Point;
+import net.minestom.server.coordinate.Vec;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import org.jetbrains.annotations.NotNull;
+import org.jglrxavpok.hephaistos.nbt.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+final class NetworkBufferTypes {
+ private static final int SEGMENT_BITS = 0x7F;
+ private static final int CONTINUE_BIT = 0x80;
+
+ static final TypeImpl BOOLEAN = new TypeImpl<>(Boolean.class,
+ (buffer, value) -> {
+ buffer.ensureSize(1);
+ buffer.nioBuffer.put(buffer.writeIndex(), value ? (byte) 1 : (byte) 0);
+ return 1;
+ },
+ buffer -> {
+ final byte value = buffer.nioBuffer.get(buffer.readIndex());
+ buffer.readIndex += 1;
+ return value == 1;
+ });
+ static final TypeImpl BYTE = new TypeImpl<>(Byte.class,
+ (buffer, value) -> {
+ buffer.ensureSize(1);
+ buffer.nioBuffer.put(buffer.writeIndex(), value);
+ return 1;
+ },
+ buffer -> {
+ final byte value = buffer.nioBuffer.get(buffer.readIndex());
+ buffer.readIndex += 1;
+ return value;
+ });
+ static final TypeImpl SHORT = new TypeImpl<>(Short.class,
+ (buffer, value) -> {
+ buffer.ensureSize(2);
+ buffer.nioBuffer.putShort(buffer.writeIndex(), value);
+ return 2;
+ },
+ buffer -> {
+ final short value = buffer.nioBuffer.getShort(buffer.readIndex());
+ buffer.readIndex += 2;
+ return value;
+ });
+ static final TypeImpl INT = new TypeImpl<>(Integer.class,
+ (buffer, value) -> {
+ buffer.ensureSize(4);
+ buffer.nioBuffer.putInt(buffer.writeIndex(), value);
+ return 4;
+ },
+ buffer -> {
+ final int value = buffer.nioBuffer.getInt(buffer.readIndex());
+ buffer.readIndex += 4;
+ return value;
+ });
+ static final TypeImpl LONG = new TypeImpl<>(Long.class,
+ (buffer, value) -> {
+ buffer.ensureSize(8);
+ buffer.nioBuffer.putLong(buffer.writeIndex(), value);
+ return 8;
+ },
+ buffer -> {
+ final long value = buffer.nioBuffer.getLong(buffer.readIndex());
+ buffer.readIndex += 8;
+ return value;
+ });
+ static final TypeImpl FLOAT = new TypeImpl<>(Float.class,
+ (buffer, value) -> {
+ buffer.ensureSize(4);
+ buffer.nioBuffer.putFloat(buffer.writeIndex(), value);
+ return 4;
+ },
+ buffer -> {
+ final float value = buffer.nioBuffer.getFloat(buffer.readIndex());
+ buffer.readIndex += 4;
+ return value;
+ });
+ static final TypeImpl DOUBLE = new TypeImpl<>(Double.class,
+ (buffer, value) -> {
+ buffer.ensureSize(8);
+ buffer.nioBuffer.putDouble(buffer.writeIndex(), value);
+ return 8;
+ },
+ buffer -> {
+ final double value = buffer.nioBuffer.getDouble(buffer.readIndex());
+ buffer.readIndex += 8;
+ return value;
+ });
+ static final TypeImpl VAR_INT = new TypeImpl<>(Integer.class,
+ (buffer, boxed) -> {
+ final int value = boxed;
+ final int index = buffer.writeIndex();
+ var nio = buffer.nioBuffer;
+ if ((value & (0xFFFFFFFF << 7)) == 0) {
+ buffer.ensureSize(1);
+ nio.put(index, (byte) value);
+ return 1;
+ } else if ((value & (0xFFFFFFFF << 14)) == 0) {
+ buffer.ensureSize(2);
+ nio.putShort(index, (short) ((value & 0x7F | 0x80) << 8 | (value >>> 7)));
+ return 2;
+ } else if ((value & (0xFFFFFFFF << 21)) == 0) {
+ buffer.ensureSize(3);
+ nio.put(index, (byte) (value & 0x7F | 0x80));
+ nio.put(index + 1, (byte) ((value >>> 7) & 0x7F | 0x80));
+ nio.put(index + 2, (byte) (value >>> 14));
+ return 3;
+ } else if ((value & (0xFFFFFFFF << 28)) == 0) {
+ buffer.ensureSize(4);
+ nio.putInt(index, (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
+ | ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21));
+ return 4;
+ } else {
+ buffer.ensureSize(5);
+ nio.putInt(index, (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
+ | ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80));
+ nio.put(index + 4, (byte) (value >>> 28));
+ return 5;
+ }
+ },
+ buffer -> {
+ int index = buffer.readIndex();
+ // https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
+ int result = 0;
+ for (int shift = 0; ; shift += 7) {
+ byte b = buffer.nioBuffer.get(index++);
+ result |= (b & 0x7f) << shift;
+ if (b >= 0) {
+ buffer.readIndex += index - buffer.readIndex();
+ return result;
+ }
+ }
+ });
+ static final TypeImpl VAR_LONG = new TypeImpl<>(Long.class,
+ (buffer, value) -> {
+ buffer.ensureSize(10);
+ int size = 0;
+ while (true) {
+ if ((value & ~((long) SEGMENT_BITS)) == 0) {
+ buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) value.intValue());
+ return size + 1;
+ }
+ buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) (value & SEGMENT_BITS | CONTINUE_BIT));
+ size++;
+ // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
+ value >>>= 7;
+ }
+ },
+ buffer -> {
+ int length = 0;
+
+ long value = 0;
+ int position = 0;
+ byte currentByte;
+
+ while (true) {
+ currentByte = buffer.nioBuffer.get(buffer.readIndex() + length);
+ length++;
+ value |= (long) (currentByte & SEGMENT_BITS) << position;
+ if ((currentByte & CONTINUE_BIT) == 0) break;
+ position += 7;
+ if (position >= 64) throw new RuntimeException("VarLong is too big");
+ }
+ buffer.readIndex += length;
+ return value;
+ });
+ static final TypeImpl RAW_BYTES = new TypeImpl<>(byte[].class,
+ (buffer, value) -> {
+ buffer.ensureSize(value.length);
+ buffer.nioBuffer.put(buffer.writeIndex(), value);
+ return value.length;
+ },
+ buffer -> {
+ final int limit = buffer.nioBuffer.limit();
+ final int length = limit - buffer.readIndex();
+ assert length > 0 : "Invalid remaining: " + length;
+ final byte[] bytes = new byte[length];
+ buffer.nioBuffer.get(buffer.readIndex(), bytes);
+ buffer.readIndex += length;
+ return bytes;
+ });
+ static final TypeImpl STRING = new TypeImpl<>(String.class,
+ (buffer, value) -> {
+ final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+ buffer.write(VAR_INT, bytes.length);
+ buffer.write(RAW_BYTES, bytes);
+ return -1;
+ },
+ buffer -> {
+ final int length = buffer.read(VAR_INT);
+ byte[] bytes = new byte[length];
+ buffer.nioBuffer.get(buffer.readIndex(), bytes);
+ buffer.readIndex += length;
+ return new String(bytes, StandardCharsets.UTF_8);
+ });
+ static final TypeImpl NBT = new TypeImpl<>(NBT.class,
+ (buffer, value) -> {
+ NBTWriter nbtWriter = buffer.nbtWriter;
+ if (nbtWriter == null) {
+ nbtWriter = new NBTWriter(new OutputStream() {
+ @Override
+ public void write(int b) {
+ buffer.write(BYTE, (byte) b);
+ }
+ }, CompressedProcesser.NONE);
+ buffer.nbtWriter = nbtWriter;
+ }
+ try {
+ nbtWriter.writeNamed("", value);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return -1;
+ },
+ buffer -> {
+ NBTReader nbtReader = buffer.nbtReader;
+ if (nbtReader == null) {
+ nbtReader = new NBTReader(new InputStream() {
+ @Override
+ public int read() {
+ return buffer.read(BYTE);
+ }
+ }, CompressedProcesser.NONE);
+ buffer.nbtReader = nbtReader;
+ }
+ try {
+ return nbtReader.read();
+ } catch (IOException | NBTException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ static final TypeImpl BLOCK_POSITION = new TypeImpl<>(Point.class,
+ (buffer, value) -> {
+ final int blockX = value.blockX();
+ final int blockY = value.blockY();
+ final int blockZ = value.blockZ();
+ final long longPos = (((long) blockX & 0x3FFFFFF) << 38) |
+ (((long) blockZ & 0x3FFFFFF) << 12) |
+ ((long) blockY & 0xFFF);
+ buffer.write(LONG, longPos);
+ return -1;
+ },
+ buffer -> {
+ final long value = buffer.read(LONG);
+ final int x = (int) (value >> 38);
+ final int y = (int) (value << 52 >> 52);
+ final int z = (int) (value << 26 >> 38);
+ return new Vec(x, y, z);
+ });
+ static final TypeImpl COMPONENT = new TypeImpl<>(Component.class,
+ (buffer, value) -> {
+ final String json = GsonComponentSerializer.gson().serialize(value);
+ buffer.write(STRING, json);
+ return -1;
+ },
+ buffer -> {
+ final String json = buffer.read(STRING);
+ return GsonComponentSerializer.gson().deserialize(json);
+ });
+ static final TypeImpl UUID = new TypeImpl<>(UUID.class,
+ (buffer, value) -> {
+ buffer.write(LONG, value.getMostSignificantBits());
+ buffer.write(LONG, value.getLeastSignificantBits());
+ return -1;
+ },
+ buffer -> {
+ final long mostSignificantBits = buffer.read(LONG);
+ final long leastSignificantBits = buffer.read(LONG);
+ return new UUID(mostSignificantBits, leastSignificantBits);
+ });
+ static final TypeImpl ITEM = new TypeImpl<>(ItemStack.class,
+ (buffer, value) -> {
+ if (value.isAir()) {
+ buffer.write(BOOLEAN, false);
+ return -1;
+ }
+ buffer.write(BOOLEAN, true);
+ buffer.write(VAR_INT, value.material().id());
+ buffer.write(BYTE, (byte) value.amount());
+ buffer.write(NBT, value.meta().toNBT());
+ return -1;
+ },
+ buffer -> {
+ final boolean present = buffer.read(BOOLEAN);
+ if (!present) return ItemStack.AIR;
+
+ final int id = buffer.read(VAR_INT);
+ final Material material = Material.fromId(id);
+ if (material == null) throw new RuntimeException("Unknown material id: " + id);
+
+ final int amount = buffer.read(BYTE);
+ final NBT nbt = buffer.read(NBT);
+ if (!(nbt instanceof NBTCompound compound)) {
+ return ItemStack.of(material, amount);
+ }
+
+ return ItemStack.fromNBT(material, compound, amount);
+ });
+
+ record TypeImpl(@NotNull Class type,
+ @NotNull TypeWriter writer,
+ @NotNull TypeReader reader) implements NetworkBuffer.Type {
+ }
+
+ interface TypeWriter {
+ long write(@NotNull NetworkBuffer buffer, @NotNull T value);
+ }
+
+ interface TypeReader {
+ @NotNull T read(@NotNull NetworkBuffer buffer);
+ }
+}
diff --git a/src/main/java/net/minestom/server/network/PacketProcessor.java b/src/main/java/net/minestom/server/network/PacketProcessor.java
index 5d5f5fd0d..cf810bd24 100644
--- a/src/main/java/net/minestom/server/network/PacketProcessor.java
+++ b/src/main/java/net/minestom/server/network/PacketProcessor.java
@@ -27,16 +27,19 @@ public record PacketProcessor(@NotNull ClientPacketsHandler statusHandler,
}
public @NotNull ClientPacket create(@NotNull ConnectionState connectionState, int packetId, ByteBuffer body) {
- BinaryReader binaryReader = new BinaryReader(body);
- return switch (connectionState) {
- case PLAY -> playHandler.create(packetId, binaryReader);
- case LOGIN -> loginHandler.create(packetId, binaryReader);
- case STATUS -> statusHandler.create(packetId, binaryReader);
+ NetworkBuffer networkBuffer = new NetworkBuffer(body);
+ BinaryReader reader = new BinaryReader(networkBuffer);
+ final ClientPacket clientPacket = switch (connectionState) {
+ case PLAY -> playHandler.create(packetId, reader);
+ case LOGIN -> loginHandler.create(packetId, reader);
+ case STATUS -> statusHandler.create(packetId, reader);
case UNKNOWN -> {
assert packetId == 0;
- yield new HandshakePacket(binaryReader);
+ yield new HandshakePacket(reader);
}
};
+ body.position(networkBuffer.readIndex());
+ return clientPacket;
}
public ClientPacket process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {
diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java
index 7e94e76dd..652af3113 100644
--- a/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java
+++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java
@@ -5,6 +5,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.velocity.VelocityProxy;
import net.minestom.server.network.ConnectionManager;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.client.ClientPreplayPacket;
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
import net.minestom.server.network.player.GameProfile;
@@ -18,6 +19,10 @@ import org.jetbrains.annotations.Nullable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+
+import static net.minestom.server.network.NetworkBuffer.STRING;
public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) implements ClientPreplayPacket {
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
@@ -41,14 +46,20 @@ public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) i
// Velocity
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
if (data != null && data.length > 0) {
- BinaryReader reader = new BinaryReader(data);
- success = VelocityProxy.checkIntegrity(reader);
+ NetworkBuffer buffer = new NetworkBuffer(ByteBuffer.wrap(data));
+ success = VelocityProxy.checkIntegrity(buffer);
if (success) {
// Get the real connection address
- final InetAddress address = VelocityProxy.readAddress(reader);
+ final InetAddress address;
+ try {
+ address = InetAddress.getByName(buffer.read(STRING));
+ } catch (UnknownHostException e) {
+ MinecraftServer.getExceptionManager().handleException(e);
+ return;
+ }
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
socketAddress = new InetSocketAddress(address, port);
- gameProfile = new GameProfile(reader);
+ gameProfile = new GameProfile(new BinaryReader(buffer));
}
}
}
diff --git a/src/main/java/net/minestom/server/tag/Serializers.java b/src/main/java/net/minestom/server/tag/Serializers.java
index 952a23d4c..59b469842 100644
--- a/src/main/java/net/minestom/server/tag/Serializers.java
+++ b/src/main/java/net/minestom/server/tag/Serializers.java
@@ -3,9 +3,9 @@ package net.minestom.server.tag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.item.ItemStack;
-import net.minestom.server.utils.Utils;
import org.jglrxavpok.hephaistos.nbt.*;
+import java.util.UUID;
import java.util.function.Function;
/**
@@ -22,8 +22,8 @@ final class Serializers {
static final Entry STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String);
static final Entry NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
- static final Entry UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> Utils.intArrayToUuid(intArray.getValue().copyArray()),
- uuid -> NBT.IntArray(Utils.uuidToIntArray(uuid)));
+ static final Entry UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> intArrayToUuid(intArray.getValue().copyArray()),
+ uuid -> NBT.IntArray(uuidToIntArray(uuid)));
static final Entry ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT);
static final Entry COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()),
component -> NBT.String(GsonComponentSerializer.gson().serialize(component)));
@@ -55,4 +55,26 @@ final class Serializers {
return writer.apply(value);
}
}
+
+ private static int[] uuidToIntArray(UUID uuid) {
+ int[] array = new int[4];
+
+ final long uuidMost = uuid.getMostSignificantBits();
+ final long uuidLeast = uuid.getLeastSignificantBits();
+
+ array[0] = (int) (uuidMost >> 32);
+ array[1] = (int) uuidMost;
+
+ array[2] = (int) (uuidLeast >> 32);
+ array[3] = (int) uuidLeast;
+
+ return array;
+ }
+
+ private static UUID intArrayToUuid(int[] array) {
+ final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
+ final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
+
+ return new UUID(uuidMost, uuidLeast);
+ }
}
diff --git a/src/main/java/net/minestom/server/utils/NBTUtils.java b/src/main/java/net/minestom/server/utils/NBTUtils.java
deleted file mode 100644
index eec170a12..000000000
--- a/src/main/java/net/minestom/server/utils/NBTUtils.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package net.minestom.server.utils;
-
-import net.kyori.adventure.nbt.api.BinaryTagHolder;
-import net.kyori.adventure.util.Codec;
-import net.minestom.server.adventure.MinestomAdventure;
-import net.minestom.server.inventory.Inventory;
-import net.minestom.server.item.Enchantment;
-import net.minestom.server.item.ItemStack;
-import net.minestom.server.item.Material;
-import net.minestom.server.utils.binary.BinaryReader;
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.Contract;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jglrxavpok.hephaistos.nbt.*;
-import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
-
-import java.util.List;
-import java.util.Map;
-
-// for lack of a better name
-@ApiStatus.Internal
-public final class NBTUtils {
-
- /**
- * An Adventure codec to convert between NBT and SNBT.
- *
- * @deprecated Use {@link MinestomAdventure#NBT_CODEC}
- */
- @Deprecated(forRemoval = true)
- public static final Codec SNBT_CODEC = MinestomAdventure.NBT_CODEC;
-
- private NBTUtils() {
- }
-
- /**
- * Turns an {@link NBTCompound} into an Adventure {@link BinaryTagHolder}.
- *
- * @param tag the tag, if any
- * @return the binary tag holder, or {@code null} if the tag was null
- */
- @Contract("null -> null; !null -> !null")
- public static BinaryTagHolder asBinaryTagHolder(@Nullable NBTCompound tag) {
- if (tag == null) {
- return null;
- }
-
- return BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC);
- }
-
- /**
- * Loads all the items from the 'items' list into the given inventory
- *
- * @param items the items to save
- * @param destination the inventory destination
- */
- public static void loadAllItems(@NotNull NBTList items, @NotNull Inventory destination) {
- destination.clear();
- for (NBTCompound tag : items) {
- Material material = Material.fromNamespaceId(tag.getString("id"));
- if (material == Material.AIR) {
- material = Material.STONE;
- }
- byte count = tag.getByte("Count");
- NBTCompound nbtCompound = null;
- if (tag.containsKey("tag")) {
- nbtCompound = tag.getCompound("tag");
- }
- ItemStack itemStack = ItemStack.fromNBT(material, nbtCompound, count);
- destination.setItemStack(tag.getByte("Slot"), itemStack);
- }
- }
-
- public static void saveAllItems(@NotNull List list, @NotNull Inventory inventory) {
- for (int i = 0; i < inventory.getSize(); i++) {
- final ItemStack stack = inventory.getItemStack(i);
- final NBTCompound tag = stack.meta().toNBT();
- final int slotIndex = i;
- list.add(NBT.Compound(nbt -> {
- nbt.set("tag", tag);
- nbt.setByte("Slot", (byte) slotIndex);
- nbt.setByte("Count", (byte) stack.amount());
- nbt.setString("id", stack.material().name());
- }));
- }
- }
-
- public static void writeEnchant(@NotNull MutableNBTCompound nbt, @NotNull String listName,
- @NotNull Map enchantmentMap) {
- nbt.set(listName, NBT.List(
- NBTType.TAG_Compound,
- enchantmentMap.entrySet().stream()
- .map(entry -> NBT.Compound(Map.of(
- "lvl", NBT.Short(entry.getValue()),
- "id", NBT.String(entry.getKey().name()))))
- .toList()
- ));
- }
-
- public static @NotNull ItemStack readItemStack(@NotNull BinaryReader reader) {
- final boolean present = reader.readBoolean();
- if (!present) return ItemStack.AIR;
-
- final int id = reader.readVarInt();
- if (id == -1) {
- // Drop mode
- return ItemStack.AIR;
- }
-
- final Material material = Material.fromId((short) id);
- final byte count = reader.readByte();
- NBTCompound nbtCompound = reader.readTag() instanceof NBTCompound compound ?
- compound : null;
- return ItemStack.fromNBT(material, nbtCompound, count);
- }
-}
diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java
index eb65f8a5e..8f3772dbd 100644
--- a/src/main/java/net/minestom/server/utils/PacketUtils.java
+++ b/src/main/java/net/minestom/server/utils/PacketUtils.java
@@ -17,6 +17,7 @@ import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.*;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection;
@@ -255,24 +256,26 @@ public final class PacketUtils {
int id,
@NotNull Writeable writeable,
int compressionThreshold) {
- BinaryWriter writerView = BinaryWriter.view(buffer); // ensure that the buffer is not resized
+ NetworkBuffer networkBuffer = new NetworkBuffer(buffer, false);
+ BinaryWriter legacyWriter = new BinaryWriter(networkBuffer);
if (compressionThreshold <= 0) {
// Uncompressed format https://wiki.vg/Protocol#Without_compression
- final int lengthIndex = Utils.writeEmptyVarIntHeader(buffer);
- Utils.writeVarInt(buffer, id);
- writeable.write(writerView);
- final int finalSize = buffer.position() - (lengthIndex + 3);
+ final int lengthIndex = networkBuffer.skipWrite(3);
+ networkBuffer.write(NetworkBuffer.VAR_INT, id);
+ writeable.write(legacyWriter);
+ final int finalSize = networkBuffer.writeIndex() - (lengthIndex + 3);
Utils.writeVarIntHeader(buffer, lengthIndex, finalSize);
+ buffer.position(networkBuffer.writeIndex());
return;
}
// Compressed format https://wiki.vg/Protocol#With_compression
- final int compressedIndex = Utils.writeEmptyVarIntHeader(buffer);
- final int uncompressedIndex = Utils.writeEmptyVarIntHeader(buffer);
+ final int compressedIndex = networkBuffer.skipWrite(3);
+ final int uncompressedIndex = networkBuffer.skipWrite(3);
- final int contentStart = buffer.position();
- Utils.writeVarInt(buffer, id);
- writeable.write(writerView);
- final int packetSize = buffer.position() - contentStart;
+ final int contentStart = networkBuffer.writeIndex();
+ networkBuffer.write(NetworkBuffer.VAR_INT, id);
+ writeable.write(legacyWriter);
+ final int packetSize = networkBuffer.writeIndex() - contentStart;
final boolean compressed = packetSize >= compressionThreshold;
if (compressed) {
// Packet large enough, compress it
@@ -283,11 +286,15 @@ public final class PacketUtils {
deflater.finish();
deflater.deflate(buffer.position(contentStart));
deflater.reset();
+
+ networkBuffer.skipWrite(buffer.position() - contentStart);
}
}
// Packet header (Packet + Data Length)
- Utils.writeVarIntHeader(buffer, compressedIndex, buffer.position() - uncompressedIndex);
+ Utils.writeVarIntHeader(buffer, compressedIndex, networkBuffer.writeIndex() - uncompressedIndex);
Utils.writeVarIntHeader(buffer, uncompressedIndex, compressed ? packetSize : 0);
+
+ buffer.position(networkBuffer.writeIndex());
}
@ApiStatus.Internal
diff --git a/src/main/java/net/minestom/server/utils/SerializerUtils.java b/src/main/java/net/minestom/server/utils/SerializerUtils.java
deleted file mode 100644
index d0e81a250..000000000
--- a/src/main/java/net/minestom/server/utils/SerializerUtils.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package net.minestom.server.utils;
-
-import net.minestom.server.coordinate.Point;
-import net.minestom.server.coordinate.Vec;
-
-public final class SerializerUtils {
-
- private SerializerUtils() {
-
- }
-
- public static long positionToLong(int x, int y, int z) {
- return (((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF);
- }
-
- public static Point longToBlockPosition(long value) {
- final int x = (int) (value >> 38);
- final int y = (int) (value << 52 >> 52);
- final int z = (int) (value << 26 >> 38);
- return new Vec(x, y, z);
- }
-}
diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java
index f31e25368..972212cba 100644
--- a/src/main/java/net/minestom/server/utils/Utils.java
+++ b/src/main/java/net/minestom/server/utils/Utils.java
@@ -45,12 +45,6 @@ public final class Utils {
buffer.put(startIndex + 2, (byte) (value >>> 14));
}
- public static int writeEmptyVarIntHeader(@NotNull ByteBuffer buffer) {
- final int index = buffer.position();
- buffer.position(index + 3); // Skip 3 bytes
- return index;
- }
-
public static int readVarInt(ByteBuffer buf) {
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
int result = 0;
@@ -62,94 +56,4 @@ public final class Utils {
}
}
}
-
- public static long readVarLong(@NotNull ByteBuffer buf) {
- // https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L404
- long result = 0;
- for (int shift = 0; shift < 56; shift += 7) {
- byte b = buf.get();
- result |= (b & 0x7fL) << shift;
- if (b >= 0) {
- return result;
- }
- }
- return result | (buf.get() & 0xffL) << 56;
- }
-
- public static void writeVarLong(ByteBuffer buffer, long value) {
- do {
- byte temp = (byte) (value & 0b01111111);
- value >>>= 7;
- if (value != 0) {
- temp |= 0b10000000;
- }
- buffer.put(temp);
- } while (value != 0);
- }
-
- public static int[] uuidToIntArray(UUID uuid) {
- int[] array = new int[4];
-
- final long uuidMost = uuid.getMostSignificantBits();
- final long uuidLeast = uuid.getLeastSignificantBits();
-
- array[0] = (int) (uuidMost >> 32);
- array[1] = (int) uuidMost;
-
- array[2] = (int) (uuidLeast >> 32);
- array[3] = (int) uuidLeast;
-
- return array;
- }
-
- public static UUID intArrayToUuid(int[] array) {
- final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
- final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
-
- return new UUID(uuidMost, uuidLeast);
- }
-
- private static final int[] MAGIC = {
- -1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
- 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
- 0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
- 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
- 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135,
- 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
- 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
- 178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
- 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
- 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
- 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
- 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
- 104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
- 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
- 0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
- 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
- 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
- 0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
- 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
- 0, 5};
-
- public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
- final long maxEntryValue = (1L << bitsPerEntry) - 1;
- final char valuesPerLong = (char) (64 / bitsPerEntry);
- final int magicIndex = 3 * (valuesPerLong - 1);
- final long divideMul = Integer.toUnsignedLong(MAGIC[magicIndex]);
- final long divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1]);
- final int divideShift = MAGIC[magicIndex + 2];
- final int size = (blocks.length + valuesPerLong - 1) / valuesPerLong;
-
- long[] data = new long[size];
-
- for (int i = 0; i < blocks.length; i++) {
- final long value = blocks[i];
- final int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
- final int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
- data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
- }
-
- return data;
- }
-
}
diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java
index c804bdf36..a3cf3b520 100644
--- a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java
+++ b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java
@@ -2,21 +2,14 @@ package net.minestom.server.utils.binary;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
-import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.item.ItemStack;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.Either;
-import net.minestom.server.utils.NBTUtils;
-import net.minestom.server.utils.SerializerUtils;
-import net.minestom.server.utils.Utils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
-import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
import org.jglrxavpok.hephaistos.nbt.NBT;
-import org.jglrxavpok.hephaistos.nbt.NBTException;
-import org.jglrxavpok.hephaistos.nbt.NBTReader;
-import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -27,17 +20,22 @@ import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
+import static net.minestom.server.network.NetworkBuffer.*;
+
/**
* Class used to read from a byte array.
*
* WARNING: not thread-safe.
*/
public class BinaryReader extends InputStream {
- private final ByteBuffer buffer;
- private NBTReader nbtReader = null;
+ private final NetworkBuffer buffer;
+
+ public BinaryReader(NetworkBuffer buffer) {
+ this.buffer = buffer;
+ }
public BinaryReader(@NotNull ByteBuffer buffer) {
- this.buffer = buffer;
+ this.buffer = new NetworkBuffer(buffer);
}
public BinaryReader(byte[] bytes) {
@@ -45,57 +43,53 @@ public class BinaryReader extends InputStream {
}
public int readVarInt() {
- return Utils.readVarInt(buffer);
+ return buffer.read(VAR_INT);
}
public long readVarLong() {
- return Utils.readVarLong(buffer);
+ return buffer.read(VAR_LONG);
}
public boolean readBoolean() {
- return buffer.get() == 1;
+ return buffer.read(BOOLEAN);
}
public byte readByte() {
- return buffer.get();
+ return buffer.read(BYTE);
}
public short readShort() {
- return buffer.getShort();
- }
-
- public char readChar() {
- return buffer.getChar();
+ return buffer.read(SHORT);
}
public int readUnsignedShort() {
- return buffer.getShort() & 0xFFFF;
+ return buffer.read(SHORT) & 0xFFFF;
}
/**
* Same as readInt
*/
public int readInteger() {
- return buffer.getInt();
+ return buffer.read(INT);
}
/**
* Same as readInteger, created for parity with BinaryWriter
*/
public int readInt() {
- return buffer.getInt();
+ return buffer.read(INT);
}
public long readLong() {
- return buffer.getLong();
+ return buffer.read(LONG);
}
public float readFloat() {
- return buffer.getFloat();
+ return buffer.read(FLOAT);
}
public double readDouble() {
- return buffer.getDouble();
+ return buffer.read(DOUBLE);
}
/**
@@ -112,9 +106,11 @@ public class BinaryReader extends InputStream {
final int length = readVarInt();
byte[] bytes = new byte[length];
try {
- this.buffer.get(bytes);
+ for (int i = 0; i < length; i++) {
+ bytes[i] = readByte();
+ }
} catch (BufferUnderflowException e) {
- throw new RuntimeException("Could not read " + length + ", " + buffer.remaining() + " remaining.");
+ throw new RuntimeException("Could not read " + length + ", " + buffer.readableBytes() + " remaining.");
}
final String str = new String(bytes, StandardCharsets.UTF_8);
Check.stateCondition(str.length() > maxLength,
@@ -123,12 +119,14 @@ public class BinaryReader extends InputStream {
}
public String readSizedString() {
- return readSizedString(Integer.MAX_VALUE);
+ return buffer.read(STRING);
}
public byte[] readBytes(int length) {
byte[] bytes = new byte[length];
- buffer.get(bytes);
+ for (int i = 0; i < length; i++) {
+ bytes[i] = readByte();
+ }
return bytes;
}
@@ -177,27 +175,19 @@ public class BinaryReader extends InputStream {
}
public byte[] readRemainingBytes() {
- return readBytes(available());
+ return buffer.read(RAW_BYTES);
}
public Point readBlockPosition() {
- return SerializerUtils.longToBlockPosition(buffer.getLong());
+ return buffer.read(BLOCK_POSITION);
}
public UUID readUuid() {
- return new UUID(readLong(), readLong());
+ return buffer.read(UUID);
}
- /**
- * Tries to read an {@link ItemStack}.
- *
- * @return the read item
- * @throws NullPointerException if the item could not get read
- */
public ItemStack readItemStack() {
- final ItemStack itemStack = NBTUtils.readItemStack(this);
- Check.notNull(itemStack, "#readSlot returned null, probably because the buffer was corrupted");
- return itemStack;
+ return buffer.read(ITEM);
}
public Component readComponent(int maxLength) {
@@ -206,7 +196,7 @@ public class BinaryReader extends InputStream {
}
public Component readComponent() {
- return readComponent(Integer.MAX_VALUE);
+ return buffer.read(COMPONENT);
}
/**
@@ -263,10 +253,6 @@ public class BinaryReader extends InputStream {
return list;
}
- public ByteBuffer getBuffer() {
- return buffer;
- }
-
@Override
public int read() {
return readByte() & 0xFF;
@@ -274,21 +260,11 @@ public class BinaryReader extends InputStream {
@Override
public int available() {
- return buffer.remaining();
+ return buffer.readableBytes();
}
public NBT readTag() {
- NBTReader reader = this.nbtReader;
- if (reader == null) {
- reader = new NBTReader(this, CompressedProcesser.NONE);
- this.nbtReader = reader;
- }
- try {
- return reader.read();
- } catch (IOException | NBTException e) {
- MinecraftServer.getExceptionManager().handleException(e);
- throw new RuntimeException();
- }
+ return buffer.read(NBT);
}
/**
@@ -299,11 +275,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 = buffer.position();
+ int startingPosition = buffer.readIndex();
extractor.run();
- int endingPosition = getBuffer().position();
+ int endingPosition = buffer.readIndex();
byte[] output = new byte[endingPosition - startingPosition];
- buffer.get(startingPosition, output);
+ buffer.copyTo(buffer.readIndex(), output, 0, output.length);
+ //buffer.get(startingPosition, output);
return output;
}
}
diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java
index 93f475eaa..54b417cd2 100644
--- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java
+++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java
@@ -1,49 +1,42 @@
package net.minestom.server.utils.binary;
import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
-import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
+import net.minestom.server.coordinate.Vec;
import net.minestom.server.item.ItemStack;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.Either;
-import net.minestom.server.utils.SerializerUtils;
-import net.minestom.server.utils.Utils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
-import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
import org.jglrxavpok.hephaistos.nbt.NBT;
-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.Collection;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import static net.minestom.server.network.NetworkBuffer.*;
+
/**
* Class used to write to a byte array.
* WARNING: not thread-safe.
*/
public class BinaryWriter extends OutputStream {
- private ByteBuffer buffer;
- private NBTWriter nbtWriter; // Lazily initialized
+ private final NetworkBuffer buffer;
- private final boolean resizable;
+ public BinaryWriter(NetworkBuffer buffer) {
+ this.buffer = buffer;
+ }
private BinaryWriter(ByteBuffer buffer, boolean resizable) {
- this.buffer = buffer;
- this.resizable = resizable;
+ this.buffer = new NetworkBuffer(buffer, resizable);
}
public BinaryWriter(@NotNull ByteBuffer buffer) {
- this.buffer = buffer;
- this.resizable = true;
+ this.buffer = new NetworkBuffer(buffer);
}
public BinaryWriter(int initialCapacity) {
@@ -59,158 +52,55 @@ public class BinaryWriter extends OutputStream {
return new BinaryWriter(buffer, false);
}
- protected void ensureSize(int length) {
- if (!resizable) return;
- final int position = buffer.position();
- if (position + length >= buffer.limit()) {
- final int newLength = (position + length) * 4;
- var copy = buffer.isDirect() ?
- ByteBuffer.allocateDirect(newLength) : ByteBuffer.allocate(newLength);
- copy.put(buffer.flip());
- this.buffer = copy;
- }
- }
-
- /**
- * Writes a component to the buffer as a sized string.
- *
- * @param component the component
- */
public void writeComponent(@NotNull Component component) {
- this.writeSizedString(GsonComponentSerializer.gson().serialize(component));
+ this.buffer.write(COMPONENT, component);
}
- /**
- * Writes a single byte to the buffer.
- *
- * @param b the byte to write
- */
public void writeByte(byte b) {
- ensureSize(Byte.BYTES);
- buffer.put(b);
+ this.buffer.write(BYTE, b);
}
- /**
- * Writes a single boolean to the buffer.
- *
- * @param b the boolean to write
- */
public void writeBoolean(boolean b) {
- writeByte((byte) (b ? 1 : 0));
+ this.buffer.write(BOOLEAN, b);
}
- /**
- * Writes a single char to the buffer.
- *
- * @param c the char to write
- */
- public void writeChar(char c) {
- ensureSize(Character.BYTES);
- buffer.putChar(c);
- }
-
- /**
- * Writes a single short to the buffer.
- *
- * @param s the short to write
- */
public void writeShort(short s) {
- ensureSize(Short.BYTES);
- buffer.putShort(s);
+ this.buffer.write(SHORT, s);
}
- /**
- * Writes a single int to the buffer.
- *
- * @param i the int to write
- */
public void writeInt(int i) {
- ensureSize(Integer.BYTES);
- buffer.putInt(i);
+ this.buffer.write(INT, i);
}
- /**
- * Writes a single long to the buffer.
- *
- * @param l the long to write
- */
public void writeLong(long l) {
- ensureSize(Long.BYTES);
- buffer.putLong(l);
+ this.buffer.write(LONG, l);
}
- /**
- * Writes a single float to the buffer.
- *
- * @param f the float to write
- */
public void writeFloat(float f) {
- ensureSize(Float.BYTES);
- buffer.putFloat(f);
+ this.buffer.write(FLOAT, f);
}
- /**
- * Writes a single double to the buffer.
- *
- * @param d the double to write
- */
public void writeDouble(double d) {
- ensureSize(Double.BYTES);
- buffer.putDouble(d);
+ this.buffer.write(DOUBLE, d);
}
- /**
- * Writes a single var-int to the buffer.
- *
- * @param i the int to write
- */
public void writeVarInt(int i) {
- ensureSize(5);
- Utils.writeVarInt(buffer, i);
+ this.buffer.write(VAR_INT, i);
}
- /**
- * Writes a single var-long to the buffer.
- *
- * @param l the long to write
- */
public void writeVarLong(long l) {
- ensureSize(10);
- Utils.writeVarLong(buffer, l);
+ this.buffer.write(VAR_LONG, l);
}
- /**
- * Writes a string to the buffer.
- *
- * The size is a var-int type.
- *
- * @param string the string to write
- */
public void writeSizedString(@NotNull String string) {
- final var bytes = string.getBytes(StandardCharsets.UTF_8);
- writeVarInt(bytes.length);
- writeBytes(bytes);
+ this.buffer.write(STRING, string);
}
- /**
- * Writes a null terminated string to the buffer. This method adds the null character
- * to the end of the string before writing.
- *
- * @param string the string to write
- * @param charset the charset to encode in
- */
public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
final var bytes = (string + '\0').getBytes(charset);
writeBytes(bytes);
}
- /**
- * Writes a var-int array to the buffer.
- *
- * It is sized by another var-int at the beginning.
- *
- * @param array the integers to write
- */
public void writeVarIntArray(int[] array) {
if (array == null) {
writeVarInt(0);
@@ -253,46 +143,16 @@ public class BinaryWriter extends OutputStream {
writeBytes(array);
}
- /**
- * Writes a byte array.
- *
- * WARNING: it doesn't write the length of {@code bytes}.
- *
- * @param bytes the byte array to write
- */
public void writeBytes(byte @NotNull [] bytes) {
- if (bytes.length == 0) return;
- ensureSize(bytes.length);
- buffer.put(bytes);
+ this.buffer.write(RAW_BYTES, bytes);
}
- /**
- * Writes a string to the buffer.
- *
- * The array is sized by a var-int and all strings are wrote using {@link #writeSizedString(String)}.
- *
- * @param array the string array to write
- */
public void writeStringArray(@NotNull String[] array) {
- if (array == null) {
- writeVarInt(0);
- return;
- }
- writeVarInt(array.length);
- for (String element : array) {
- writeSizedString(element);
- }
+ this.buffer.writeCollection(STRING, array);
}
- /**
- * Writes an {@link UUID}.
- * It is done by writing both long, the most and least significant bits.
- *
- * @param uuid the {@link UUID} to write
- */
public void writeUuid(@NotNull UUID uuid) {
- writeLong(uuid.getMostSignificantBits());
- writeLong(uuid.getLeastSignificantBits());
+ this.buffer.write(UUID, uuid);
}
public void writeBlockPosition(@NotNull Point point) {
@@ -300,30 +160,15 @@ public class BinaryWriter extends OutputStream {
}
public void writeBlockPosition(int x, int y, int z) {
- writeLong(SerializerUtils.positionToLong(x, y, z));
+ this.buffer.write(BLOCK_POSITION, new Vec(x, y, z));
}
public void writeItemStack(@NotNull ItemStack itemStack) {
- if (itemStack.isAir()) {
- writeBoolean(false);
- } else {
- writeBoolean(true);
- writeVarInt(itemStack.material().id());
- writeByte((byte) itemStack.amount());
- write(itemStack.meta());
- }
+ this.buffer.write(ITEM, itemStack);
}
public void writeNBT(@NotNull String name, @NotNull NBT tag) {
- if (nbtWriter == null) {
- this.nbtWriter = new NBTWriter(this, CompressedProcesser.NONE);
- }
- try {
- nbtWriter.writeNamed(name, tag);
- } catch (IOException e) {
- // should not throw, as nbtWriter points to this PacketWriter
- MinecraftServer.getExceptionManager().handleException(e);
- }
+ this.buffer.write(NBT, tag);
}
public void writeEither(Either either, BiConsumer leftWriter, BiConsumer rightWriter) {
@@ -346,12 +191,13 @@ public class BinaryWriter extends OutputStream {
}
public void write(@NotNull ByteBuffer buffer) {
- ensureSize(buffer.remaining());
- this.buffer.put(buffer);
+ byte[] remaining = new byte[buffer.remaining()];
+ buffer.get(remaining);
+ writeBytes(remaining);
}
public void write(@NotNull BinaryWriter writer) {
- write(writer.buffer);
+ writeBytes(writer.toByteArray());
}
/**
@@ -387,73 +233,18 @@ public class BinaryWriter extends OutputStream {
* @return the byte array containing all the {@link BinaryWriter} data
*/
public byte[] toByteArray() {
- buffer.flip();
- byte[] bytes = new byte[buffer.remaining()];
- buffer.get(bytes);
+ byte[] bytes = new byte[buffer.writeIndex()];
+ this.buffer.copyTo(0, bytes, 0, bytes.length);
return bytes;
}
- /**
- * 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 var headerBuf = headerWriter.getBuffer();
- // Merge both the headerBuf and this buffer
- final var finalBuffer = concat(headerBuf, buffer);
- // Change the buffer used by this writer
- setBuffer(finalBuffer);
- }
-
- /**
- * 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 var footerBuf = footerWriter.getBuffer();
- // Merge both this buffer and the 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 ByteBuffer getBuffer() {
- return buffer;
- }
-
- /**
- * Changes the buffer used by this binary writer.
- *
- * @param buffer the new buffer used by this binary writer
- */
- public void setBuffer(ByteBuffer buffer) {
- this.buffer = buffer;
- }
-
@Override
public void write(int b) {
writeByte((byte) b);
}
public void writeUnsignedShort(int yourShort) {
- // FIXME unsigned
- ensureSize(Short.BYTES);
- buffer.putShort((short) (yourShort & 0xFFFF));
+ this.buffer.write(SHORT, (short) (yourShort & 0xFFFF));
}
/**
diff --git a/src/test/java/net/minestom/server/network/NetworkBufferTest.java b/src/test/java/net/minestom/server/network/NetworkBufferTest.java
new file mode 100644
index 000000000..13126fc4b
--- /dev/null
+++ b/src/test/java/net/minestom/server/network/NetworkBufferTest.java
@@ -0,0 +1,286 @@
+package net.minestom.server.network;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnknownNullability;
+import org.jglrxavpok.hephaistos.nbt.NBT;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class NetworkBufferTest {
+
+ @Test
+ public void readableBytes() {
+ var buffer = new NetworkBuffer();
+ assertEquals(0, buffer.readableBytes());
+
+ buffer.write(NetworkBuffer.BYTE, (byte) 0);
+ assertEquals(1, buffer.readableBytes());
+
+ buffer.write(NetworkBuffer.LONG, 50L);
+ assertEquals(9, buffer.readableBytes());
+
+ assertEquals((byte) 0, buffer.read(NetworkBuffer.BYTE));
+ assertEquals(8, buffer.readableBytes());
+
+ assertEquals(50L, buffer.read(NetworkBuffer.LONG));
+ assertEquals(0, buffer.readableBytes());
+ }
+
+ @Test
+ public void numbers() {
+ assertBufferType(NetworkBuffer.BOOLEAN, false, new byte[]{0x00});
+ assertBufferType(NetworkBuffer.BOOLEAN, true, new byte[]{0x01});
+
+ assertBufferType(NetworkBuffer.BYTE, (byte) 0x00, new byte[]{0x00});
+ assertBufferType(NetworkBuffer.BYTE, (byte) 0x01, new byte[]{0x01});
+ assertBufferType(NetworkBuffer.BYTE, (byte) 0x7F, new byte[]{0x7F});
+ assertBufferType(NetworkBuffer.BYTE, (byte) 0x80, new byte[]{(byte) 0x80});
+ assertBufferType(NetworkBuffer.BYTE, (byte) 0xFF, new byte[]{(byte) 0xFF});
+
+ assertBufferType(NetworkBuffer.SHORT, (short) 0x0000, new byte[]{0x00, 0x00});
+ assertBufferType(NetworkBuffer.SHORT, (short) 0x0001, new byte[]{0x00, 0x01});
+ assertBufferType(NetworkBuffer.SHORT, (short) 0x7FFF, new byte[]{0x7F, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.SHORT, (short) 0x8000, new byte[]{(byte) 0x80, 0x00});
+ assertBufferType(NetworkBuffer.SHORT, (short) 0xFFFF, new byte[]{(byte) 0xFF, (byte) 0xFF});
+
+ assertBufferType(NetworkBuffer.INT, 0, new byte[]{0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.INT, 1, new byte[]{0x00, 0x00, 0x00, 0x01});
+ assertBufferType(NetworkBuffer.INT, 2, new byte[]{0x00, 0x00, 0x00, 0x02});
+ assertBufferType(NetworkBuffer.INT, 127, new byte[]{0x00, 0x00, 0x00, 0x7F});
+ assertBufferType(NetworkBuffer.INT, 128, new byte[]{0x00, 0x00, 0x00, (byte) 0x80});
+ assertBufferType(NetworkBuffer.INT, 255, new byte[]{0x00, 0x00, 0x00, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.INT, 256, new byte[]{0x00, 0x00, 0x01, 0x00});
+ assertBufferType(NetworkBuffer.INT, 25565, new byte[]{0x00, 0x00, 0x63, (byte) 0xDD});
+ assertBufferType(NetworkBuffer.INT, 32767, new byte[]{0x00, 0x00, 0x7F, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.INT, 32768, new byte[]{0x00, 0x00, (byte) 0x80, 0x00});
+ assertBufferType(NetworkBuffer.INT, 65535, new byte[]{0x00, 0x00, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.INT, 65536, new byte[]{0x00, 0x01, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.INT, 2147483647, new byte[]{0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.INT, -1, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.INT, -2147483648, new byte[]{(byte) 0x80, 0x00, 0x00, 0x00});
+
+ assertBufferType(NetworkBuffer.LONG, 0L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 1L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
+ assertBufferType(NetworkBuffer.LONG, 2L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02});
+ assertBufferType(NetworkBuffer.LONG, 127L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F});
+ assertBufferType(NetworkBuffer.LONG, 128L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x80});
+ assertBufferType(NetworkBuffer.LONG, 255L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, 256L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 25565L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, (byte) 0xDD});
+ assertBufferType(NetworkBuffer.LONG, 32767L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, 32768L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x80, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 65535L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, 65536L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 2147483647L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, 2147483648L, new byte[]{0x00, 0x00, 0x00, 0x00, (byte) 0x80, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 4294967295L, new byte[]{0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, 4294967296L, new byte[]{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, 9223372036854775807L, new byte[]{0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, -1L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
+ assertBufferType(NetworkBuffer.LONG, -2147483648L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x80, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, -4294967296L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.LONG, -9223372036854775808L, new byte[]{(byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+
+ assertBufferType(NetworkBuffer.FLOAT, 0f, new byte[]{0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 1f, new byte[]{0x3F, (byte) 0x80, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 1.1f, new byte[]{0x3F, (byte) 0x8C, (byte) 0xCC, (byte) 0xCD});
+ assertBufferType(NetworkBuffer.FLOAT, 1.5f, new byte[]{0x3F, (byte) 0xC0, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 1.6f, new byte[]{0x3F, (byte) 0xCC, (byte) 0xCC, (byte) 0xCD});
+ assertBufferType(NetworkBuffer.FLOAT, 2f, new byte[]{0x40, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 2.5f, new byte[]{0x40, 0x20, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 3f, new byte[]{0x40, 0x40, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 4f, new byte[]{0x40, (byte) 0x80, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 5f, new byte[]{0x40, (byte) 0xA0, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 10f, new byte[]{0x41, 0x20, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 100f, new byte[]{0x42, (byte) 0xC8, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 1000f, new byte[]{0x44, 0x7a, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 10000f, new byte[]{0x46, 0x1C, 0x40, 0x00});
+ assertBufferType(NetworkBuffer.FLOAT, 100000f, new byte[]{0x47, (byte) 0xC3, 0x50, 0x00});
+
+ assertBufferType(NetworkBuffer.DOUBLE, 0d, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 1d, new byte[]{0x3F, (byte) 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 1.1d, new byte[]{0x3F, (byte) 0xF1, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x9A});
+ assertBufferType(NetworkBuffer.DOUBLE, 1.5d, new byte[]{0x3F, (byte) 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 1.6d, new byte[]{0x3F, (byte) 0xF9, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x9A});
+ assertBufferType(NetworkBuffer.DOUBLE, 2d, new byte[]{0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 2.5d, new byte[]{0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 3d, new byte[]{0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 4d, new byte[]{0x40, 0x10, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 5d, new byte[]{0x40, 0x14, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 10d, new byte[]{0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 100d, new byte[]{0x40, 0x59, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 1000d, new byte[]{0x40, (byte) 0x8F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+ assertBufferType(NetworkBuffer.DOUBLE, 10000d, new byte[]{0x40, (byte) 0xC3, (byte) 0x88, 0x00, 0x00, 0x00, 0x00, 0x00});
+ }
+
+ @Test
+ public void varInt() {
+ assertBufferType(NetworkBuffer.VAR_INT, 0, new byte[]{0});
+ assertBufferType(NetworkBuffer.VAR_INT, 1, new byte[]{0x01});
+ assertBufferType(NetworkBuffer.VAR_INT, 2, new byte[]{0x02});
+ assertBufferType(NetworkBuffer.VAR_INT, 11, new byte[]{0x0B});
+ assertBufferType(NetworkBuffer.VAR_INT, 127, new byte[]{0x7f});
+ assertBufferType(NetworkBuffer.VAR_INT, 128, new byte[]{(byte) 0x80, 0x01});
+ assertBufferType(NetworkBuffer.VAR_INT, 255, new byte[]{(byte) 0xff, 0x01});
+ assertBufferType(NetworkBuffer.VAR_INT, 25565, new byte[]{(byte) 0xdd, (byte) 0xc7, 0x01});
+ assertBufferType(NetworkBuffer.VAR_INT, 2097151, new byte[]{(byte) 0xff, (byte) 0xff, 0x7f});
+ assertBufferType(NetworkBuffer.VAR_INT, 2147483647, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07});
+ assertBufferType(NetworkBuffer.VAR_INT, -1, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x0f});
+ assertBufferType(NetworkBuffer.VAR_INT, -2147483648, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x08});
+ }
+
+ @Test
+ public void varLong() {
+ assertBufferType(NetworkBuffer.VAR_LONG, 0L, new byte[]{0});
+ assertBufferType(NetworkBuffer.VAR_LONG, 1L, new byte[]{0x01});
+ assertBufferType(NetworkBuffer.VAR_LONG, 2L, new byte[]{0x02});
+ assertBufferType(NetworkBuffer.VAR_LONG, 127L, new byte[]{0x7f});
+ assertBufferType(NetworkBuffer.VAR_LONG, 128L, new byte[]{(byte) 0x80, 0x01});
+ assertBufferType(NetworkBuffer.VAR_LONG, 255L, new byte[]{(byte) 0xff, 0x01});
+ assertBufferType(NetworkBuffer.VAR_LONG, 2147483647L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07});
+ assertBufferType(NetworkBuffer.VAR_LONG, 9223372036854775807L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x7f});
+ assertBufferType(NetworkBuffer.VAR_LONG, -1L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x01});
+ assertBufferType(NetworkBuffer.VAR_LONG, -2147483648L, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x01});
+ assertBufferType(NetworkBuffer.VAR_LONG, -9223372036854775808L, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01});
+ }
+
+ @Test
+ public void rawBytes() {
+ // FIXME: currently break because the array is identity compared
+ //assertBufferType(NetworkBuffer.RAW_BYTES, new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64},
+ // new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64});
+ }
+
+ @Test
+ public void string() {
+ assertBufferType(NetworkBuffer.STRING, "Hello World", new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64});
+ }
+
+ @Test
+ public void nbt() {
+ assertBufferType(NetworkBuffer.NBT, NBT.Int(5));
+ assertBufferType(NetworkBuffer.NBT, NBT.Compound(Map.of("key", NBT.Int(5))));
+ }
+
+ @Test
+ public void component() {
+ assertBufferType(NetworkBuffer.COMPONENT, Component.text("Hello world"));
+ }
+
+ @Test
+ public void uuid() {
+ assertBufferType(NetworkBuffer.UUID, new UUID(0, 0), new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+ assertBufferType(NetworkBuffer.UUID, new UUID(1, 1), new byte[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1});
+ }
+
+ @Test
+ public void item() {
+ assertBufferType(NetworkBuffer.ITEM, ItemStack.AIR);
+ assertBufferType(NetworkBuffer.ITEM, ItemStack.of(Material.STONE, 1));
+ assertBufferType(NetworkBuffer.ITEM, ItemStack.of(Material.DIAMOND_AXE, 1).withMeta(builder -> builder.damage(1)));
+ }
+
+ @Test
+ public void optional() {
+ assertBufferTypeOptional(NetworkBuffer.BOOLEAN, null, new byte[]{0});
+ assertBufferTypeOptional(NetworkBuffer.BOOLEAN, true, new byte[]{1, 1});
+ }
+
+ @Test
+ public void collection() {
+ assertBufferTypeCollection(NetworkBuffer.BOOLEAN, List.of(), new byte[]{0});
+ assertBufferTypeCollection(NetworkBuffer.BOOLEAN, List.of(true), new byte[]{0x01, 0x01});
+ }
+
+ static void assertBufferType(NetworkBuffer.@NotNull Type type, @UnknownNullability T value, byte[] expected, @NotNull Action action) {
+ var buffer = new NetworkBuffer();
+ action.write(buffer, type, value);
+ assertEquals(0, buffer.readIndex());
+ if (expected != null) assertEquals(expected.length, buffer.writeIndex());
+
+ var actual = action.read(buffer, type);
+
+ assertEquals(value, actual);
+ if (expected != null) assertEquals(expected.length, buffer.readIndex(), "Invalid read index");
+ if (expected != null) assertEquals(expected.length, buffer.writeIndex());
+
+ if (expected != null) {
+ var bytes = new byte[expected.length];
+ buffer.copyTo(0, bytes, 0, bytes.length);
+ assertArrayEquals(expected, bytes, "Invalid bytes: " + Arrays.toString(expected) + " != " + Arrays.toString(bytes));
+ }
+ }
+
+ static void assertBufferType(NetworkBuffer.@NotNull Type type, @NotNull T value, byte @Nullable [] expected) {
+ assertBufferType(type, value, expected, new Action<>() {
+ @Override
+ public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type type, @UnknownNullability T value) {
+ buffer.write(type, value);
+ }
+
+ @Override
+ public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type type) {
+ return buffer.read(type);
+ }
+ });
+ }
+
+ static void assertBufferType(NetworkBuffer.@NotNull Type type, @NotNull T value) {
+ assertBufferType(type, value, null);
+ }
+
+ static void assertBufferTypeOptional(NetworkBuffer.@NotNull Type type, @Nullable T value, byte @Nullable [] expected) {
+ assertBufferType(type, value, expected, new Action() {
+ @Override
+ public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type type, @UnknownNullability T value) {
+ buffer.writeOptional(type, value);
+ }
+
+ @Override
+ public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type type) {
+ return buffer.readOptional(type);
+ }
+ });
+ }
+
+ static void assertBufferTypeOptional(NetworkBuffer.@NotNull Type type, @Nullable T value) {
+ assertBufferTypeOptional(type, value, null);
+ }
+
+ static void assertBufferTypeCollection(NetworkBuffer.@NotNull Type type, @NotNull Collection values, byte @Nullable [] expected) {
+ var buffer = new NetworkBuffer();
+ buffer.writeCollection(type, values);
+ assertEquals(0, buffer.readIndex());
+ if (expected != null) assertEquals(expected.length, buffer.writeIndex());
+
+ var actual = buffer.readCollection(type);
+
+ assertEquals(values, actual);
+ if (expected != null) assertEquals(expected.length, buffer.readIndex());
+ if (expected != null) assertEquals(expected.length, buffer.writeIndex());
+
+ if (expected != null) {
+ var bytes = new byte[expected.length];
+ buffer.copyTo(0, bytes, 0, bytes.length);
+ assertArrayEquals(expected, bytes, "Invalid bytes: " + Arrays.toString(expected) + " != " + Arrays.toString(bytes));
+ }
+ }
+
+ static void assertBufferTypeCollection(NetworkBuffer.@NotNull Type type, @NotNull Collection value) {
+ assertBufferTypeCollection(type, value, null);
+ }
+
+ interface Action {
+ void write(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type type, @UnknownNullability T value);
+
+ T read(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type type);
+ }
+}
diff --git a/src/test/java/net/minestom/server/network/SocketWriteTest.java b/src/test/java/net/minestom/server/network/SocketWriteTest.java
new file mode 100644
index 000000000..481305f40
--- /dev/null
+++ b/src/test/java/net/minestom/server/network/SocketWriteTest.java
@@ -0,0 +1,107 @@
+package net.minestom.server.network;
+
+import net.minestom.server.network.packet.server.ServerPacket;
+import net.minestom.server.utils.ObjectPool;
+import net.minestom.server.utils.PacketUtils;
+import net.minestom.server.utils.Utils;
+import net.minestom.server.utils.binary.BinaryWriter;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+public class SocketWriteTest {
+
+ record IntPacket(int value) implements ServerPacket {
+ @Override
+ public void write(@NotNull BinaryWriter writer) {
+ writer.writeInt(value);
+ }
+
+ @Override
+ public int getId() {
+ return 1;
+ }
+ }
+
+ record CompressiblePacket(String value) implements ServerPacket {
+ @Override
+ public void write(@NotNull BinaryWriter writer) {
+ writer.writeSizedString(value);
+ }
+
+ @Override
+ public int getId() {
+ return 1;
+ }
+ }
+
+ @Test
+ public void writeSingleUncompressed() {
+ var packet = new IntPacket(5);
+
+ var buffer = ObjectPool.PACKET_POOL.get();
+ PacketUtils.writeFramedPacket(buffer, packet, false);
+
+ // 3 bytes length [var-int] + 1 byte packet id [var-int] + 4 bytes int
+ // The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
+ assertEquals(3 + 1 + 4, buffer.position(), "Invalid buffer position");
+ }
+
+ @Test
+ public void writeMultiUncompressed() {
+ var packet = new IntPacket(5);
+
+ var buffer = ObjectPool.PACKET_POOL.get();
+ PacketUtils.writeFramedPacket(buffer, packet, false);
+ PacketUtils.writeFramedPacket(buffer, packet, false);
+
+ // 3 bytes length [var-int] + 1 byte packet id [var-int] + 4 bytes int
+ // The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
+ assertEquals((3 + 1 + 4) * 2, buffer.position(), "Invalid buffer position");
+ }
+
+ @Test
+ public void writeSingleCompressed() {
+ var string = "Hello world!".repeat(200);
+ var stringLength = string.getBytes(StandardCharsets.UTF_8).length;
+ var lengthLength = Utils.getVarIntSize(stringLength);
+
+ var packet = new CompressiblePacket(string);
+
+ var buffer = ObjectPool.PACKET_POOL.get();
+ PacketUtils.writeFramedPacket(buffer, packet, true);
+
+ // 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + payload
+ // The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
+ assertNotEquals(3 + 3 + 1 + lengthLength + stringLength, buffer.position(), "Buffer position does not account for compression");
+ }
+
+ @Test
+ public void writeSingleCompressedSmall() {
+ var packet = new IntPacket(5);
+
+ var buffer = ObjectPool.PACKET_POOL.get();
+ PacketUtils.writeFramedPacket(buffer, packet, true);
+
+ // 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + 4 bytes int
+ // The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
+ assertEquals(3 + 3 + 1 + 4, buffer.position(), "Invalid buffer position");
+ }
+
+ @Test
+ public void writeMultiCompressedSmall() {
+ var packet = new IntPacket(5);
+
+ var buffer = ObjectPool.PACKET_POOL.get();
+ PacketUtils.writeFramedPacket(buffer, packet, true);
+ PacketUtils.writeFramedPacket(buffer, packet, true);
+
+ // 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + 4 bytes int
+ // The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
+ assertEquals((3 + 3 + 1 + 4) * 2, buffer.position(), "Invalid buffer position");
+ }
+}