Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix varlong serialization #853

Merged
merged 2 commits into from
Dec 9, 2024
Merged

Conversation

valaphee
Copy link
Contributor

@valaphee valaphee commented Aug 19, 2024

readVarLong uses an int instead of a long for its value. This PR also removes the multiplications.

Original PR GeyserMC/PacketLib#48

Copy link
Member

@Tim203 Tim203 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like our current (and your changes) of at least the readVarInt is wrong.
The if (size > 5) and if (size > 35) should become >=. This is because of the & 0x80) = 0x80 bit in combination with the placement of the size check, as it currently allows varints with 6 bytes instead of the max 5. I personally btw prefer the old (length must be <= 5).
Maybe we can replace this with a do while loop? I personally think that the current while condition makes it unnecessarily hard to read.

@valaphee
Copy link
Contributor Author

valaphee commented Sep 15, 2024

The implementation is correct, it has to be greater-than as a VarInt should be able to consist out of 35, or 70 bits to be able to fully represent 32 bit or 64 bit. (and therefore has to run 5 iterations, at the 6th iteration it should fail, which it doe)

Using bits has just the slight advantage of not having to multiply by 7 each iteration, even though it's definitely not a bottleneck.

@Tim203
Copy link
Member

Tim203 commented Sep 15, 2024

You're right that the max length of a varint is 5 bytes to represent 4 bytes. But look at the condition in the while.
The body of the while is only executed when there is at least one more byte to read, because it checks if the continue bit is set.
This means that the total bytes it can read is the while body + 1 (because the continue bit is not set for the final byte). Resulting in this method accepting a max length of 6 bytes instead of 5.

This is why I recommend switching to a do while loop variant, because it has to read at least 1 byte and continue until there is no continue bit set.

@Konicai
Copy link
Member

Konicai commented Sep 15, 2024

Some unit tests would be nice

@valaphee
Copy link
Contributor Author

valaphee commented Sep 16, 2024

I mean you are theoretically right, but even the implementation Minecraft uses in both Bedrock and Java Edition post and prior Netty rewrite has this flaw.

byte b;
do {
	b = buf.readByte(); // will also do the 6th iteration
	i |= (b & 127) << j++ * 7;
	if (j > 5) {
		throw new RuntimeException("VarInt too big");
	}
} while (shouldContinueRead(b));

But I agree, it shouldn't consume the extra byte (even though it doesn't match the Vanilla impl) and I'll add a unit test

@valaphee
Copy link
Contributor Author

valaphee commented Sep 16, 2024

The writeVarLong method seems to be also wrong
0x80_80_80_80_80_80_80_80 should not be encoded as 0x80_81_82_84_88_90_e0_40_80_01 (as the 0x40 byte misses the marker, etc.)

But there are no serverbound varlong writes at least

… simple tests to ensure functionality, VarInt/Long wider than 5/10 bytes will now take precedence over index out of bounds
@valaphee valaphee changed the title Fix readVarLong, remove multiplication in readVarInt Fix varlong serialization Sep 24, 2024
@valaphee valaphee requested a review from Tim203 October 15, 2024 10:09
@onebeastchris onebeastchris merged commit c99cc90 into GeyserMC:master Dec 9, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants