Skip to content

Commit

Permalink
Merge branch 'dev/3.0.0' of https://github.com/PaperMC/Velocity
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Sep 5, 2024
2 parents 44298c0 + 7848068 commit 1bfa9a6
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,26 @@ public enum ProtocolUtils {
.build();

public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
private static final int MAXIMUM_VARINT_SIZE = 5;
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE,
BinaryTagTypes.BYTE_ARRAY, BinaryTagTypes.STRING, BinaryTagTypes.LIST,
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
private static final QuietDecoderException BAD_VARINT_CACHED =
new QuietDecoderException("Bad VarInt decoded");
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
private static final int[] VAR_INT_LENGTHS = new int[65];

static {
for (int i = 0; i <= 32; ++i) {
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
VAR_INT_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
}
VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0.
VAR_INT_LENGTHS[32] = 1; // Special case for the number 0.
}

private static DecoderException badVarint() {
return MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
: BAD_VARINT_CACHED;
}

/**
Expand All @@ -127,56 +133,29 @@ public enum ProtocolUtils {
* @return the decoded VarInt
*/
public static int readVarInt(ByteBuf buf) {
int read = readVarIntSafely(buf);
if (read == Integer.MIN_VALUE) {
throw MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
: BAD_VARINT_CACHED;
int readable = buf.readableBytes();
if (readable == 0) {
// special case for empty buffer
throw badVarint();
}
return read;
}

/**
* Reads a Minecraft-style VarInt from the specified {@code buf}. The difference between this
* method and {@link #readVarInt(ByteBuf)} is that this function returns a sentinel value if the
* varint is invalid.
*
* @param buf the buffer to read from
* @return the decoded VarInt, or {@code Integer.MIN_VALUE} if the varint is invalid
*/
public static int readVarIntSafely(ByteBuf buf) {
int i = 0;
int maxRead = Math.min(5, buf.readableBytes());
for (int j = 0; j < maxRead; j++) {
int k = buf.readByte();
i |= (k & 0x7F) << j * 7;
if ((k & 0x80) != 128) {
return i;
}
// we can read at least one byte, and this should be a common case
int k = buf.readByte();
if ((k & 0x80) != 128) {
return k;
}
return Integer.MIN_VALUE;
}

/**
* Reads a Minecraft-style VarInt from the specified {@code buf}. The difference between this
* method and {@link #readVarInt(ByteBuf)} is that this function returns a sentinel value if the
* varint is invalid.
*
* @param buf the buffer to read from
* @return the decoded VarInt
* @throws DecoderException if the varint is invalid
*/
public static int readVarIntSafelyOrThrow(ByteBuf buf) {
int i = 0;
int maxRead = Math.min(5, buf.readableBytes());
for (int j = 0; j < maxRead; j++) {
int k = buf.readByte();
// in case decoding one byte was not enough, use a loop to decode up to the next 4 bytes
int maxRead = Math.min(MAXIMUM_VARINT_SIZE, readable);
int i = k & 0x7F;
for (int j = 1; j < maxRead; j++) {
k = buf.readByte();
i |= (k & 0x7F) << j * 7;
if ((k & 0x80) != 128) {
return i;
}
}
throw MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
: BAD_VARINT_CACHED;
throw badVarint();
}

/**
Expand All @@ -186,7 +165,7 @@ public static int readVarIntSafelyOrThrow(ByteBuf buf) {
* @return the byte size of {@code value} if encoded as a VarInt
*/
public static int varIntBytes(int value) {
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
return VAR_INT_LENGTHS[Integer.numberOfLeadingZeros(value)];
}

/**
Expand All @@ -210,6 +189,8 @@ public static void writeVarInt(ByteBuf buf, int value) {

private static void writeVarIntFull(ByteBuf buf, int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/

// This essentially is an unrolled version of the "traditional" VarInt encoding.
if ((value & (0xFFFFFFFF << 7)) == 0) {
buf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

package com.velocitypowered.proxy.protocol.netty;

import com.velocitypowered.proxy.protocol.netty.VarintByteDecoder.DecodeResult;
import static io.netty.util.ByteProcessor.FIND_NON_NUL;

import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
Expand All @@ -29,53 +30,114 @@
*/
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {

private static final QuietDecoderException BAD_LENGTH_CACHED =
private static final QuietDecoderException BAD_PACKET_LENGTH =
new QuietDecoderException("Bad packet length");
private static final QuietDecoderException VARINT_BIG_CACHED =
private static final QuietDecoderException VARINT_TOO_BIG =
new QuietDecoderException("VarInt too big");

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
if (!ctx.channel().isActive()) {
in.clear();
return;
}

final VarintByteDecoder reader = new VarintByteDecoder();
// skip any runs of 0x00 we might find
int packetStart = in.forEachByte(FIND_NON_NUL);
if (packetStart == -1) {
return;
}
in.readerIndex(packetStart);

int varintEnd = in.forEachByte(reader);
if (varintEnd == -1) {
// We tried to go beyond the end of the buffer. This is probably a good sign that the
// buffer was too short to hold a proper varint.
if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
// Special case where the entire packet is just a run of zeroes. We ignore them all.
in.clear();
}
// try to read the length of the packet
in.markReaderIndex();
int preIndex = in.readerIndex();
int length = readRawVarInt21(in);
if (preIndex == in.readerIndex()) {
return;
}
if (length < 0) {
throw BAD_PACKET_LENGTH;
}

if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
// this will return to the point where the next varint starts
in.readerIndex(varintEnd);
} else if (reader.getResult() == DecodeResult.SUCCESS) {
int readVarint = reader.getReadVarint();
int bytesRead = reader.getBytesRead();
if (readVarint < 0) {
in.clear();
throw BAD_LENGTH_CACHED;
} else if (readVarint == 0) {
// skip over the empty packet(s) and ignore it
in.readerIndex(varintEnd + 1);
// note that zero-length packets are ignored
if (length > 0) {
if (in.readableBytes() < length) {
in.resetReaderIndex();
} else {
int minimumRead = bytesRead + readVarint;
if (in.isReadable(minimumRead)) {
out.add(in.retainedSlice(varintEnd + 1, readVarint));
in.skipBytes(minimumRead);
}
out.add(in.readRetainedSlice(length));
}
} else if (reader.getResult() == DecodeResult.TOO_BIG) {
in.clear();
throw VARINT_BIG_CACHED;
}
}

/**
* Reads a VarInt from the buffer of up to 21 bits in size.
*
* @param buffer the buffer to read from
* @return the VarInt decoded, {@code 0} if no varint could be read
* @throws QuietDecoderException if the VarInt is too big to be decoded
*/
private static int readRawVarInt21(ByteBuf buffer) {
if (buffer.readableBytes() < 4) {
// we don't have enough that we can read a potentially full varint, so fall back to
// the slow path.
return readRawVarintSmallBuf(buffer);
}
int wholeOrMore = buffer.getIntLE(buffer.readerIndex());

// take the last three bytes and check if any of them have the high bit set
int atStop = ~wholeOrMore & 0x808080;
if (atStop == 0) {
// all bytes have the high bit set, so the varint we are trying to decode is too wide
throw VARINT_TOO_BIG;
}

int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
buffer.skipBytes(bitsToKeep >> 3);

// remove all bits we don't need to keep, a trick from
// https://github.com/netty/netty/pull/14050#issuecomment-2107750734:
//
// > The idea is that thisVarintMask has 0s above the first one of firstOneOnStop, and 1s at
// > and below it. For example if firstOneOnStop is 0x800080 (where the last 0x80 is the only
// > one that matters), then thisVarintMask is 0xFF.
//
// this is also documented in Hacker's Delight, section 2-1 "Manipulating Rightmost Bits"
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));

// merge together using this trick: https://github.com/netty/netty/pull/14050#discussion_r1597896639
preservedBytes = (preservedBytes & 0x007F007F) | ((preservedBytes & 0x00007F00) >> 1);
preservedBytes = (preservedBytes & 0x00003FFF) | ((preservedBytes & 0x3FFF0000) >> 2);
return preservedBytes;
}

private static int readRawVarintSmallBuf(ByteBuf buffer) {
if (!buffer.isReadable()) {
return 0;
}
buffer.markReaderIndex();

byte tmp = buffer.readByte();
if (tmp >= 0) {
return tmp;
}
int result = tmp & 0x7F;
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
if ((tmp = buffer.readByte()) >= 0) {
return result | tmp << 7;
}
result |= (tmp & 0x7F) << 7;
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
if ((tmp = buffer.readByte()) >= 0) {
return result | tmp << 14;
}
return result | (tmp & 0x7F) << 14;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public String toString() {

@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.id = ProtocolUtils.readVarIntSafelyOrThrow(buf);
this.id = ProtocolUtils.readVarInt(buf);
this.channel = ProtocolUtils.readString(buf);
if (buf.isReadable()) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void testNegativeOld() {
private void writeReadTestOld(ByteBuf buf, int test) {
buf.clear();
writeVarIntOld(buf, test);
assertEquals(test, ProtocolUtils.readVarIntSafely(buf));
assertEquals(test, ProtocolUtils.readVarInt(buf));
}

@Test
Expand Down Expand Up @@ -103,7 +103,7 @@ void testBytesWrittenAtBitBoundaries() {
"Encoding of " + i + " was invalid");

assertEquals(i, oldReadVarIntSafely(varintNew));
assertEquals(i, ProtocolUtils.readVarIntSafely(varintOld));
assertEquals(i, ProtocolUtils.readVarInt(varintOld));

varintNew.clear();
varintOld.clear();
Expand Down

0 comments on commit 1bfa9a6

Please sign in to comment.