Minestom/src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java

401 lines
15 KiB
Java
Raw Normal View History

package net.minestom.server.network.player;
2020-11-19 02:28:56 +01:00
import net.minestom.server.MinecraftServer;
2021-06-11 17:19:11 +02:00
import net.minestom.server.adventure.MinestomAdventure;
2021-08-04 14:47:19 +02:00
import net.minestom.server.entity.Player;
2022-02-20 12:58:09 +01:00
import net.minestom.server.event.EventDispatcher;
2022-02-17 13:51:55 +01:00
import net.minestom.server.event.ListenerHandle;
import net.minestom.server.event.player.PlayerPacketOutEvent;
2021-08-08 19:02:36 +02:00
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.PacketProcessor;
2022-09-05 13:54:58 +02:00
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.client.handshake.ClientHandshakePacket;
2021-11-17 06:31:24 +01:00
import net.minestom.server.network.packet.server.*;
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.network.socket.Worker;
2022-05-16 07:34:47 +02:00
import net.minestom.server.utils.ObjectPool;
import net.minestom.server.utils.PacketUtils;
2021-08-06 14:21:11 +02:00
import net.minestom.server.utils.binary.BinaryBuffer;
import net.minestom.server.utils.validate.Check;
import org.jctools.queues.MessagePassingQueue;
2021-08-04 14:47:19 +02:00
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
2021-08-09 15:09:08 +02:00
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
2021-08-08 19:02:36 +02:00
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
2021-08-08 19:02:36 +02:00
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.
* <p>
* It is the implementation used for all network client.
*/
@ApiStatus.Internal
public class PlayerSocketConnection extends PlayerConnection {
2021-08-09 15:09:08 +02:00
private final static Logger LOGGER = LoggerFactory.getLogger(PlayerSocketConnection.class);
2022-05-16 07:34:47 +02:00
private static final ObjectPool<BinaryBuffer> POOL = ObjectPool.BUFFER_POOL;
2021-08-09 15:09:08 +02:00
2021-08-04 03:53:01 +02:00
private final Worker worker;
private final MessagePassingQueue<Runnable> workerQueue;
2020-08-15 04:05:15 +02:00
private final SocketChannel channel;
private SocketAddress remoteAddress;
2020-11-16 03:10:51 +01:00
2021-11-17 06:31:24 +01:00
private volatile boolean compressed = false;
2020-11-09 23:48:34 +01:00
//Could be null. Only used for Mojang Auth
2022-02-20 12:58:09 +01:00
private volatile EncryptionContext encryptionContext;
2020-11-09 23:48:34 +01:00
private byte[] nonce = new byte[4];
2020-11-14 23:18:52 +01:00
// Data from client packets
2020-11-09 23:48:34 +01:00
private String loginUsername;
2022-10-27 14:33:48 +02:00
private GameProfile gameProfile;
private String serverAddress;
private int serverPort;
private int protocolVersion;
private final List<BinaryBuffer> waitingBuffers = new ArrayList<>();
2022-05-16 07:34:47 +02:00
private final AtomicReference<BinaryBuffer> tickBuffer = new AtomicReference<>(POOL.get());
private BinaryBuffer cacheBuffer;
2021-03-08 01:30:18 +01:00
2022-02-20 12:58:09 +01:00
private final ListenerHandle<PlayerPacketOutEvent> outgoing = EventDispatcher.getHandle(PlayerPacketOutEvent.class);
2022-02-17 13:51:55 +01:00
public PlayerSocketConnection(@NotNull Worker worker, @NotNull SocketChannel channel, SocketAddress remoteAddress) {
2020-08-15 04:05:15 +02:00
super();
2021-08-04 03:53:01 +02:00
this.worker = worker;
this.workerQueue = worker.queue();
2020-08-15 04:05:15 +02:00
this.channel = channel;
this.remoteAddress = remoteAddress;
}
public void processPackets(BinaryBuffer readBuffer, PacketProcessor packetProcessor) {
2021-08-08 19:02:36 +02:00
// Decrypt data
2022-02-20 12:58:09 +01:00
{
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;
}
2021-08-08 19:02:36 +02:00
}
}
// Read all packets
try {
this.cacheBuffer = PacketUtils.readPackets(readBuffer, compressed,
(id, payload) -> {
if (!isOnline())
return; // Prevent packet corruption
2022-09-05 13:54:58 +02:00
ClientPacket packet = null;
try {
2022-09-05 13:54:58 +02:00
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();
}
}
2021-08-06 14:21:11 +02:00
public void consumeCache(BinaryBuffer buffer) {
final BinaryBuffer cache = this.cacheBuffer;
if (cache != null) {
buffer.write(cache);
2021-08-06 14:21:11 +02:00
this.cacheBuffer = null;
}
2020-08-15 04:05:15 +02:00
}
/**
* 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) {
2022-02-20 12:58:09 +01:00
Check.stateCondition(encryptionContext != null, "Encryption is already enabled!");
this.encryptionContext = new EncryptionContext(MojangCrypt.getCipher(1, secretKey), MojangCrypt.getCipher(2, secretKey));
2020-08-15 04:05:15 +02:00
}
/**
* Enables compression and add a new codec to the pipeline.
*
* @throws IllegalStateException if encryption is already enabled for this connection
*/
2020-11-20 04:36:33 +01:00
public void startCompression() {
Check.stateCondition(compressed, "Compression is already enabled!");
2020-11-20 04:36:33 +01:00
final int threshold = MinecraftServer.getCompressionThreshold();
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
2022-03-20 02:35:45 +01:00
sendPacket(new SetCompressionPacket(threshold));
2021-11-17 06:31:24 +01:00
this.compressed = true;
}
2020-08-15 04:05:15 +02:00
@Override
2021-11-17 06:31:24 +01:00
public void sendPacket(@NotNull SendablePacket packet) {
final boolean compressed = this.compressed;
this.workerQueue.relaxedOffer(() -> writePacketSync(packet, compressed));
}
@Override
public void sendPackets(@NotNull Collection<SendablePacket> packets) {
final List<SendablePacket> 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());
}
2020-08-15 04:05:15 +02:00
@Override
public @NotNull SocketAddress getRemoteAddress() {
return remoteAddress;
}
/**
* Changes the internal remote address field.
* <p>
* Mostly unsafe, used internally when interacting with a proxy.
*
* @param remoteAddress the new connection remote address
*/
2021-08-04 14:47:19 +02:00
@ApiStatus.Internal
public void setRemoteAddress(@NotNull SocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
2020-08-15 04:05:15 +02:00
}
2020-08-15 04:05:15 +02:00
@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();
});
2020-08-15 04:05:15 +02:00
}
public @NotNull SocketChannel getChannel() {
2020-08-15 04:05:15 +02:00
return channel;
}
2022-10-27 14:33:48 +02:00
public @Nullable GameProfile gameProfile() {
return gameProfile;
}
public void UNSAFE_setProfile(@NotNull GameProfile gameProfile) {
this.gameProfile = gameProfile;
}
2020-11-09 23:48:34 +01:00
/**
* Retrieves the username received from the client during connection.
* <p>
* This value has not been checked and could be anything.
*
* @return the username given by the client, unchecked
*/
public @Nullable String getLoginUsername() {
2020-11-09 23:48:34 +01:00
return loginUsername;
}
/**
* Sets the internal login username field.
2020-11-09 23:48:34 +01:00
*
* @param loginUsername the new login username field
*/
public void UNSAFE_setLoginUsername(@NotNull String loginUsername) {
this.loginUsername = loginUsername;
}
/**
2020-11-03 18:22:36 +01:00
* Gets the server address that the client used to connect.
* <p>
* 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;
}
/**
2020-11-03 18:22:36 +01:00
* Gets the server port that the client used to connect.
* <p>
* 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;
}
2020-11-16 03:10:51 +01:00
public byte[] getNonce() {
return nonce;
}
public void setNonce(byte[] nonce) {
this.nonce = nonce;
}
2021-11-17 06:31:24 +01:00
private void writePacketSync(SendablePacket packet, boolean compressed) {
if (!channel.isConnected()) return;
2022-02-17 13:51:55 +01:00
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;
2022-02-17 13:51:55 +01:00
}
// Write packet
2021-11-17 06:31:24 +01:00
if (packet instanceof ServerPacket serverPacket) {
writeServerPacketSync(serverPacket, compressed);
} else if (packet instanceof FramedPacket framedPacket) {
2022-02-17 13:51:55 +01:00
var buffer = framedPacket.body();
2022-05-16 07:34:47 +02:00
writeBufferSync(buffer, 0, buffer.limit());
2021-11-17 06:31:24 +01:00
} 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);
2021-12-22 08:06:00 +01:00
} else if (packet instanceof LazyPacket lazyPacket) {
writeServerPacketSync(lazyPacket.packet(), compressed);
2021-11-17 06:31:24 +01:00
} else {
throw new RuntimeException("Unknown packet type: " + packet.getClass().getName());
}
}
private void writeServerPacketSync(ServerPacket serverPacket, boolean compressed) {
final Player player = getPlayer();
if (player != null) {
2024-03-19 17:42:06 +01:00
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)));
2021-11-17 06:31:24 +01:00
}
}
2022-05-16 07:34:47 +02:00
try (var hold = ObjectPool.PACKET_POOL.hold()) {
var buffer = PacketUtils.createFramedPacket(getConnectionState(), hold.get(), serverPacket, compressed);
2022-05-16 07:34:47 +02:00
writeBufferSync(buffer, 0, buffer.limit());
}
2021-11-17 06:31:24 +01:00
}
2022-02-17 13:51:55 +01:00
private void writeBufferSync(@NotNull ByteBuffer buffer, int index, int length) {
2022-02-20 12:58:09 +01:00
// Encrypt data
2022-05-16 07:34:47 +02:00
final EncryptionContext encryptionContext = this.encryptionContext;
if (encryptionContext != null) { // Encryption support
try (var hold = ObjectPool.PACKET_POOL.hold()) {
ByteBuffer output = hold.get();
2022-02-20 12:58:09 +01:00
try {
2022-05-16 07:34:47 +02:00
length = encryptionContext.encrypt().update(buffer.slice(index, length), output);
writeBufferSync0(output, 0, length);
2022-02-20 12:58:09 +01:00
} catch (ShortBufferException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
2022-05-16 07:34:47 +02:00
return;
2021-11-17 06:31:24 +01:00
}
}
2022-05-16 07:34:47 +02:00
writeBufferSync0(buffer, index, length);
}
private void writeBufferSync0(@NotNull ByteBuffer buffer, int index, int length) {
2021-11-17 06:31:24 +01:00
BinaryBuffer localBuffer = tickBuffer.getPlain();
if (localBuffer == null)
return; // Socket is closed
2021-11-17 06:31:24 +01:00
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);
}
}
}
2022-03-20 03:22:38 +01:00
public void flushSync() throws IOException {
final SocketChannel channel = this.channel;
final List<BinaryBuffer> waitingBuffers = this.waitingBuffers;
2022-03-20 03:22:38 +01:00
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<BinaryBuffer> iterator = waitingBuffers.iterator();
while (iterator.hasNext()) {
BinaryBuffer waitingBuffer = iterator.next();
if (!waitingBuffer.writeChannel(channel)) break;
iterator.remove();
2022-05-16 07:34:47 +02:00
POOL.add(waitingBuffer);
}
2021-11-17 06:31:24 +01:00
}
}
private BinaryBuffer updateLocalBuffer() {
2022-05-16 07:34:47 +02:00
BinaryBuffer newBuffer = POOL.get();
2021-11-17 06:31:24 +01:00
this.waitingBuffers.add(tickBuffer.getPlain());
this.tickBuffer.setPlain(newBuffer);
return newBuffer;
}
2022-02-20 12:58:09 +01:00
record EncryptionContext(Cipher encrypt, Cipher decrypt) {
}
}