Buffer API rework (#1485)

This commit is contained in:
TheMode 2022-10-28 19:27:48 +02:00 committed by GitHub
parent c3df2af306
commit ba2816fc74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1077 additions and 596 deletions

View File

@ -142,7 +142,7 @@ public class BasicQueryResponse implements Writeable {
writer.writeNullTerminatedString(this.map, Query.CHARSET); writer.writeNullTerminatedString(this.map, Query.CHARSET);
writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET); writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET);
writer.writeNullTerminatedString(this.maxPlayers, 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); writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
} }
} }

View File

@ -1,19 +1,17 @@
package net.minestom.server.extras.velocity; package net.minestom.server.extras.velocity;
import net.minestom.server.MinecraftServer; import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import static net.minestom.server.network.NetworkBuffer.*;
/** /**
* Support for <a href="https://velocitypowered.com/">Velocity</a> modern forwarding. * Support for <a href="https://velocitypowered.com/">Velocity</a> modern forwarding.
* <p> * <p>
@ -47,12 +45,14 @@ public final class VelocityProxy {
return enabled; return enabled;
} }
public static boolean checkIntegrity(@NotNull BinaryReader reader) { public static boolean checkIntegrity(NetworkBuffer buffer) {
final byte[] signature = reader.readBytes(32); final byte[] signature = new byte[32];
ByteBuffer buf = reader.getBuffer(); for (int i = 0; i < signature.length; i++) {
buf.mark(); signature[i] = buffer.read(BYTE);
final byte[] data = reader.readRemainingBytes(); }
buf.reset(); final int index = buffer.readIndex();
final byte[] data = buffer.read(RAW_BYTES);
buffer.readIndex(index);
try { try {
Mac mac = Mac.getInstance(MAC_ALGORITHM); Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(key); mac.init(key);
@ -63,16 +63,7 @@ public final class VelocityProxy {
} catch (NoSuchAlgorithmException | InvalidKeyException e) { } catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace(); e.printStackTrace();
} }
final int version = reader.readVarInt(); final int version = buffer.read(VAR_INT);
return version == SUPPORTED_FORWARDING_VERSION; 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;
}
}
} }

View File

@ -20,7 +20,6 @@ import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.ObjectPool; import net.minestom.server.utils.ObjectPool;
import net.minestom.server.utils.Utils;
import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome; import net.minestom.server.world.biomes.Biome;
@ -200,8 +199,8 @@ public class DynamicChunk extends Chunk {
} }
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight); final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
heightmapsNBT = NBT.Compound(Map.of( heightmapsNBT = NBT.Compound(Map.of(
"MOTION_BLOCKING", NBT.LongArray(Utils.encodeBlocks(motionBlocking, bitsForHeight)), "MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
"WORLD_SURFACE", NBT.LongArray(Utils.encodeBlocks(worldSurface, bitsForHeight)))); "WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
} }
// Data // Data
final byte[] data = ObjectPool.PACKET_POOL.use(buffer -> { final byte[] data = ObjectPool.PACKET_POOL.use(buffer -> {
@ -265,4 +264,47 @@ public class DynamicChunk extends Chunk {
private void assertLock() { private void assertLock() {
assert Thread.holdsLock(this) : "Chunk must be locked before access"; 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;
}
} }

View File

@ -42,10 +42,7 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
writer.writeByte((byte) 0); writer.writeByte((byte) 0);
return; return;
} }
BinaryWriter w = new BinaryWriter(); writer.writeNBT("", nbt);
w.writeNBT("", nbt);
var cachedBuffer = w.getBuffer();
writer.write(cachedBuffer.flip());
} }
@Override @Override

View File

@ -1,13 +1,14 @@
package net.minestom.server.item; package net.minestom.server.item;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.event.HoverEventSource;
import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.tag.Tag; import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.TagWritable; import net.minestom.server.tag.TagWritable;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.*; import org.jetbrains.annotations.*;
import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTCompound;
@ -167,8 +168,8 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
@Override @Override
default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) { default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(), final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC);
NBTUtils.asBinaryTagHolder(meta().toNBT())))); return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(), tagHolder)));
} }
/** /**

View File

@ -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> BOOLEAN = NetworkBufferTypes.BOOLEAN;
public static final Type<Byte> BYTE = NetworkBufferTypes.BYTE;
public static final Type<Short> SHORT = NetworkBufferTypes.SHORT;
public static final Type<Integer> INT = NetworkBufferTypes.INT;
public static final Type<Long> LONG = NetworkBufferTypes.LONG;
public static final Type<Float> FLOAT = NetworkBufferTypes.FLOAT;
public static final Type<Double> DOUBLE = NetworkBufferTypes.DOUBLE;
public static final Type<Integer> VAR_INT = NetworkBufferTypes.VAR_INT;
public static final Type<Long> VAR_LONG = NetworkBufferTypes.VAR_LONG;
public static final Type<byte[]> RAW_BYTES = NetworkBufferTypes.RAW_BYTES;
public static final Type<String> STRING = NetworkBufferTypes.STRING;
public static final Type<NBT> NBT = NetworkBufferTypes.NBT;
public static final Type<Point> BLOCK_POSITION = NetworkBufferTypes.BLOCK_POSITION;
public static final Type<Component> COMPONENT = NetworkBufferTypes.COMPONENT;
public static final Type<UUID> UUID = NetworkBufferTypes.UUID;
public static final Type<ItemStack> 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 <T> void write(@NotNull Type<T> type, @NotNull T value) {
var impl = (NetworkBufferTypes.TypeImpl<T>) type;
final long length = impl.writer().write(this, value);
if (length != -1) this.writeIndex += length;
}
public <T> @NotNull T read(@NotNull Type<T> type) {
var impl = (NetworkBufferTypes.TypeImpl<T>) type;
return impl.reader().read(this);
}
public <T> void writeOptional(@NotNull Type<T> type, @Nullable T value) {
write(BOOLEAN, value != null);
if (value != null) write(type, value);
}
public <T> @Nullable T readOptional(@NotNull Type<T> type) {
return read(BOOLEAN) ? read(type) : null;
}
public <T> void writeCollection(@NotNull Type<T> 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 <T> void writeCollection(@NotNull Type<T> type, @NotNull T @Nullable ... values) {
writeCollection(type, values == null ? null : List.of(values));
}
public <T> @NotNull Collection<@NotNull T> readCollection(@NotNull Type<T> type) {
final int size = read(VAR_INT);
final Collection<T> 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<T> permits NetworkBufferTypes.TypeImpl {
}
}

View File

@ -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> 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> 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> 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<Integer> 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> 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> 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> 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<Integer> 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<Long> 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<byte[]> 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> 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> 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<Point> 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> 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> 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<ItemStack> 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<T>(@NotNull Class<T> type,
@NotNull TypeWriter<T> writer,
@NotNull TypeReader<T> reader) implements NetworkBuffer.Type<T> {
}
interface TypeWriter<T> {
long write(@NotNull NetworkBuffer buffer, @NotNull T value);
}
interface TypeReader<T> {
@NotNull T read(@NotNull NetworkBuffer buffer);
}
}

View File

@ -27,16 +27,19 @@ public record PacketProcessor(@NotNull ClientPacketsHandler statusHandler,
} }
public @NotNull ClientPacket create(@NotNull ConnectionState connectionState, int packetId, ByteBuffer body) { public @NotNull ClientPacket create(@NotNull ConnectionState connectionState, int packetId, ByteBuffer body) {
BinaryReader binaryReader = new BinaryReader(body); NetworkBuffer networkBuffer = new NetworkBuffer(body);
return switch (connectionState) { BinaryReader reader = new BinaryReader(networkBuffer);
case PLAY -> playHandler.create(packetId, binaryReader); final ClientPacket clientPacket = switch (connectionState) {
case LOGIN -> loginHandler.create(packetId, binaryReader); case PLAY -> playHandler.create(packetId, reader);
case STATUS -> statusHandler.create(packetId, binaryReader); case LOGIN -> loginHandler.create(packetId, reader);
case STATUS -> statusHandler.create(packetId, reader);
case UNKNOWN -> { case UNKNOWN -> {
assert packetId == 0; 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) { public ClientPacket process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.velocity.VelocityProxy; import net.minestom.server.extras.velocity.VelocityProxy;
import net.minestom.server.network.ConnectionManager; 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.client.ClientPreplayPacket;
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.GameProfile;
@ -18,6 +19,10 @@ import org.jetbrains.annotations.Nullable;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; 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 { public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) implements ClientPreplayPacket {
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
@ -41,14 +46,20 @@ public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) i
// Velocity // Velocity
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) { if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
if (data != null && data.length > 0) { if (data != null && data.length > 0) {
BinaryReader reader = new BinaryReader(data); NetworkBuffer buffer = new NetworkBuffer(ByteBuffer.wrap(data));
success = VelocityProxy.checkIntegrity(reader); success = VelocityProxy.checkIntegrity(buffer);
if (success) { if (success) {
// Get the real connection address // 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(); final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
socketAddress = new InetSocketAddress(address, port); socketAddress = new InetSocketAddress(address, port);
gameProfile = new GameProfile(reader); gameProfile = new GameProfile(new BinaryReader(buffer));
} }
} }
} }

View File

@ -3,9 +3,9 @@ package net.minestom.server.tag;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.utils.Utils;
import org.jglrxavpok.hephaistos.nbt.*; import org.jglrxavpok.hephaistos.nbt.*;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -22,8 +22,8 @@ final class Serializers {
static final Entry<String, NBTString> STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String); static final Entry<String, NBTString> STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String);
static final Entry<NBT, NBT> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity()); static final Entry<NBT, NBT> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
static final Entry<java.util.UUID, NBTIntArray> UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> Utils.intArrayToUuid(intArray.getValue().copyArray()), static final Entry<java.util.UUID, NBTIntArray> UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> intArrayToUuid(intArray.getValue().copyArray()),
uuid -> NBT.IntArray(Utils.uuidToIntArray(uuid))); uuid -> NBT.IntArray(uuidToIntArray(uuid)));
static final Entry<ItemStack, NBTCompound> ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT); static final Entry<ItemStack, NBTCompound> ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT);
static final Entry<Component, NBTString> COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()), static final Entry<Component, NBTString> COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()),
component -> NBT.String(GsonComponentSerializer.gson().serialize(component))); component -> NBT.String(GsonComponentSerializer.gson().serialize(component)));
@ -55,4 +55,26 @@ final class Serializers {
return writer.apply(value); 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);
}
} }

View File

@ -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<NBT, String, NBTException, RuntimeException> 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<NBTCompound> 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<NBTCompound> 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<Enchantment, Short> 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);
}
}

View File

@ -17,6 +17,7 @@ import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.*; import net.minestom.server.network.packet.server.*;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.network.player.PlayerSocketConnection;
@ -255,24 +256,26 @@ public final class PacketUtils {
int id, int id,
@NotNull Writeable writeable, @NotNull Writeable writeable,
int compressionThreshold) { 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) { if (compressionThreshold <= 0) {
// Uncompressed format https://wiki.vg/Protocol#Without_compression // Uncompressed format https://wiki.vg/Protocol#Without_compression
final int lengthIndex = Utils.writeEmptyVarIntHeader(buffer); final int lengthIndex = networkBuffer.skipWrite(3);
Utils.writeVarInt(buffer, id); networkBuffer.write(NetworkBuffer.VAR_INT, id);
writeable.write(writerView); writeable.write(legacyWriter);
final int finalSize = buffer.position() - (lengthIndex + 3); final int finalSize = networkBuffer.writeIndex() - (lengthIndex + 3);
Utils.writeVarIntHeader(buffer, lengthIndex, finalSize); Utils.writeVarIntHeader(buffer, lengthIndex, finalSize);
buffer.position(networkBuffer.writeIndex());
return; return;
} }
// Compressed format https://wiki.vg/Protocol#With_compression // Compressed format https://wiki.vg/Protocol#With_compression
final int compressedIndex = Utils.writeEmptyVarIntHeader(buffer); final int compressedIndex = networkBuffer.skipWrite(3);
final int uncompressedIndex = Utils.writeEmptyVarIntHeader(buffer); final int uncompressedIndex = networkBuffer.skipWrite(3);
final int contentStart = buffer.position(); final int contentStart = networkBuffer.writeIndex();
Utils.writeVarInt(buffer, id); networkBuffer.write(NetworkBuffer.VAR_INT, id);
writeable.write(writerView); writeable.write(legacyWriter);
final int packetSize = buffer.position() - contentStart; final int packetSize = networkBuffer.writeIndex() - contentStart;
final boolean compressed = packetSize >= compressionThreshold; final boolean compressed = packetSize >= compressionThreshold;
if (compressed) { if (compressed) {
// Packet large enough, compress it // Packet large enough, compress it
@ -283,11 +286,15 @@ public final class PacketUtils {
deflater.finish(); deflater.finish();
deflater.deflate(buffer.position(contentStart)); deflater.deflate(buffer.position(contentStart));
deflater.reset(); deflater.reset();
networkBuffer.skipWrite(buffer.position() - contentStart);
} }
} }
// Packet header (Packet + Data Length) // 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); Utils.writeVarIntHeader(buffer, uncompressedIndex, compressed ? packetSize : 0);
buffer.position(networkBuffer.writeIndex());
} }
@ApiStatus.Internal @ApiStatus.Internal

View File

@ -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);
}
}

View File

@ -45,12 +45,6 @@ public final class Utils {
buffer.put(startIndex + 2, (byte) (value >>> 14)); 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) { public static int readVarInt(ByteBuffer buf) {
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393 // https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
int result = 0; 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;
}
} }

View File

@ -2,21 +2,14 @@ package net.minestom.server.utils.binary;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Point;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.Either; 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 net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
import org.jglrxavpok.hephaistos.nbt.NBT; 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.io.InputStream;
import java.nio.BufferUnderflowException; import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -27,17 +20,22 @@ import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import static net.minestom.server.network.NetworkBuffer.*;
/** /**
* Class used to read from a byte array. * Class used to read from a byte array.
* <p> * <p>
* WARNING: not thread-safe. * WARNING: not thread-safe.
*/ */
public class BinaryReader extends InputStream { public class BinaryReader extends InputStream {
private final ByteBuffer buffer; private final NetworkBuffer buffer;
private NBTReader nbtReader = null;
public BinaryReader(NetworkBuffer buffer) {
this.buffer = buffer;
}
public BinaryReader(@NotNull ByteBuffer buffer) { public BinaryReader(@NotNull ByteBuffer buffer) {
this.buffer = buffer; this.buffer = new NetworkBuffer(buffer);
} }
public BinaryReader(byte[] bytes) { public BinaryReader(byte[] bytes) {
@ -45,57 +43,53 @@ public class BinaryReader extends InputStream {
} }
public int readVarInt() { public int readVarInt() {
return Utils.readVarInt(buffer); return buffer.read(VAR_INT);
} }
public long readVarLong() { public long readVarLong() {
return Utils.readVarLong(buffer); return buffer.read(VAR_LONG);
} }
public boolean readBoolean() { public boolean readBoolean() {
return buffer.get() == 1; return buffer.read(BOOLEAN);
} }
public byte readByte() { public byte readByte() {
return buffer.get(); return buffer.read(BYTE);
} }
public short readShort() { public short readShort() {
return buffer.getShort(); return buffer.read(SHORT);
}
public char readChar() {
return buffer.getChar();
} }
public int readUnsignedShort() { public int readUnsignedShort() {
return buffer.getShort() & 0xFFFF; return buffer.read(SHORT) & 0xFFFF;
} }
/** /**
* Same as readInt * Same as readInt
*/ */
public int readInteger() { public int readInteger() {
return buffer.getInt(); return buffer.read(INT);
} }
/** /**
* Same as readInteger, created for parity with BinaryWriter * Same as readInteger, created for parity with BinaryWriter
*/ */
public int readInt() { public int readInt() {
return buffer.getInt(); return buffer.read(INT);
} }
public long readLong() { public long readLong() {
return buffer.getLong(); return buffer.read(LONG);
} }
public float readFloat() { public float readFloat() {
return buffer.getFloat(); return buffer.read(FLOAT);
} }
public double readDouble() { public double readDouble() {
return buffer.getDouble(); return buffer.read(DOUBLE);
} }
/** /**
@ -112,9 +106,11 @@ public class BinaryReader extends InputStream {
final int length = readVarInt(); final int length = readVarInt();
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
try { try {
this.buffer.get(bytes); for (int i = 0; i < length; i++) {
bytes[i] = readByte();
}
} catch (BufferUnderflowException e) { } 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); final String str = new String(bytes, StandardCharsets.UTF_8);
Check.stateCondition(str.length() > maxLength, Check.stateCondition(str.length() > maxLength,
@ -123,12 +119,14 @@ public class BinaryReader extends InputStream {
} }
public String readSizedString() { public String readSizedString() {
return readSizedString(Integer.MAX_VALUE); return buffer.read(STRING);
} }
public byte[] readBytes(int length) { public byte[] readBytes(int length) {
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
buffer.get(bytes); for (int i = 0; i < length; i++) {
bytes[i] = readByte();
}
return bytes; return bytes;
} }
@ -177,27 +175,19 @@ public class BinaryReader extends InputStream {
} }
public byte[] readRemainingBytes() { public byte[] readRemainingBytes() {
return readBytes(available()); return buffer.read(RAW_BYTES);
} }
public Point readBlockPosition() { public Point readBlockPosition() {
return SerializerUtils.longToBlockPosition(buffer.getLong()); return buffer.read(BLOCK_POSITION);
} }
public UUID readUuid() { 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() { public ItemStack readItemStack() {
final ItemStack itemStack = NBTUtils.readItemStack(this); return buffer.read(ITEM);
Check.notNull(itemStack, "#readSlot returned null, probably because the buffer was corrupted");
return itemStack;
} }
public Component readComponent(int maxLength) { public Component readComponent(int maxLength) {
@ -206,7 +196,7 @@ public class BinaryReader extends InputStream {
} }
public Component readComponent() { public Component readComponent() {
return readComponent(Integer.MAX_VALUE); return buffer.read(COMPONENT);
} }
/** /**
@ -263,10 +253,6 @@ public class BinaryReader extends InputStream {
return list; return list;
} }
public ByteBuffer getBuffer() {
return buffer;
}
@Override @Override
public int read() { public int read() {
return readByte() & 0xFF; return readByte() & 0xFF;
@ -274,21 +260,11 @@ public class BinaryReader extends InputStream {
@Override @Override
public int available() { public int available() {
return buffer.remaining(); return buffer.readableBytes();
} }
public NBT readTag() { public NBT readTag() {
NBTReader reader = this.nbtReader; return buffer.read(NBT);
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();
}
} }
/** /**
@ -299,11 +275,12 @@ public class BinaryReader extends InputStream {
* @param extractor the extraction code, simply call the reader's read* methods here. * @param extractor the extraction code, simply call the reader's read* methods here.
*/ */
public byte[] extractBytes(Runnable extractor) { public byte[] extractBytes(Runnable extractor) {
int startingPosition = buffer.position(); int startingPosition = buffer.readIndex();
extractor.run(); extractor.run();
int endingPosition = getBuffer().position(); int endingPosition = buffer.readIndex();
byte[] output = new byte[endingPosition - startingPosition]; byte[] output = new byte[endingPosition - startingPosition];
buffer.get(startingPosition, output); buffer.copyTo(buffer.readIndex(), output, 0, output.length);
//buffer.get(startingPosition, output);
return output; return output;
} }
} }

View File

@ -1,49 +1,42 @@
package net.minestom.server.utils.binary; package net.minestom.server.utils.binary;
import net.kyori.adventure.text.Component; 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.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.Either; 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.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
import org.jglrxavpok.hephaistos.nbt.NBT; import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import static net.minestom.server.network.NetworkBuffer.*;
/** /**
* Class used to write to a byte array. * Class used to write to a byte array.
* WARNING: not thread-safe. * WARNING: not thread-safe.
*/ */
public class BinaryWriter extends OutputStream { public class BinaryWriter extends OutputStream {
private ByteBuffer buffer; private final NetworkBuffer buffer;
private NBTWriter nbtWriter; // Lazily initialized
private final boolean resizable; public BinaryWriter(NetworkBuffer buffer) {
this.buffer = buffer;
}
private BinaryWriter(ByteBuffer buffer, boolean resizable) { private BinaryWriter(ByteBuffer buffer, boolean resizable) {
this.buffer = buffer; this.buffer = new NetworkBuffer(buffer, resizable);
this.resizable = resizable;
} }
public BinaryWriter(@NotNull ByteBuffer buffer) { public BinaryWriter(@NotNull ByteBuffer buffer) {
this.buffer = buffer; this.buffer = new NetworkBuffer(buffer);
this.resizable = true;
} }
public BinaryWriter(int initialCapacity) { public BinaryWriter(int initialCapacity) {
@ -59,158 +52,55 @@ public class BinaryWriter extends OutputStream {
return new BinaryWriter(buffer, false); 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) { 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) { public void writeByte(byte b) {
ensureSize(Byte.BYTES); this.buffer.write(BYTE, b);
buffer.put(b);
} }
/**
* Writes a single boolean to the buffer.
*
* @param b the boolean to write
*/
public void writeBoolean(boolean b) { 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) { public void writeShort(short s) {
ensureSize(Short.BYTES); this.buffer.write(SHORT, s);
buffer.putShort(s);
} }
/**
* Writes a single int to the buffer.
*
* @param i the int to write
*/
public void writeInt(int i) { public void writeInt(int i) {
ensureSize(Integer.BYTES); this.buffer.write(INT, i);
buffer.putInt(i);
} }
/**
* Writes a single long to the buffer.
*
* @param l the long to write
*/
public void writeLong(long l) { public void writeLong(long l) {
ensureSize(Long.BYTES); this.buffer.write(LONG, l);
buffer.putLong(l);
} }
/**
* Writes a single float to the buffer.
*
* @param f the float to write
*/
public void writeFloat(float f) { public void writeFloat(float f) {
ensureSize(Float.BYTES); this.buffer.write(FLOAT, f);
buffer.putFloat(f);
} }
/**
* Writes a single double to the buffer.
*
* @param d the double to write
*/
public void writeDouble(double d) { public void writeDouble(double d) {
ensureSize(Double.BYTES); this.buffer.write(DOUBLE, d);
buffer.putDouble(d);
} }
/**
* Writes a single var-int to the buffer.
*
* @param i the int to write
*/
public void writeVarInt(int i) { public void writeVarInt(int i) {
ensureSize(5); this.buffer.write(VAR_INT, i);
Utils.writeVarInt(buffer, i);
} }
/**
* Writes a single var-long to the buffer.
*
* @param l the long to write
*/
public void writeVarLong(long l) { public void writeVarLong(long l) {
ensureSize(10); this.buffer.write(VAR_LONG, l);
Utils.writeVarLong(buffer, l);
} }
/**
* Writes a string to the buffer.
* <p>
* The size is a var-int type.
*
* @param string the string to write
*/
public void writeSizedString(@NotNull String string) { public void writeSizedString(@NotNull String string) {
final var bytes = string.getBytes(StandardCharsets.UTF_8); this.buffer.write(STRING, string);
writeVarInt(bytes.length);
writeBytes(bytes);
} }
/**
* 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) { public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
final var bytes = (string + '\0').getBytes(charset); final var bytes = (string + '\0').getBytes(charset);
writeBytes(bytes); writeBytes(bytes);
} }
/**
* Writes a var-int array to the buffer.
* <p>
* It is sized by another var-int at the beginning.
*
* @param array the integers to write
*/
public void writeVarIntArray(int[] array) { public void writeVarIntArray(int[] array) {
if (array == null) { if (array == null) {
writeVarInt(0); writeVarInt(0);
@ -253,46 +143,16 @@ public class BinaryWriter extends OutputStream {
writeBytes(array); writeBytes(array);
} }
/**
* Writes a byte array.
* <p>
* WARNING: it doesn't write the length of {@code bytes}.
*
* @param bytes the byte array to write
*/
public void writeBytes(byte @NotNull [] bytes) { public void writeBytes(byte @NotNull [] bytes) {
if (bytes.length == 0) return; this.buffer.write(RAW_BYTES, bytes);
ensureSize(bytes.length);
buffer.put(bytes);
} }
/**
* Writes a string to the buffer.
* <p>
* 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) { public void writeStringArray(@NotNull String[] array) {
if (array == null) { this.buffer.writeCollection(STRING, array);
writeVarInt(0);
return;
}
writeVarInt(array.length);
for (String element : array) {
writeSizedString(element);
}
} }
/**
* 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) { public void writeUuid(@NotNull UUID uuid) {
writeLong(uuid.getMostSignificantBits()); this.buffer.write(UUID, uuid);
writeLong(uuid.getLeastSignificantBits());
} }
public void writeBlockPosition(@NotNull Point point) { public void writeBlockPosition(@NotNull Point point) {
@ -300,30 +160,15 @@ public class BinaryWriter extends OutputStream {
} }
public void writeBlockPosition(int x, int y, int z) { 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) { public void writeItemStack(@NotNull ItemStack itemStack) {
if (itemStack.isAir()) { this.buffer.write(ITEM, itemStack);
writeBoolean(false);
} else {
writeBoolean(true);
writeVarInt(itemStack.material().id());
writeByte((byte) itemStack.amount());
write(itemStack.meta());
}
} }
public void writeNBT(@NotNull String name, @NotNull NBT tag) { public void writeNBT(@NotNull String name, @NotNull NBT tag) {
if (nbtWriter == null) { this.buffer.write(NBT, tag);
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);
}
} }
public <L, R> void writeEither(Either<L, R> either, BiConsumer<BinaryWriter, L> leftWriter, BiConsumer<BinaryWriter, R> rightWriter) { public <L, R> void writeEither(Either<L, R> either, BiConsumer<BinaryWriter, L> leftWriter, BiConsumer<BinaryWriter, R> rightWriter) {
@ -346,12 +191,13 @@ public class BinaryWriter extends OutputStream {
} }
public void write(@NotNull ByteBuffer buffer) { public void write(@NotNull ByteBuffer buffer) {
ensureSize(buffer.remaining()); byte[] remaining = new byte[buffer.remaining()];
this.buffer.put(buffer); buffer.get(remaining);
writeBytes(remaining);
} }
public void write(@NotNull BinaryWriter writer) { 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 * @return the byte array containing all the {@link BinaryWriter} data
*/ */
public byte[] toByteArray() { public byte[] toByteArray() {
buffer.flip(); byte[] bytes = new byte[buffer.writeIndex()];
byte[] bytes = new byte[buffer.remaining()]; this.buffer.copyTo(0, bytes, 0, bytes.length);
buffer.get(bytes);
return bytes; 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 @Override
public void write(int b) { public void write(int b) {
writeByte((byte) b); writeByte((byte) b);
} }
public void writeUnsignedShort(int yourShort) { public void writeUnsignedShort(int yourShort) {
// FIXME unsigned this.buffer.write(SHORT, (short) (yourShort & 0xFFFF));
ensureSize(Short.BYTES);
buffer.putShort((short) (yourShort & 0xFFFF));
} }
/** /**

View File

@ -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 <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> 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 <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @NotNull T value, byte @Nullable [] expected) {
assertBufferType(type, value, expected, new Action<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type, @UnknownNullability T value) {
buffer.write(type, value);
}
@Override
public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type) {
return buffer.read(type);
}
});
}
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @NotNull T value) {
assertBufferType(type, value, null);
}
static <T> void assertBufferTypeOptional(NetworkBuffer.@NotNull Type<T> type, @Nullable T value, byte @Nullable [] expected) {
assertBufferType(type, value, expected, new Action<T>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type, @UnknownNullability T value) {
buffer.writeOptional(type, value);
}
@Override
public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type) {
return buffer.readOptional(type);
}
});
}
static <T> void assertBufferTypeOptional(NetworkBuffer.@NotNull Type<T> type, @Nullable T value) {
assertBufferTypeOptional(type, value, null);
}
static <T> void assertBufferTypeCollection(NetworkBuffer.@NotNull Type<T> type, @NotNull Collection<T> 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 <T> void assertBufferTypeCollection(NetworkBuffer.@NotNull Type<T> type, @NotNull Collection<T> value) {
assertBufferTypeCollection(type, value, null);
}
interface Action<T> {
void write(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value);
T read(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type<T> type);
}
}

View File

@ -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");
}
}