All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.minestom.server.network.packet.PacketReading Maven / Gradle / Ivy

There is a newer version: 7320437640
Show newest version
package net.minestom.server.network.packet;

import net.minestom.server.ServerFlag;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.zip.DataFormatException;

import static net.minestom.server.network.NetworkBuffer.VAR_INT;

/**
 * Tools to read packets from a {@link NetworkBuffer} for network processing.
 * 

* Fairly internal and performance sensitive. */ @SuppressWarnings("ALL") @ApiStatus.Internal public final class PacketReading { private final static Logger LOGGER = LoggerFactory.getLogger(PacketReading.class); private static final int MAX_VAR_INT_SIZE = 5; private static final Result.Empty EMPTY_CLIENT_PACKET = new Result.Empty<>(); public sealed interface Result { /** * At least one packet was read. * The buffer may still contain half-read packets and should therefore be compacted for next read. */ record Success(List packets, ConnectionState newState) implements Result { public Success { if (packets.isEmpty()) { throw new IllegalArgumentException("Empty packets"); } } public Success(T packet, ConnectionState newState) { this(List.of(packet), newState); } } /** * Represents no packet to read. Can generally be ignored. *

* Happens when a packet length or payload couldn't be read, but the buffer has enough capacity. */ record Empty() implements Result { } /** * Represents a failure to read a packet due to insufficient buffer capacity. *

* Buffer should be expanded to at least {@code requiredCapacity} bytes. *

* If the buffer does not allow to read the packet length, max var-int length is returned. */ record Failure(long requiredCapacity) implements Result { } } public static Result readClients( @NotNull NetworkBuffer buffer, @NotNull ConnectionState state, boolean compressed ) throws DataFormatException { return readPackets(buffer, PacketVanilla.CLIENT_PACKET_PARSER, state, PacketVanilla::nextClientState, compressed); } public static Result readServers( @NotNull NetworkBuffer buffer, @NotNull ConnectionState state, boolean compressed ) throws DataFormatException { return readPackets(buffer, PacketVanilla.SERVER_PACKET_PARSER, state, PacketVanilla::nextServerState, compressed); } public static Result readPackets( @NotNull NetworkBuffer buffer, @NotNull PacketParser parser, @NotNull ConnectionState state, @NotNull BiFunction stateUpdater, boolean compressed ) throws DataFormatException { List packets = new ArrayList<>(); readLoop: while (buffer.readableBytes() > 0) { final Result result = readPacket(buffer, parser, state, stateUpdater, compressed); if (buffer.readableBytes() == 0 && packets.isEmpty()) return result; switch (result) { case Result.Success success -> { assert success.packets().size() == 1; packets.add(success.packets().getFirst()); state = success.newState(); } case Result.Empty ignored -> { break readLoop; } case Result.Failure failure -> { return packets.isEmpty() ? failure : new Result.Success<>(packets, state); } } } return !packets.isEmpty() ? new Result.Success<>(packets, state) : EMPTY_CLIENT_PACKET; } public static Result readClient( @NotNull NetworkBuffer buffer, @NotNull ConnectionState state, boolean compressed ) throws DataFormatException { return readPacket(buffer, PacketVanilla.CLIENT_PACKET_PARSER, state, PacketVanilla::nextClientState, compressed); } public static Result readServer( @NotNull NetworkBuffer buffer, @NotNull ConnectionState state, boolean compressed ) throws DataFormatException { return readPacket(buffer, PacketVanilla.SERVER_PACKET_PARSER, state, PacketVanilla::nextServerState, compressed); } public static Result readPacket( @NotNull NetworkBuffer buffer, @NotNull PacketParser parser, @NotNull ConnectionState state, @NotNull BiFunction stateUpdater, boolean compressed ) throws DataFormatException { final long beginMark = buffer.readIndex(); // READ PACKET LENGTH final int packetLength; try { packetLength = buffer.read(VAR_INT); } catch (IndexOutOfBoundsException e) { // Couldn't read a single var-int return new Result.Failure<>(MAX_VAR_INT_SIZE); } final long readerStart = buffer.readIndex(); if (readerStart > buffer.writeIndex()) { // Can't read the packet length, buffer has enough capacity buffer.readIndex(beginMark); return EMPTY_CLIENT_PACKET; } final int maxPacketSize = maxPacketSize(state); if (packetLength > maxPacketSize) { throw new DataFormatException("Packet too large: " + packetLength); } // READ PAYLOAD https://wiki.vg/Protocol#Packet_format if (buffer.readableBytes() < packetLength) { // Can't read the full packet buffer.readIndex(beginMark); final long packetLengthVarIntSize = readerStart - beginMark; final long requiredCapacity = packetLengthVarIntSize + packetLength; // Must return a failure if the buffer is too small // Otherwise do nothing, and hope to read the packet remains next time if (requiredCapacity > buffer.capacity()) return new Result.Failure<>(requiredCapacity); else return EMPTY_CLIENT_PACKET; } final long readerEnd = readerStart + packetLength; final long writerEnd = buffer.writeIndex(); buffer.writeIndex(readerEnd); final PacketRegistry registry = parser.stateRegistry(state); final T packet = readFramedPacket(buffer, registry, compressed); final ConnectionState nextState = stateUpdater.apply(packet, state); buffer.index(readerEnd, writerEnd); return new Result.Success<>(packet, nextState); } private static T readFramedPacket(NetworkBuffer buffer, PacketRegistry registry, boolean compressed) throws DataFormatException { if (!compressed) { // No compression format return readPayload(buffer, registry); } final int dataLength = buffer.read(VAR_INT); if (dataLength == 0) { // Uncompressed packet return readPayload(buffer, registry); } // Decompress the packet into the pooled buffer // and read the uncompressed packet from it NetworkBuffer decompressed = PacketVanilla.PACKET_POOL.get(); try { if (decompressed.capacity() < dataLength) decompressed.resize(dataLength); buffer.decompress(buffer.readIndex(), buffer.readableBytes(), decompressed); return readPayload(decompressed, registry); } finally { PacketVanilla.PACKET_POOL.add(decompressed); } } private static T readPayload(NetworkBuffer buffer, PacketRegistry registry) { final int packetId = buffer.read(VAR_INT); final PacketRegistry.PacketInfo packetInfo = registry.packetInfo(packetId); final NetworkBuffer.Type serializer = packetInfo.serializer(); try { final T packet = serializer.read(buffer); if (buffer.readableBytes() != 0) { LOGGER.warn("WARNING: Packet ({}) 0x{} not fully read ({})", packetInfo.packetClass().getSimpleName(), Integer.toHexString(packetId), buffer); } return packet; } catch (Exception e) { throw new RuntimeException(e); } } public static int maxPacketSize(ConnectionState state) { return switch (state) { case HANDSHAKE, LOGIN -> ServerFlag.MAX_PACKET_SIZE_PRE_AUTH; default -> ServerFlag.MAX_PACKET_SIZE; }; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy