Minor optimizations for MinecraftCompressorAndLengthEncoder and friends

No need to bounce around changing the writer index, we can just set the value directly.

Also pull out the handshake checks into a separate function, to improve inlining.
This commit is contained in:
Andrew Steinborn
2025-10-18 16:15:22 -04:00
parent 4cd3b68697
commit 70c3eabdb1
4 changed files with 48 additions and 46 deletions

View File

@@ -150,7 +150,7 @@ public enum ProtocolUtils {
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY}; BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
private static final QuietDecoderException BAD_VARINT_CACHED = private static final QuietDecoderException BAD_VARINT_CACHED =
new QuietDecoderException("Bad VarInt decoded"); new QuietDecoderException("Bad VarInt decoded");
private static final int[] VAR_INT_LENGTHS = new int[65]; private static final int[] VAR_INT_LENGTHS = new int[33];
static { static {
for (int i = 0; i <= 32; ++i) { for (int i = 0; i <= 32; ++i) {
@@ -250,16 +250,15 @@ public enum ProtocolUtils {
} }
/** /**
* Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}. * Directly encodes a 21-bit Minecraft VarInt, ready to be written with {@link ByteBuf#writeMedium(int)}.
* The upper 11 bits will be discarded. * The upper 11 bits will be discarded.
* *
* @param buf the buffer to read from * @param value the value to encode
* @param value the integer to write * @return the encoded value
*/ */
public static void write21BitVarInt(ByteBuf buf, int value) { public static int encode21BitVarInt(int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); return (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
} }
public static String readString(ByteBuf buf) { public static String readString(ByteBuf buf) {

View File

@@ -46,7 +46,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
if (uncompressed < threshold) { if (uncompressed < threshold) {
// Under the threshold, there is nothing to do. // Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, uncompressed + 1); ProtocolUtils.writeVarInt(out, uncompressed + 1);
ProtocolUtils.writeVarInt(out, 0); out.writeByte(0);
out.writeBytes(msg); out.writeBytes(msg);
} else { } else {
handleCompressed(ctx, msg, out); handleCompressed(ctx, msg, out);
@@ -57,7 +57,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throws DataFormatException { throws DataFormatException {
int uncompressed = msg.readableBytes(); int uncompressed = msg.readableBytes();
ProtocolUtils.write21BitVarInt(out, 0); // Dummy packet length out.writeMedium(0); // Reserve the packet length
ProtocolUtils.writeVarInt(out, uncompressed); ProtocolUtils.writeVarInt(out, uncompressed);
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
@@ -72,11 +72,8 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet."); throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet.");
} }
int writerIndex = out.writerIndex();
int packetLength = out.readableBytes() - 3; int packetLength = out.readableBytes() - 3;
out.writerIndex(0); out.setMedium(0, ProtocolUtils.encode21BitVarInt(packetLength)); // Rewrite packet length
ProtocolUtils.write21BitVarInt(out, packetLength); // Rewrite packet length
out.writerIndex(writerIndex);
} }
@Override @Override

View File

@@ -83,9 +83,8 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
// try to read the length of the packet // try to read the length of the packet
in.markReaderIndex(); in.markReaderIndex();
int preIndex = in.readerIndex();
int length = readRawVarInt21(in); int length = readRawVarInt21(in);
if (preIndex == in.readerIndex()) { if (packetStart == in.readerIndex()) {
return; return;
} }
if (length < 0) { if (length < 0) {
@@ -94,38 +93,9 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
if (length > 0) { if (length > 0) {
if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) { if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) {
StateRegistry.PacketRegistry.ProtocolRegistry registry = if (validateServerboundHandshakePacket(in, length)) {
state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION);
final int index = in.readerIndex();
final int packetId = readRawVarInt21(in);
// Index hasn't changed, we've read nothing
if (index == in.readerIndex()) {
in.resetReaderIndex();
return; return;
} }
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
MinecraftPacket packet = registry.createPacket(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.expectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, in.readableBytes());
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
}
in.readerIndex(index);
} }
} }
@@ -139,6 +109,41 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
} }
} }
private boolean validateServerboundHandshakePacket(ByteBuf in, int length) throws Exception {
StateRegistry.PacketRegistry.ProtocolRegistry registry =
state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION);
final int index = in.readerIndex();
final int packetId = readRawVarInt21(in);
// Index hasn't changed, we've read nothing
if (index == in.readerIndex()) {
in.resetReaderIndex();
return true;
}
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
MinecraftPacket packet = registry.createPacket(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.expectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, in.readableBytes());
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
}
in.readerIndex(index);
return false;
}
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (MinecraftDecoder.DEBUG) { if (MinecraftDecoder.DEBUG) {

View File

@@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol; package com.velocitypowered.proxy.protocol;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.encode21BitVarInt;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -83,7 +84,7 @@ public class ProtocolUtilsTest {
private void writeReadTest3Bytes(ByteBuf buf, int test) { private void writeReadTest3Bytes(ByteBuf buf, int test) {
buf.clear(); buf.clear();
ProtocolUtils.write21BitVarInt(buf, test); buf.writeMedium(encode21BitVarInt(test));
assertEquals(test, ProtocolUtils.readVarInt(buf)); assertEquals(test, ProtocolUtils.readVarInt(buf));
} }