Changes to ChannelRead

This commit is contained in:
LOOHP 2022-02-10 10:08:22 +00:00
parent 31b3a954a2
commit 67f24599f2
4 changed files with 117 additions and 94 deletions

View File

@ -1,6 +1,5 @@
package com.loohp.limbo.network; package com.loohp.limbo.network;
import com.loohp.limbo.network.protocol.packets.Packet;
import com.loohp.limbo.network.protocol.packets.PacketIn; import com.loohp.limbo.network.protocol.packets.PacketIn;
import com.loohp.limbo.network.protocol.packets.PacketOut; import com.loohp.limbo.network.protocol.packets.PacketOut;
import com.loohp.limbo.utils.DataTypeIO; import com.loohp.limbo.utils.DataTypeIO;
@ -10,24 +9,20 @@ import com.loohp.limbo.utils.Pair;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
public class Channel implements AutoCloseable { public class Channel implements AutoCloseable {
private final ClientConnection clientConnection;
private final List<Pair<NamespacedKey, ChannelPacketHandler>> handlers; private final List<Pair<NamespacedKey, ChannelPacketHandler>> handlers;
private final AtomicBoolean valid; private final AtomicBoolean valid;
protected DataOutputStream output; protected final DataInputStream input;
protected DataInputStream input; protected final DataOutputStream output;
public Channel(ClientConnection clientConnection, DataOutputStream output, DataInputStream input) { public Channel(DataInputStream input, DataOutputStream output) {
this.clientConnection = clientConnection;
this.output = output;
this.input = input; this.input = input;
this.output = output;
this.handlers = new CopyOnWriteArrayList<>(); this.handlers = new CopyOnWriteArrayList<>();
this.valid = new AtomicBoolean(true); this.valid = new AtomicBoolean(true);
} }
@ -54,55 +49,20 @@ public class Channel implements AutoCloseable {
return readPacket(-1); return readPacket(-1);
} }
protected PacketIn readPacket(int size) throws Exception { protected PacketIn readPacket(int size) throws IOException {
PacketIn packet = null; PacketIn packet = null;
do { do {
ensureOpen(); ensureOpen();
size = size < 0 ? DataTypeIO.readVarInt(input) : size; size = size < 0 ? DataTypeIO.readVarInt(input) : size;
int packetId = DataTypeIO.readVarInt(input); int packetId = DataTypeIO.readVarInt(input);
Class<? extends PacketIn> packetType; ChannelPacketRead read = new ChannelPacketRead(size, packetId, input);
switch (clientConnection.getClientState()) {
case HANDSHAKE:
packetType = Packet.getHandshakeIn().get(packetId);
break;
case STATUS:
packetType = Packet.getStatusIn().get(packetId);
break;
case LOGIN:
packetType = Packet.getLoginIn().get(packetId);
break;
case PLAY:
packetType = Packet.getPlayIn().get(packetId);
break;
default:
throw new IllegalStateException("Illegal ClientState!");
}
if (packetType == null) {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
} else {
Constructor<?>[] constructors = packetType.getConstructors();
Constructor<?> constructor = Stream.of(constructors).filter(each -> each.getParameterCount() > 0 && each.getParameterTypes()[0].equals(DataInputStream.class)).findFirst().orElse(null);
if (constructor == null) {
throw new NoSuchMethodException(packetType + " has no valid constructors!");
} else if (constructor.getParameterCount() == 1) {
packet = (PacketIn) constructor.newInstance(input);
} else if (constructor.getParameterCount() == 3) {
packet = (PacketIn) constructor.newInstance(input, size, packetId);
} else {
throw new NoSuchMethodException(packetType + " has no valid constructors!");
}
ChannelPacketRead read = new ChannelPacketRead(packetId, packet);
for (Pair<NamespacedKey, ChannelPacketHandler> pair : handlers) { for (Pair<NamespacedKey, ChannelPacketHandler> pair : handlers) {
read = pair.getSecond().read(read); read = pair.getSecond().read(read);
if (read == null) { if (read == null) {
packet = null; packet = null;
break; break;
} }
packet = read.getPacket(); packet = read.getReadPacket();
if (!packetType.isInstance(packet)) {
throw new IllegalStateException("Packet Handler \"" + pair.getFirst() + "\" changed the packet type illegally!");
}
}
} }
size = -1; size = -1;
} while (packet == null); } while (packet == null);
@ -111,30 +71,12 @@ public class Channel implements AutoCloseable {
protected boolean writePacket(PacketOut packet) throws IOException { protected boolean writePacket(PacketOut packet) throws IOException {
ensureOpen(); ensureOpen();
int packetId;
switch (clientConnection.getClientState()) {
case STATUS:
packetId = Packet.getStatusOut().get(packet.getClass());
break;
case LOGIN:
packetId = Packet.getLoginOut().get(packet.getClass());
break;
case PLAY:
packetId = Packet.getPlayOut().get(packet.getClass());
break;
default:
throw new IllegalStateException("Illegal ClientState!");
}
Class<? extends PacketOut> packetType = packet.getClass();
ChannelPacketWrite write = new ChannelPacketWrite(packet); ChannelPacketWrite write = new ChannelPacketWrite(packet);
for (Pair<NamespacedKey, ChannelPacketHandler> pair : handlers) { for (Pair<NamespacedKey, ChannelPacketHandler> pair : handlers) {
write = pair.getSecond().write(write); write = pair.getSecond().write(write);
if (write == null) { if (write == null) {
return false; return false;
} }
if (!packetType.isInstance(write.getPacket())) {
throw new IllegalStateException("Packet Handler \"" + pair.getFirst() + "\" changed the packet type illegally!");
}
} }
packet = write.getPacket(); packet = write.getPacket();
byte[] packetByte = packet.serializePacket(); byte[] packetByte = packet.serializePacket();

View File

@ -2,14 +2,28 @@ package com.loohp.limbo.network;
import com.loohp.limbo.network.protocol.packets.PacketIn; import com.loohp.limbo.network.protocol.packets.PacketIn;
import java.io.DataInput;
public final class ChannelPacketRead { public final class ChannelPacketRead {
private int size;
private int packetId; private int packetId;
private DataInput input;
private PacketIn packet; private PacketIn packet;
protected ChannelPacketRead(int packetId, PacketIn packet) { ChannelPacketRead(int size, int packetId, DataInput input) {
this.size = size;
this.packetId = packetId; this.packetId = packetId;
this.packet = packet; this.input = input;
this.packet = null;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
} }
public int getPacketId() { public int getPacketId() {
@ -20,7 +34,11 @@ public final class ChannelPacketRead {
this.packetId = packetId; this.packetId = packetId;
} }
public PacketIn getPacket() { public boolean hasReadPacket() {
return packet != null;
}
public PacketIn getReadPacket() {
return packet; return packet;
} }
@ -28,4 +46,8 @@ public final class ChannelPacketRead {
this.packet = packet; this.packet = packet;
} }
public DataInput getDataInput() {
return input;
}
} }

View File

@ -6,7 +6,7 @@ public final class ChannelPacketWrite {
private PacketOut packet; private PacketOut packet;
protected ChannelPacketWrite(PacketOut packet) { ChannelPacketWrite(PacketOut packet) {
this.packet = packet; this.packet = packet;
} }

View File

@ -10,6 +10,7 @@ import com.loohp.limbo.events.player.PlayerSelectedSlotChangeEvent;
import com.loohp.limbo.events.status.StatusPingEvent; import com.loohp.limbo.events.status.StatusPingEvent;
import com.loohp.limbo.file.ServerProperties; import com.loohp.limbo.file.ServerProperties;
import com.loohp.limbo.location.Location; import com.loohp.limbo.location.Location;
import com.loohp.limbo.network.protocol.packets.Packet;
import com.loohp.limbo.network.protocol.packets.PacketHandshakingIn; import com.loohp.limbo.network.protocol.packets.PacketHandshakingIn;
import com.loohp.limbo.network.protocol.packets.PacketIn; import com.loohp.limbo.network.protocol.packets.PacketIn;
import com.loohp.limbo.network.protocol.packets.PacketLoginInLoginStart; import com.loohp.limbo.network.protocol.packets.PacketLoginInLoginStart;
@ -60,6 +61,7 @@ import com.loohp.limbo.utils.ForwardingUtils;
import com.loohp.limbo.utils.GameMode; import com.loohp.limbo.utils.GameMode;
import com.loohp.limbo.utils.MojangAPIUtils; import com.loohp.limbo.utils.MojangAPIUtils;
import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse; import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse;
import com.loohp.limbo.utils.NamespacedKey;
import com.loohp.limbo.world.BlockPosition; import com.loohp.limbo.world.BlockPosition;
import com.loohp.limbo.world.World; import com.loohp.limbo.world.World;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -71,9 +73,11 @@ import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser; import org.json.simple.parser.JSONParser;
import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -89,11 +93,14 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ClientConnection extends Thread { public class ClientConnection extends Thread {
private static final NamespacedKey DEFAULT_HANDLER_NAMESPACE = new NamespacedKey("default");
private final Random random = new Random(); private final Random random = new Random();
private final Socket client_socket; private final Socket clientSocket;
protected Channel channel; protected Channel channel;
private boolean running; private boolean running;
private ClientState state; private ClientState state;
@ -105,12 +112,12 @@ public class ClientConnection extends Thread {
private InetAddress inetAddress; private InetAddress inetAddress;
private boolean ready; private boolean ready;
public ClientConnection(Socket client_socket) { public ClientConnection(Socket clientSocket) {
this.client_socket = client_socket; this.clientSocket = clientSocket;
this.inetAddress = client_socket.getInetAddress(); this.inetAddress = clientSocket.getInetAddress();
this.lastPacketTimestamp = new AtomicLong(-1); this.lastPacketTimestamp = new AtomicLong(-1);
this.lastKeepAlivePayLoad = new AtomicLong(-1); this.lastKeepAlivePayLoad = new AtomicLong(-1);
this.channel = new Channel(this, null, null); this.channel = null;
this.running = false; this.running = false;
this.ready = false; this.ready = false;
} }
@ -148,7 +155,7 @@ public class ClientConnection extends Thread {
} }
public Socket getSocket() { public Socket getSocket() {
return client_socket; return clientSocket;
} }
public Channel getChannel() { public Channel getChannel() {
@ -180,7 +187,7 @@ public class ClientConnection extends Thread {
} catch (IOException ignored) { } catch (IOException ignored) {
} }
try { try {
client_socket.close(); clientSocket.close();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
@ -196,27 +203,79 @@ public class ClientConnection extends Thread {
} catch (IOException ignored) { } catch (IOException ignored) {
} }
try { try {
client_socket.close(); clientSocket.close();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
private void setChannel(DataInputStream input, DataOutputStream output) {
this.channel = new Channel(input, output);
this.channel.addHandlerBefore(DEFAULT_HANDLER_NAMESPACE, new ChannelPacketHandler() {
@Override
public ChannelPacketRead read(ChannelPacketRead read) {
if (read.hasReadPacket()) {
return super.read(read);
}
try {
DataInput input = read.getDataInput();
int size = read.getSize();
int packetId = read.getPacketId();
Class<? extends PacketIn> packetType;
switch (state) {
case HANDSHAKE:
packetType = Packet.getHandshakeIn().get(packetId);
break;
case STATUS:
packetType = Packet.getStatusIn().get(packetId);
break;
case LOGIN:
packetType = Packet.getLoginIn().get(packetId);
break;
case PLAY:
packetType = Packet.getPlayIn().get(packetId);
break;
default:
throw new IllegalStateException("Illegal ClientState!");
}
if (packetType == null) {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
return null;
}
Constructor<?>[] constructors = packetType.getConstructors();
Constructor<?> constructor = Stream.of(constructors).filter(each -> each.getParameterCount() > 0 && each.getParameterTypes()[0].equals(DataInputStream.class)).findFirst().orElse(null);
if (constructor == null) {
throw new NoSuchMethodException(packetType + " has no valid constructors!");
} else if (constructor.getParameterCount() == 1) {
read.setPacket((PacketIn) constructor.newInstance(input));
} else if (constructor.getParameterCount() == 3) {
read.setPacket((PacketIn) constructor.newInstance(input, size, packetId));
} else {
throw new NoSuchMethodException(packetType + " has no valid constructors!");
}
return super.read(read);
} catch (Exception e) {
throw new RuntimeException("Unable to read packet", e);
}
}
});
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
running = true; running = true;
state = ClientState.HANDSHAKE; state = ClientState.HANDSHAKE;
try { try {
client_socket.setKeepAlive(true); clientSocket.setKeepAlive(true);
channel.input = new DataInputStream(client_socket.getInputStream()); setChannel(new DataInputStream(clientSocket.getInputStream()), new DataOutputStream(clientSocket.getOutputStream()));
channel.output = new DataOutputStream(client_socket.getOutputStream());
int handShakeSize = DataTypeIO.readVarInt(channel.input); int handShakeSize = DataTypeIO.readVarInt(channel.input);
//legacy ping //legacy ping
if (handShakeSize == 0xFE) { if (handShakeSize == 0xFE) {
state = ClientState.LEGACY; state = ClientState.LEGACY;
channel.output.writeByte(255); channel.output.writeByte(255);
String str = inetAddress.getHostName() + ":" + client_socket.getPort(); String str = inetAddress.getHostName() + ":" + clientSocket.getPort();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Legacy Status has pinged"); Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Legacy Status has pinged");
ServerProperties p = Limbo.getInstance().getServerProperties(); ServerProperties p = Limbo.getInstance().getServerProperties();
StatusPingEvent event = Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), p.getMotd(), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null))); StatusPingEvent event = Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), p.getMotd(), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null)));
@ -226,7 +285,7 @@ public class ClientConnection extends Thread {
channel.output.write(bytes); channel.output.write(bytes);
channel.close(); channel.close();
client_socket.close(); clientSocket.close();
state = ClientState.DISCONNECTED; state = ClientState.DISCONNECTED;
} }
@ -243,10 +302,10 @@ public class ClientConnection extends Thread {
switch (handshake.getHandshakeType()) { switch (handshake.getHandshakeType()) {
case STATUS: case STATUS:
state = ClientState.STATUS; state = ClientState.STATUS;
while (client_socket.isConnected()) { while (clientSocket.isConnected()) {
PacketIn packetIn = channel.readPacket(); PacketIn packetIn = channel.readPacket();
if (packetIn instanceof PacketStatusInRequest) { if (packetIn instanceof PacketStatusInRequest) {
String str = inetAddress.getHostName() + ":" + client_socket.getPort(); String str = inetAddress.getHostName() + ":" + clientSocket.getPort();
if (Limbo.getInstance().getServerProperties().handshakeVerboseEnabled()) { if (Limbo.getInstance().getServerProperties().handshakeVerboseEnabled()) {
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged"); Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged");
} }
@ -302,7 +361,7 @@ public class ClientConnection extends Thread {
} }
int messageId = this.random.nextInt(); int messageId = this.random.nextInt();
while (client_socket.isConnected()) { while (clientSocket.isConnected()) {
PacketIn packetIn = channel.readPacket(); PacketIn packetIn = channel.readPacket();
if (packetIn instanceof PacketLoginInLoginStart) { if (packetIn instanceof PacketLoginInLoginStart) {
PacketLoginInLoginStart start = (PacketLoginInLoginStart) packetIn; PacketLoginInLoginStart start = (PacketLoginInLoginStart) packetIn;
@ -367,7 +426,7 @@ public class ClientConnection extends Thread {
} }
} catch (Exception e) { } catch (Exception e) {
channel.close(); channel.close();
client_socket.close(); clientSocket.close();
state = ClientState.DISCONNECTED; state = ClientState.DISCONNECTED;
} }
@ -401,7 +460,7 @@ public class ClientConnection extends Thread {
PacketPlayOutPlayerAbilities abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F, flags.toArray(new PlayerAbilityFlags[flags.size()])); PacketPlayOutPlayerAbilities abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F, flags.toArray(new PlayerAbilityFlags[flags.size()]));
sendPacket(abilities); sendPacket(abilities);
String str = inetAddress.getHostName() + ":" + client_socket.getPort() + "|" + player.getName() + "(" + player.getUniqueId() + ")"; String str = inetAddress.getHostName() + ":" + clientSocket.getPort() + "|" + player.getName() + "(" + player.getUniqueId() + ")";
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!"); Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!");
PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandsPacket(player); PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandsPacket(player);
@ -465,7 +524,7 @@ public class ClientConnection extends Thread {
}; };
new Timer().schedule(keepAliveTask, 5000, 10000); new Timer().schedule(keepAliveTask, 5000, 10000);
while (client_socket.isConnected()) { while (clientSocket.isConnected()) {
try { try {
CheckedBiConsumer<PlayerMoveEvent, Location, IOException> processMoveEvent = (event, originalTo) -> { CheckedBiConsumer<PlayerMoveEvent, Location, IOException> processMoveEvent = (event, originalTo) -> {
if (event.isCancelled()) { if (event.isCancelled()) {
@ -565,7 +624,7 @@ public class ClientConnection extends Thread {
Limbo.getInstance().getEventsManager().callEvent(new PlayerQuitEvent(player)); Limbo.getInstance().getEventsManager().callEvent(new PlayerQuitEvent(player));
str = inetAddress.getHostName() + ":" + client_socket.getPort() + "|" + player.getName(); str = inetAddress.getHostName() + ":" + clientSocket.getPort() + "|" + player.getName();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had disconnected!"); Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had disconnected!");
} }
@ -575,7 +634,7 @@ public class ClientConnection extends Thread {
try { try {
channel.close(); channel.close();
client_socket.close(); clientSocket.close();
} catch (Exception ignored) { } catch (Exception ignored) {
} }
state = ClientState.DISCONNECTED; state = ClientState.DISCONNECTED;