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

net.minestom.server.network.player.PlayerSocketConnection Maven / Gradle / Ivy

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

import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.ListenerHandle;
import net.minestom.server.event.player.PlayerPacketOutEvent;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.PacketProcessor;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.client.handshake.ClientHandshakePacket;
import net.minestom.server.network.packet.server.*;
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.network.socket.Worker;
import net.minestom.server.utils.ObjectPool;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.binary.BinaryBuffer;
import net.minestom.server.utils.validate.Check;
import org.jctools.queues.MessagePassingQueue;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.DataFormatException;

/**
 * Represents a socket connection.
 * 

* It is the implementation used for all network client. */ @ApiStatus.Internal public class PlayerSocketConnection extends PlayerConnection { private final static Logger LOGGER = LoggerFactory.getLogger(PlayerSocketConnection.class); private static final ObjectPool POOL = ObjectPool.BUFFER_POOL; private final Worker worker; private final MessagePassingQueue workerQueue; private final SocketChannel channel; private SocketAddress remoteAddress; private volatile boolean compressed = false; //Could be null. Only used for Mojang Auth private volatile EncryptionContext encryptionContext; private byte[] nonce = new byte[4]; // Data from client packets private String loginUsername; private GameProfile gameProfile; private String serverAddress; private int serverPort; private int protocolVersion; private final List waitingBuffers = new ArrayList<>(); private final AtomicReference tickBuffer = new AtomicReference<>(POOL.get()); private BinaryBuffer cacheBuffer; private final ListenerHandle outgoing = EventDispatcher.getHandle(PlayerPacketOutEvent.class); public PlayerSocketConnection(@NotNull Worker worker, @NotNull SocketChannel channel, SocketAddress remoteAddress) { super(); this.worker = worker; this.workerQueue = worker.queue(); this.channel = channel; this.remoteAddress = remoteAddress; } public void processPackets(BinaryBuffer readBuffer, PacketProcessor packetProcessor) { // Decrypt data { final EncryptionContext encryptionContext = this.encryptionContext; if (encryptionContext != null) { ByteBuffer input = readBuffer.asByteBuffer(0, readBuffer.writerOffset()); try { encryptionContext.decrypt().update(input, input.duplicate()); } catch (ShortBufferException e) { MinecraftServer.getExceptionManager().handleException(e); return; } } } // Read all packets try { this.cacheBuffer = PacketUtils.readPackets(readBuffer, compressed, (id, payload) -> { if (!isOnline()) return; // Prevent packet corruption ClientPacket packet = null; try { packet = packetProcessor.process(this, id, payload); } catch (Exception e) { // Error while reading the packet MinecraftServer.getExceptionManager().handleException(e); } finally { if (payload.position() != payload.limit()) { LOGGER.warn("WARNING: Packet ({}) 0x{} not fully read ({}) {}", getConnectionState(), Integer.toHexString(id), payload, packet); } } }); } catch (DataFormatException e) { MinecraftServer.getExceptionManager().handleException(e); disconnect(); } } public void consumeCache(BinaryBuffer buffer) { final BinaryBuffer cache = this.cacheBuffer; if (cache != null) { buffer.write(cache); this.cacheBuffer = null; } } /** * Sets the encryption key and add the codecs to the pipeline. * * @param secretKey the secret key to use in the encryption * @throws IllegalStateException if encryption is already enabled for this connection */ public void setEncryptionKey(@NotNull SecretKey secretKey) { Check.stateCondition(encryptionContext != null, "Encryption is already enabled!"); this.encryptionContext = new EncryptionContext(MojangCrypt.getCipher(1, secretKey), MojangCrypt.getCipher(2, secretKey)); } /** * Enables compression and add a new codec to the pipeline. * * @throws IllegalStateException if encryption is already enabled for this connection */ public void startCompression() { Check.stateCondition(compressed, "Compression is already enabled!"); final int threshold = MinecraftServer.getCompressionThreshold(); Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0"); sendPacket(new SetCompressionPacket(threshold)); this.compressed = true; } @Override public void sendPacket(@NotNull SendablePacket packet) { final boolean compressed = this.compressed; this.workerQueue.relaxedOffer(() -> writePacketSync(packet, compressed)); } @Override public void sendPackets(@NotNull Collection packets) { final List packetsCopy = List.copyOf(packets); final boolean compressed = this.compressed; this.workerQueue.relaxedOffer(() -> { for (SendablePacket packet : packetsCopy) writePacketSync(packet, compressed); }); } @ApiStatus.Internal public void write(@NotNull ByteBuffer buffer, int index, int length) { this.workerQueue.relaxedOffer(() -> writeBufferSync(buffer, index, length)); } @ApiStatus.Internal public void write(@NotNull ByteBuffer buffer) { write(buffer, buffer.position(), buffer.remaining()); } @Override public @NotNull SocketAddress getRemoteAddress() { return remoteAddress; } /** * Changes the internal remote address field. *

* Mostly unsafe, used internally when interacting with a proxy. * * @param remoteAddress the new connection remote address */ @ApiStatus.Internal public void setRemoteAddress(@NotNull SocketAddress remoteAddress) { this.remoteAddress = remoteAddress; } @Override public void disconnect() { super.disconnect(); this.workerQueue.relaxedOffer(() -> { this.worker.disconnect(this, channel); final BinaryBuffer tick = tickBuffer.getAndSet(null); if (tick != null) POOL.add(tick); for (BinaryBuffer buffer : waitingBuffers) POOL.add(buffer); this.waitingBuffers.clear(); }); } public @NotNull SocketChannel getChannel() { return channel; } public @Nullable GameProfile gameProfile() { return gameProfile; } public void UNSAFE_setProfile(@NotNull GameProfile gameProfile) { this.gameProfile = gameProfile; } /** * Retrieves the username received from the client during connection. *

* This value has not been checked and could be anything. * * @return the username given by the client, unchecked */ public @Nullable String getLoginUsername() { return loginUsername; } /** * Sets the internal login username field. * * @param loginUsername the new login username field */ public void UNSAFE_setLoginUsername(@NotNull String loginUsername) { this.loginUsername = loginUsername; } /** * Gets the server address that the client used to connect. *

* WARNING: it is given by the client, it is possible for it to be wrong. * * @return the server address used */ @Override public @Nullable String getServerAddress() { return serverAddress; } /** * Gets the server port that the client used to connect. *

* WARNING: it is given by the client, it is possible for it to be wrong. * * @return the server port used */ @Override public int getServerPort() { return serverPort; } /** * Gets the protocol version of a client. * * @return protocol version of client. */ @Override public int getProtocolVersion() { return protocolVersion; } /** * Used in {@link ClientHandshakePacket} to change the internal fields. * * @param serverAddress the server address which the client used * @param serverPort the server port which the client used * @param protocolVersion the protocol version which the client used */ public void refreshServerInformation(@Nullable String serverAddress, int serverPort, int protocolVersion) { this.serverAddress = serverAddress; this.serverPort = serverPort; this.protocolVersion = protocolVersion; } public byte[] getNonce() { return nonce; } public void setNonce(byte[] nonce) { this.nonce = nonce; } private void writePacketSync(SendablePacket packet, boolean compressed) { if (!channel.isConnected()) return; final Player player = getPlayer(); // Outgoing event if (player != null && outgoing.hasListener()) { final ServerPacket serverPacket = SendablePacket.extractServerPacket(getConnectionState(), packet); PlayerPacketOutEvent event = new PlayerPacketOutEvent(player, serverPacket); outgoing.call(event); if (event.isCancelled()) return; } // Write packet if (packet instanceof ServerPacket serverPacket) { writeServerPacketSync(serverPacket, compressed); } else if (packet instanceof FramedPacket framedPacket) { var buffer = framedPacket.body(); writeBufferSync(buffer, 0, buffer.limit()); } else if (packet instanceof CachedPacket cachedPacket) { var buffer = cachedPacket.body(getConnectionState()); if (buffer != null) writeBufferSync(buffer, buffer.position(), buffer.remaining()); else writeServerPacketSync(cachedPacket.packet(getConnectionState()), compressed); } else if (packet instanceof LazyPacket lazyPacket) { writeServerPacketSync(lazyPacket.packet(), compressed); } else { throw new RuntimeException("Unknown packet type: " + packet.getClass().getName()); } } private void writeServerPacketSync(ServerPacket serverPacket, boolean compressed) { final Player player = getPlayer(); if (player != null) { if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && serverPacket instanceof ServerPacket.ComponentHolding) { serverPacket = ((ServerPacket.ComponentHolding) serverPacket).copyWithOperator(component -> MinestomAdventure.COMPONENT_TRANSLATOR.apply(component, Objects.requireNonNullElseGet(player.getLocale(), MinestomAdventure::getDefaultLocale))); } } try (var hold = ObjectPool.PACKET_POOL.hold()) { var buffer = PacketUtils.createFramedPacket(getConnectionState(), hold.get(), serverPacket, compressed); writeBufferSync(buffer, 0, buffer.limit()); } } private void writeBufferSync(@NotNull ByteBuffer buffer, int index, int length) { // Encrypt data final EncryptionContext encryptionContext = this.encryptionContext; if (encryptionContext != null) { // Encryption support try (var hold = ObjectPool.PACKET_POOL.hold()) { ByteBuffer output = hold.get(); try { length = encryptionContext.encrypt().update(buffer.slice(index, length), output); writeBufferSync0(output, 0, length); } catch (ShortBufferException e) { MinecraftServer.getExceptionManager().handleException(e); } return; } } writeBufferSync0(buffer, index, length); } private void writeBufferSync0(@NotNull ByteBuffer buffer, int index, int length) { BinaryBuffer localBuffer = tickBuffer.getPlain(); if (localBuffer == null) return; // Socket is closed final int capacity = localBuffer.capacity(); if (length <= capacity) { if (!localBuffer.canWrite(length)) localBuffer = updateLocalBuffer(); localBuffer.write(buffer, index, length); } else { final int bufferCount = length / capacity + 1; for (int i = 0; i < bufferCount; i++) { final int sliceStart = i * capacity; final int sliceLength = Math.min(length, sliceStart + capacity) - sliceStart; if (!localBuffer.canWrite(sliceLength)) localBuffer = updateLocalBuffer(); localBuffer.write(buffer, sliceStart, sliceLength); } } } public void flushSync() throws IOException { final SocketChannel channel = this.channel; final List waitingBuffers = this.waitingBuffers; if (!channel.isConnected()) throw new ClosedChannelException(); if (waitingBuffers.isEmpty()) { BinaryBuffer localBuffer = tickBuffer.getPlain(); if (localBuffer == null) return; // Socket is closed localBuffer.writeChannel(channel); } else { // Write as much as possible from the waiting list Iterator iterator = waitingBuffers.iterator(); while (iterator.hasNext()) { BinaryBuffer waitingBuffer = iterator.next(); if (!waitingBuffer.writeChannel(channel)) break; iterator.remove(); POOL.add(waitingBuffer); } } } private BinaryBuffer updateLocalBuffer() { BinaryBuffer newBuffer = POOL.get(); this.waitingBuffers.add(tickBuffer.getPlain()); this.tickBuffer.setPlain(newBuffer); return newBuffer; } record EncryptionContext(Cipher encrypt, Cipher decrypt) { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy