mirror of
https://github.com/PaperMC/Velocity.git
synced 2026-02-19 15:37:42 +01:00
Merge branch 'dev/1.1.0' into dev/2.0.0
This commit is contained in:
@@ -122,7 +122,7 @@ public enum ProtocolVersion {
|
|||||||
* @return the protocol version
|
* @return the protocol version
|
||||||
*/
|
*/
|
||||||
public int getProtocol() {
|
public int getProtocol() {
|
||||||
return protocol;
|
return protocol == -1 ? snapshotProtocol : protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -223,20 +223,21 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
*/
|
*/
|
||||||
public void closeWith(Object msg) {
|
public void closeWith(Object msg) {
|
||||||
if (channel.isActive()) {
|
if (channel.isActive()) {
|
||||||
boolean is1Point8 = this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0;
|
boolean is17 = this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) < 0
|
||||||
boolean isLegacyOrPing = this.getState() == StateRegistry.HANDSHAKE
|
&& this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_7_2) >= 0;
|
||||||
|| this.getState() == StateRegistry.STATUS;
|
if (is17 && this.getState() != StateRegistry.STATUS) {
|
||||||
if (channel.eventLoop().inEventLoop() && (is1Point8 || isLegacyOrPing)) {
|
channel.eventLoop().execute(() -> {
|
||||||
|
// 1.7.x versions have a race condition with switching protocol states, so just explicitly
|
||||||
|
// close the connection after a short while.
|
||||||
|
this.setAutoReading(false);
|
||||||
|
channel.eventLoop().schedule(() -> {
|
||||||
|
knownDisconnect = true;
|
||||||
|
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}, 250, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
knownDisconnect = true;
|
knownDisconnect = true;
|
||||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||||
} else {
|
|
||||||
// 1.7.x versions have a race condition with switching protocol states, so just explicitly
|
|
||||||
// close the connection after a short while.
|
|
||||||
this.setAutoReading(false);
|
|
||||||
channel.eventLoop().schedule(() -> {
|
|
||||||
knownDisconnect = true;
|
|
||||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}, 250, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
@@ -69,19 +70,21 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
|||||||
MinecraftConnection smc = serverConn.ensureConnected();
|
MinecraftConnection smc = serverConn.ensureConnected();
|
||||||
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
||||||
|
|
||||||
|
final ConnectedPlayer player = serverConn.getPlayer();
|
||||||
|
|
||||||
if (existingConnection != null) {
|
if (existingConnection != null) {
|
||||||
// Shut down the existing server connection.
|
// Shut down the existing server connection.
|
||||||
serverConn.getPlayer().setConnectedServer(null);
|
player.setConnectedServer(null);
|
||||||
existingConnection.disconnect();
|
existingConnection.disconnect();
|
||||||
|
|
||||||
// Send keep alive to try to avoid timeouts
|
// Send keep alive to try to avoid timeouts
|
||||||
serverConn.getPlayer().sendKeepAlive();
|
player.sendKeepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
|
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
|
||||||
smc.setAutoReading(false);
|
smc.setAutoReading(false);
|
||||||
server.getEventManager()
|
server.getEventManager()
|
||||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(),
|
.fire(new ServerConnectedEvent(player, serverConn.getServer(),
|
||||||
existingConnection != null ? existingConnection.getServer() : null))
|
existingConnection != null ? existingConnection.getServer() : null))
|
||||||
.whenCompleteAsync((x, error) -> {
|
.whenCompleteAsync((x, error) -> {
|
||||||
// Make sure we can still transition (player might have disconnected here).
|
// Make sure we can still transition (player might have disconnected here).
|
||||||
@@ -93,17 +96,15 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
// Change the client to use the ClientPlaySessionHandler if required.
|
// Change the client to use the ClientPlaySessionHandler if required.
|
||||||
ClientPlaySessionHandler playHandler;
|
ClientPlaySessionHandler playHandler;
|
||||||
if (serverConn.getPlayer().getConnection().getSessionHandler()
|
if (player.getConnection().getSessionHandler() instanceof ClientPlaySessionHandler) {
|
||||||
instanceof ClientPlaySessionHandler) {
|
playHandler = (ClientPlaySessionHandler) player.getConnection().getSessionHandler();
|
||||||
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection()
|
|
||||||
.getSessionHandler();
|
|
||||||
} else {
|
} else {
|
||||||
playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer());
|
playHandler = new ClientPlaySessionHandler(server, player);
|
||||||
serverConn.getPlayer().getConnection().setSessionHandler(playHandler);
|
player.getConnection().setSessionHandler(playHandler);
|
||||||
}
|
}
|
||||||
playHandler.handleBackendJoinGame(packet, serverConn);
|
playHandler.handleBackendJoinGame(packet, serverConn);
|
||||||
|
|
||||||
// Strap on the correct session handler for the server. We will have nothing more to do
|
// Set the new play session handler for the server. We will have nothing more to do
|
||||||
// with this connection once this task finishes up.
|
// with this connection once this task finishes up.
|
||||||
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
||||||
|
|
||||||
@@ -114,15 +115,15 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
|||||||
serverConn.getPlayer().setConnectedServer(serverConn);
|
serverConn.getPlayer().setConnectedServer(serverConn);
|
||||||
|
|
||||||
// We're done! :)
|
// We're done! :)
|
||||||
server.getEventManager().fireAndForget(new ServerPostConnectEvent(serverConn.getPlayer(),
|
server.getEventManager().fireAndForget(new ServerPostConnectEvent(player,
|
||||||
existingConnection == null ? null : existingConnection.getServer()));
|
existingConnection == null ? null : existingConnection.getServer()));
|
||||||
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
|
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
|
||||||
}, smc.eventLoop())
|
}, smc.eventLoop())
|
||||||
.exceptionally(exc -> {
|
.exceptionally(exc -> {
|
||||||
logger.error("Unable to switch to new server {} for {}",
|
logger.error("Unable to switch to new server {} for {}",
|
||||||
serverConn.getServerInfo().getName(),
|
serverConn.getServerInfo().getName(),
|
||||||
serverConn.getPlayer().getUsername(), exc);
|
player.getUsername(), exc);
|
||||||
serverConn.getPlayer().disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
player.disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
||||||
resultFuture.completeExceptionally(exc);
|
resultFuture.completeExceptionally(exc);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.velocitypowered.api.event.player.TabCompleteEvent;
|
|||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
|
import com.velocitypowered.proxy.connection.ConnectionTypes;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
|
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
|
||||||
@@ -333,30 +334,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
} else {
|
} else {
|
||||||
// Clear tab list to avoid duplicate entries
|
// Clear tab list to avoid duplicate entries
|
||||||
player.getTabList().clearAll();
|
player.getTabList().clearAll();
|
||||||
|
if (player.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
|
||||||
// In order to handle switching to another server, you will need to send two packets:
|
this.doSafeClientServerSwitch(joinGame);
|
||||||
//
|
} else {
|
||||||
// - The join game packet from the backend server, with a different dimension
|
this.doFastClientServerSwitch(joinGame);
|
||||||
// - A respawn with the correct dimension
|
|
||||||
//
|
|
||||||
// Most notably, by having the client accept the join game packet, we can work around the need
|
|
||||||
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
|
|
||||||
// improving compatibility with mods.
|
|
||||||
|
|
||||||
int sentOldDim = joinGame.getDimension();
|
|
||||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) {
|
|
||||||
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
|
|
||||||
// additional respawn. On older versions of Minecraft this forces the client to perform
|
|
||||||
// garbage collection which adds additional latency.
|
|
||||||
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
|
|
||||||
}
|
}
|
||||||
player.getConnection().delayedWrite(joinGame);
|
|
||||||
|
|
||||||
player.getConnection().delayedWrite(
|
|
||||||
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
|
|
||||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
|
||||||
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
|
||||||
joinGame.getCurrentDimensionData()));
|
|
||||||
destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16
|
destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +376,55 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
destination.completeJoin();
|
destination.completeJoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doFastClientServerSwitch(JoinGame joinGame) {
|
||||||
|
// In order to handle switching to another server, you will need to send two packets:
|
||||||
|
//
|
||||||
|
// - The join game packet from the backend server, with a different dimension
|
||||||
|
// - A respawn with the correct dimension
|
||||||
|
//
|
||||||
|
// Most notably, by having the client accept the join game packet, we can work around the need
|
||||||
|
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
|
||||||
|
// improving compatibility with mods.
|
||||||
|
int sentOldDim = joinGame.getDimension();
|
||||||
|
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) {
|
||||||
|
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
|
||||||
|
// additional respawn. On older versions of Minecraft this forces the client to perform
|
||||||
|
// garbage collection which adds additional latency.
|
||||||
|
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
|
||||||
|
}
|
||||||
|
player.getConnection().delayedWrite(joinGame);
|
||||||
|
|
||||||
|
player.getConnection().delayedWrite(
|
||||||
|
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
|
||||||
|
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||||
|
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||||
|
joinGame.getCurrentDimensionData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSafeClientServerSwitch(JoinGame joinGame) {
|
||||||
|
// Some clients do not behave well with the "fast" respawn sequence. In this case we will use
|
||||||
|
// a "safe" respawn sequence that involves sending three packets to the client. They have the
|
||||||
|
// same effect but tend to work better with buggier clients (Forge 1.8 in particular).
|
||||||
|
|
||||||
|
// Send the JoinGame packet itself, unmodified.
|
||||||
|
player.getConnection().delayedWrite(joinGame);
|
||||||
|
|
||||||
|
// Send a respawn packet in a different dimension.
|
||||||
|
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||||
|
player.getConnection().delayedWrite(
|
||||||
|
new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
|
||||||
|
joinGame.getGamemode(), joinGame.getLevelType(),
|
||||||
|
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||||
|
joinGame.getCurrentDimensionData()));
|
||||||
|
|
||||||
|
// Now send a respawn packet in the correct dimension.
|
||||||
|
player.getConnection().delayedWrite(
|
||||||
|
new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||||
|
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||||
|
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||||
|
joinGame.getCurrentDimensionData()));
|
||||||
|
}
|
||||||
|
|
||||||
public List<UUID> getServerBossBars() {
|
public List<UUID> getServerBossBars() {
|
||||||
return serverBossBars;
|
return serverBossBars;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
onlineMode);
|
onlineMode);
|
||||||
final GameProfile finalProfile = profile;
|
final GameProfile finalProfile = profile;
|
||||||
|
|
||||||
server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> {
|
server.getEventManager().fire(profileRequestEvent).thenComposeAsync(profileEvent -> {
|
||||||
if (mcConnection.isClosed()) {
|
if (mcConnection.isClosed()) {
|
||||||
// The player disconnected after we authenticated them.
|
// The player disconnected after we authenticated them.
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
@@ -234,7 +234,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
completeLoginProtocolPhaseAndInitialize(player);
|
completeLoginProtocolPhaseAndInitialize(player);
|
||||||
}
|
}
|
||||||
}, mcConnection.eventLoop());
|
}, mcConnection.eventLoop());
|
||||||
}).exceptionally((ex) -> {
|
}, mcConnection.eventLoop()).exceptionally((ex) -> {
|
||||||
logger.error("Exception during connection of {}", finalProfile, ex);
|
logger.error("Exception during connection of {}", finalProfile, ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class LegacyPingDecoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.channel().isActive()) {
|
if (!ctx.channel().isActive()) {
|
||||||
in.skipBytes(in.readableBytes());
|
in.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.velocitypowered.proxy.protocol.netty;
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.netty.VarintByteDecoder.DecodeResult;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
import io.netty.util.ByteProcessor;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||||
@@ -13,16 +13,15 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
new QuietDecoderException("Bad packet length");
|
new QuietDecoderException("Bad packet length");
|
||||||
private static final QuietDecoderException VARINT_BIG_CACHED =
|
private static final QuietDecoderException VARINT_BIG_CACHED =
|
||||||
new QuietDecoderException("VarInt too big");
|
new QuietDecoderException("VarInt too big");
|
||||||
private final VarintByteDecoder reader = new VarintByteDecoder();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
if (!ctx.channel().isActive()) {
|
if (!ctx.channel().isActive()) {
|
||||||
in.skipBytes(in.readableBytes());
|
in.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.reset();
|
final VarintByteDecoder reader = new VarintByteDecoder();
|
||||||
|
|
||||||
int varintEnd = in.forEachByte(reader);
|
int varintEnd = in.forEachByte(reader);
|
||||||
if (varintEnd == -1) {
|
if (varintEnd == -1) {
|
||||||
@@ -31,53 +30,23 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.result == DecodeResult.SUCCESS) {
|
if (reader.getResult() == DecodeResult.SUCCESS) {
|
||||||
if (reader.readVarint < 0) {
|
int readVarint = reader.getReadVarint();
|
||||||
|
int bytesRead = reader.getBytesRead();
|
||||||
|
if (readVarint < 0) {
|
||||||
throw BAD_LENGTH_CACHED;
|
throw BAD_LENGTH_CACHED;
|
||||||
} else if (reader.readVarint == 0) {
|
} else if (readVarint == 0) {
|
||||||
// skip over the empty packet and ignore it
|
// skip over the empty packet and ignore it
|
||||||
in.readerIndex(varintEnd + 1);
|
in.readerIndex(varintEnd + 1);
|
||||||
} else {
|
} else {
|
||||||
int minimumRead = reader.bytesRead + reader.readVarint;
|
int minimumRead = bytesRead + readVarint;
|
||||||
if (in.isReadable(minimumRead)) {
|
if (in.isReadable(minimumRead)) {
|
||||||
out.add(in.retainedSlice(varintEnd + 1, reader.readVarint));
|
out.add(in.retainedSlice(varintEnd + 1, readVarint));
|
||||||
in.skipBytes(minimumRead);
|
in.skipBytes(minimumRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (reader.result == DecodeResult.TOO_BIG) {
|
} else if (reader.getResult() == DecodeResult.TOO_BIG) {
|
||||||
throw VARINT_BIG_CACHED;
|
throw VARINT_BIG_CACHED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class VarintByteDecoder implements ByteProcessor {
|
|
||||||
private int readVarint;
|
|
||||||
private int bytesRead;
|
|
||||||
private DecodeResult result = DecodeResult.TOO_SHORT;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean process(byte k) {
|
|
||||||
readVarint |= (k & 0x7F) << bytesRead++ * 7;
|
|
||||||
if (bytesRead > 3) {
|
|
||||||
result = DecodeResult.TOO_BIG;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((k & 0x80) != 128) {
|
|
||||||
result = DecodeResult.SUCCESS;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
readVarint = 0;
|
|
||||||
bytesRead = 0;
|
|
||||||
result = DecodeResult.TOO_SHORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum DecodeResult {
|
|
||||||
SUCCESS,
|
|
||||||
TOO_SHORT,
|
|
||||||
TOO_BIG
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
|
import io.netty.util.ByteProcessor;
|
||||||
|
|
||||||
|
class VarintByteDecoder implements ByteProcessor {
|
||||||
|
|
||||||
|
private int readVarint;
|
||||||
|
private int bytesRead;
|
||||||
|
private DecodeResult result = DecodeResult.TOO_SHORT;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(byte k) {
|
||||||
|
readVarint |= (k & 0x7F) << bytesRead++ * 7;
|
||||||
|
if (bytesRead > 3) {
|
||||||
|
result = DecodeResult.TOO_BIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((k & 0x80) != 128) {
|
||||||
|
result = DecodeResult.SUCCESS;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReadVarint() {
|
||||||
|
return readVarint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBytesRead() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecodeResult getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DecodeResult {
|
||||||
|
SUCCESS,
|
||||||
|
TOO_SHORT,
|
||||||
|
TOO_BIG
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user