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

258 lines
8.0 KiB
Java

package net.minestom.server.network.player;
import io.netty.channel.Channel;
import io.netty.channel.socket.SocketChannel;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.extras.mojangAuth.Decrypter;
import net.minestom.server.extras.mojangAuth.Encrypter;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.netty.codec.PacketCompressor;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.crypto.SecretKey;
import java.net.SocketAddress;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a networking connection with Netty.
* <p>
* It is the implementation used for all network client.
*/
public class NettyPlayerConnection extends PlayerConnection {
private final SocketChannel channel;
private SocketAddress remoteAddress;
private boolean encrypted = false;
private boolean compressed = false;
//Could be null. Only used for Mojang Auth
private byte[] nonce = new byte[4];
// Data from client packets
private String loginUsername;
private String serverAddress;
private int serverPort;
// Used for the login plugin request packet, to retrieve the channel from a message id,
// cleared once the player enters the play state
private final Map<Integer, String> pluginRequestMap = new ConcurrentHashMap<>();
// Bungee
private UUID bungeeUuid;
private PlayerSkin bungeeSkin;
public NettyPlayerConnection(@NotNull SocketChannel channel) {
super();
this.channel = channel;
this.remoteAddress = channel.remoteAddress();
}
@Override
public void update() {
// Flush
this.channel.eventLoop().execute(() -> channel.flush());
// Network stats
super.update();
}
/**
* 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(encrypted, "Encryption is already enabled!");
this.encrypted = true;
channel.pipeline().addBefore("framer", "decrypt", new Decrypter(MojangCrypt.getCipher(2, secretKey)));
channel.pipeline().addBefore("framer", "encrypt", new Encrypter(MojangCrypt.getCipher(1, secretKey)));
}
/**
* Enables compression and add a new codec to the pipeline.
*
* @param threshold the threshold for a packet to be compressible
* @throws IllegalStateException if encryption is already enabled for this connection
*/
public void enableCompression(int threshold) {
Check.stateCondition(compressed, "Compression is already enabled!");
this.compressed = true;
sendPacket(new SetCompressionPacket(threshold));
channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold));
}
/**
* Writes a packet to the connection channel.
* <p>
* All packets are flushed during {@link net.minestom.server.entity.Player#update(long)}.
*
* @param serverPacket the packet to write
*/
@Override
public void sendPacket(@NotNull ServerPacket serverPacket) {
if (shouldSendPacket(serverPacket)) {
if (getPlayer() != null) {
this.channel.write(serverPacket); // Flush on player update
} else {
this.channel.writeAndFlush(serverPacket);
}
}
}
@NotNull
@Override
public 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
*/
public void setRemoteAddress(@NotNull SocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
@Override
public void disconnect() {
channel.close();
}
@NotNull
public Channel getChannel() {
return channel;
}
/**
* 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
*/
@Nullable
public 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.
* <p>
* WARNING: it is given by the client, it is possible for it to be wrong.
*
* @return the server address used
*/
@Nullable
public String getServerAddress() {
return serverAddress;
}
/**
* 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
*/
public int getServerPort() {
return serverPort;
}
@Nullable
public UUID getBungeeUuid() {
return bungeeUuid;
}
public void UNSAFE_setBungeeUuid(UUID bungeeUuid) {
this.bungeeUuid = bungeeUuid;
}
@Nullable
public PlayerSkin getBungeeSkin() {
return bungeeSkin;
}
public void UNSAFE_setBungeeSkin(PlayerSkin bungeeSkin) {
this.bungeeSkin = bungeeSkin;
}
/**
* Adds an entry to the plugin request map.
* <p>
* Only working if {@link #getConnectionState()} is {@link net.minestom.server.network.ConnectionState#LOGIN}.
*
* @param messageId the message id
* @param channel the packet channel
* @throws IllegalStateException if a messageId with the value {@code messageId} already exists for this connection
*/
public void addPluginRequestEntry(int messageId, @NotNull String channel) {
if (!getConnectionState().equals(ConnectionState.LOGIN)) {
return;
}
Check.stateCondition(pluginRequestMap.containsKey(messageId), "You cannot have two messageId with the same value");
this.pluginRequestMap.put(messageId, channel);
}
/**
* Gets a request channel from a message id, previously cached using {@link #addPluginRequestEntry(int, String)}.
* <p>
* Be aware that the internal map is cleared once the player enters the play state.
*
* @param messageId the message id
* @return the channel linked to the message id, null if not found
*/
@Nullable
public String getPluginRequestChannel(int messageId) {
return pluginRequestMap.get(messageId);
}
@Override
public void setConnectionState(@NotNull ConnectionState connectionState) {
super.setConnectionState(connectionState);
// Clear the plugin request map (since it is not used anymore)
if (connectionState.equals(ConnectionState.PLAY)) {
this.pluginRequestMap.clear();
}
}
/**
* Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields.
*
* @param serverAddress the server address which the client used
* @param serverPort the server port which the client used
*/
public void refreshServerInformation(@Nullable String serverAddress, int serverPort) {
this.serverAddress = serverAddress;
this.serverPort = serverPort;
}
public byte[] getNonce() {
return nonce;
}
public void setNonce(byte[] nonce) {
this.nonce = nonce;
}
}