diff --git a/pom.xml b/pom.xml
index 34672b6f3..2261f9583 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,5 +119,11 @@
jcl-core
2.8
+
+ io.netty
+ netty-all
+ 4.1.52.Final
+
+
\ No newline at end of file
diff --git a/src/main/java/de/bixilon/minosoft/modding/MinosoftMod.java b/src/main/java/de/bixilon/minosoft/modding/MinosoftMod.java
index c74fae74f..5328049d8 100644
--- a/src/main/java/de/bixilon/minosoft/modding/MinosoftMod.java
+++ b/src/main/java/de/bixilon/minosoft/modding/MinosoftMod.java
@@ -17,15 +17,7 @@ import de.bixilon.minosoft.modding.event.EventManager;
import de.bixilon.minosoft.modding.loading.ModInfo;
import de.bixilon.minosoft.modding.loading.ModPhases;
-interface MinosoftModInterface {
- /**
- * @param phase The current loading phase
- * @return If the loading was successful. If not, the mod is getting disabled.
- */
- boolean start(ModPhases phase);
-}
-
-public abstract class MinosoftMod implements MinosoftModInterface {
+public abstract class MinosoftMod {
private final EventManager eventManager = new EventManager();
protected boolean enabled = true;
private ModInfo info;
@@ -58,4 +50,10 @@ public abstract class MinosoftMod implements MinosoftModInterface {
public Logger getLogger() {
return logger;
}
+
+ /**
+ * @param phase The current loading phase
+ * @return If the loading was successful. If not, the mod is getting disabled.
+ */
+ public abstract boolean start(ModPhases phase);
}
\ No newline at end of file
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
index ef026e28e..0e8955444 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
@@ -25,7 +25,6 @@ import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions;
import de.bixilon.minosoft.gui.main.ConnectionChangeCallback;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
-import de.bixilon.minosoft.modding.event.EventListener;
import de.bixilon.minosoft.modding.event.EventManager;
import de.bixilon.minosoft.ping.ServerListPing;
import de.bixilon.minosoft.protocol.modding.channels.DefaultPluginChannels;
@@ -50,7 +49,7 @@ import java.util.concurrent.LinkedBlockingQueue;
public class Connection {
public static int lastConnectionId;
- final Network network = new Network(this);
+ final Network network = Network.getNetworkInstance(this);
final PacketHandler handler = new PacketHandler(this);
final PacketSender sender = new PacketSender(this);
final LinkedBlockingQueue handlingQueue = new LinkedBlockingQueue<>();
@@ -73,6 +72,7 @@ public class Connection {
ConnectionReasons nextReason;
ConnectionPing connectionStatusPing;
ServerListPing lastPing;
+ Exception lastException;
public Connection(int connectionId, String hostname, Player player) {
this.connectionId = connectionId;
@@ -81,7 +81,7 @@ public class Connection {
}
public void resolve(ConnectionReasons reason, int protocolId) {
- network.lastException = null;
+ lastException = null;
this.desiredVersionNumber = protocolId;
Thread resolveThread = new Thread(() -> {
@@ -93,7 +93,7 @@ public class Connection {
addresses = DNSUtil.getServerAddresses(hostname);
} catch (TextParseException e) {
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
- network.lastException = e;
+ lastException = e;
e.printStackTrace();
return;
}
@@ -232,7 +232,7 @@ public class Connection {
e.printStackTrace();
}
Log.fatal(String.format("Could not load mapping for %s. This version seems to be unsupported!", version));
- network.lastException = new RuntimeException(String.format("Mappings could not be loaded: %s", e.getLocalizedMessage()));
+ lastException = new RuntimeException(String.format("Mappings could not be loaded: %s", e.getLocalizedMessage()));
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
}
}
@@ -396,7 +396,7 @@ public class Connection {
}
public Exception getLastConnectionException() {
- return network.lastException;
+ return (lastException != null) ? lastException : network.getLastException();
}
public void addConnectionChangeCallback(ConnectionChangeCallback callback) {
@@ -422,8 +422,4 @@ public class Connection {
public void unregisterEvents(EventManager... eventManagers) {
this.eventManagers.removeAll(Arrays.asList(eventManagers));
}
-
- public HashSet getAllEvents() {
- return eventManagers;
- }
}
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
index 7d16ce8bd..d978071d5 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
@@ -13,270 +13,20 @@
package de.bixilon.minosoft.protocol.network;
-import de.bixilon.minosoft.logging.Log;
-import de.bixilon.minosoft.logging.LogLevels;
-import de.bixilon.minosoft.protocol.packets.ClientboundPacket;
+import de.bixilon.minosoft.protocol.network.socket.SocketNetwork;
import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
-import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.PacketCompressionInterface;
-import de.bixilon.minosoft.protocol.packets.clientbound.login.PacketEncryptionRequest;
-import de.bixilon.minosoft.protocol.packets.clientbound.login.PacketLoginSuccess;
-import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse;
-import de.bixilon.minosoft.protocol.protocol.*;
import de.bixilon.minosoft.util.ServerAddress;
-import de.bixilon.minosoft.util.Util;
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class Network {
- final Connection connection;
- final LinkedBlockingQueue queue = new LinkedBlockingQueue<>();
- Thread socketRThread;
- Thread socketSThread;
- int compressionThreshold = -1;
- Socket socket;
- OutputStream outputStream;
- InputStream inputStream;
- boolean encryptionEnabled = false;
- SecretKey secretKey;
- Exception lastException;
-
- public Network(Connection connection) {
- this.connection = connection;
+public interface Network {
+ static Network getNetworkInstance(Connection connection) {
+ return new SocketNetwork(connection);
}
- public void connect(ServerAddress address) {
- // check if we are already connected or try to connect
- if (connection.isConnected() || connection.getConnectionState() == ConnectionStates.CONNECTING) {
- return;
- }
- // wait for data or send until it should disconnect
- // first send, then receive
- // something to send it, send it
- // send, flush and remove
- // everything sent for now, waiting for data
- // add to queue
- // Could not connect
- socketRThread = new Thread(() -> {
- try {
- socket = new Socket();
- socket.setSoTimeout(ProtocolDefinition.SOCKET_CONNECT_TIMEOUT);
- socket.connect(new InetSocketAddress(address.getHostname(), address.getPort()), ProtocolDefinition.SOCKET_CONNECT_TIMEOUT);
- // connected, use minecraft timeout
- socket.setSoTimeout(ProtocolDefinition.SOCKET_TIMEOUT);
- connection.setConnectionState(ConnectionStates.HANDSHAKING);
- socket.setKeepAlive(true);
- outputStream = socket.getOutputStream();
- inputStream = socket.getInputStream();
+ void connect(ServerAddress address);
- socketRThread.setName(String.format("%d/SocketR", connection.getConnectionId()));
+ void sendPacket(ServerboundPacket packet);
- socketSThread = new Thread(() -> {
- try {
- while (connection.getConnectionState() != ConnectionStates.DISCONNECTING) {
- // wait for data or send until it should disconnect
+ void disconnect();
- // check if still connected
- if (!socket.isConnected() || socket.isClosed()) {
- break;
- }
-
- ServerboundPacket packet = queue.take();
- packet.log();
- queue.remove(packet);
- byte[] data = packet.write(connection).getOutBytes();
- if (compressionThreshold >= 0) {
- // compression is enabled
- // check if there is a need to compress it and if so, do it!
- OutByteBuffer outRawBuffer = new OutByteBuffer(connection);
- if (data.length >= compressionThreshold) {
- // compress it
- OutByteBuffer compressedBuffer = new OutByteBuffer(connection);
- byte[] compressed = Util.compress(data);
- compressedBuffer.writeVarInt(data.length);
- compressedBuffer.writeBytes(compressed);
- outRawBuffer.writeVarInt(compressedBuffer.getOutBytes().length);
- outRawBuffer.writeBytes(compressedBuffer.getOutBytes());
- } else {
- outRawBuffer.writeVarInt(data.length + 1); // 1 for the compressed length (0)
- outRawBuffer.writeVarInt(0);
- outRawBuffer.writeBytes(data);
- }
- data = outRawBuffer.getOutBytes();
- } else {
- // append packet length
- OutByteBuffer bufferWithLengthPrefix = new OutByteBuffer(connection);
- bufferWithLengthPrefix.writeVarInt(data.length);
- bufferWithLengthPrefix.writeBytes(data);
- data = bufferWithLengthPrefix.getOutBytes();
- }
-
- outputStream.write(data);
- outputStream.flush();
- if (packet instanceof PacketEncryptionResponse) {
- // enable encryption
- secretKey = ((PacketEncryptionResponse) packet).getSecretKey();
- enableEncryption(secretKey);
- // wake up other thread
- socketRThread.interrupt();
- }
- }
- } catch (IOException | InterruptedException ignored) {
- }
- });
- socketSThread.setName(String.format("%d/SocketS", connection.getConnectionId()));
- socketSThread.start();
-
- while (connection.getConnectionState() != ConnectionStates.DISCONNECTING) {
- // wait for data or send until it should disconnect
- // first send, then receive
-
- // check if still connected
- if (!socket.isConnected() || socket.isClosed()) {
- break;
- }
-
-// everything sent for now, waiting for data
- int numRead = 0;
- int length = 0;
- int read;
- do {
- read = inputStream.read();
- if (read == -1) {
- disconnect();
- return;
- }
- int value = (read & 0b01111111);
- length |= (value << (7 * numRead));
-
- numRead++;
- if (numRead > 5) {
- throw new RuntimeException("VarInt is too big");
- }
- } while ((read & 0b10000000) != 0);
- if (length > ProtocolDefinition.PROTOCOL_PACKET_MAX_SIZE) {
- Log.protocol(String.format("Server sent us a to big packet (%d bytes > %d bytes)", length, ProtocolDefinition.PROTOCOL_PACKET_MAX_SIZE));
- inputStream.skip(length);
- continue;
- }
- byte[] data = inputStream.readNBytes(length);
-
- if (compressionThreshold >= 0) {
- // compression is enabled
- // check if there is a need to decompress it and if so, do it!
- InByteBuffer rawBuffer = new InByteBuffer(data, connection);
- int packetSize = rawBuffer.readVarInt();
- byte[] left = rawBuffer.readBytesLeft();
- if (packetSize == 0) {
- // no need
- data = left;
- } else {
- // need to decompress data
- data = Util.decompress(left, connection).readBytesLeft();
- }
- }
-
- InPacketBuffer inPacketBuffer = new InPacketBuffer(data, connection);
- Packets.Clientbound packet = null;
- try {
- packet = connection.getPacketByCommand(connection.getConnectionState(), inPacketBuffer.getCommand());
- if (packet == null) {
- Log.fatal(String.format("Version packet enum does not contain a packet with id 0x%x. Your version.json is broken!", inPacketBuffer.getCommand()));
- System.exit(1);
- }
- Class extends ClientboundPacket> clazz = packet.getClazz();
-
- if (clazz == null) {
- Log.warn(String.format("[IN] Received unknown packet (id=0x%x, name=%s, length=%d, dataLength=%d, version=%s, state=%s)", inPacketBuffer.getCommand(), packet, inPacketBuffer.getLength(), inPacketBuffer.getBytesLeft(), connection.getVersion(), connection.getConnectionState()));
- continue;
- }
- try {
- ClientboundPacket packetInstance = clazz.getConstructor().newInstance();
- boolean success = packetInstance.read(inPacketBuffer);
- if (inPacketBuffer.getBytesLeft() > 0 || !success) {
- // warn not all data used
- Log.warn(String.format("[IN] Could not parse packet %s (used=%d, available=%d, total=%d, success=%s)", packet, inPacketBuffer.getPosition(), inPacketBuffer.getBytesLeft(), inPacketBuffer.getLength(), success));
- continue;
- }
-
- //set special settings to avoid miss timing issues
- if (packetInstance instanceof PacketLoginSuccess) {
- connection.setConnectionState(ConnectionStates.PLAY);
- } else if (packetInstance instanceof PacketCompressionInterface) {
- compressionThreshold = ((PacketCompressionInterface) packetInstance).getThreshold();
- } else if (packetInstance instanceof PacketEncryptionRequest) {
- // wait until response is ready
- connection.handle(packetInstance);
- try {
- Thread.sleep(Integer.MAX_VALUE);
- } catch (InterruptedException ignored) {
- }
- continue;
- }
- connection.handle(packetInstance);
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
- // safety first, but will not occur
- e.printStackTrace();
- }
- } catch (Exception e) {
- Log.protocol(String.format("An error occurred while parsing an packet (%s): %s", packet, e));
- if (Log.getLevel().ordinal() >= LogLevels.DEBUG.ordinal()) {
- e.printStackTrace();
- }
- }
- }
- disconnect();
- } catch (IOException e) {
- // Could not connect
- if (Log.getLevel().ordinal() >= LogLevels.DEBUG.ordinal()) {
- e.printStackTrace();
- }
- if (socketSThread != null) {
- socketSThread.interrupt();
- }
- if (e instanceof SocketException && e.getMessage().equals("Socket closed")) {
- return;
- }
- lastException = e;
- connection.setConnectionState(ConnectionStates.FAILED);
- }
- });
- socketRThread.setName(String.format("%d/Socket", connection.getConnectionId()));
- socketRThread.start();
- }
-
- public void sendPacket(ServerboundPacket p) {
- queue.add(p);
- }
-
- public void enableEncryption(SecretKey secretKey) {
- Cipher cipherEncrypt = CryptManager.createNetCipherInstance(Cipher.ENCRYPT_MODE, secretKey);
- Cipher cipherDecrypt = CryptManager.createNetCipherInstance(Cipher.DECRYPT_MODE, secretKey);
- inputStream = new CipherInputStream(inputStream, cipherDecrypt);
- outputStream = new CipherOutputStream(outputStream, cipherEncrypt);
- encryptionEnabled = true;
- Log.debug("Encryption enabled!");
- }
-
- public void disconnect() {
- connection.setConnectionState(ConnectionStates.DISCONNECTING);
- try {
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- socketRThread.interrupt();
- socketSThread.interrupt();
- connection.setConnectionState(ConnectionStates.DISCONNECTED);
- }
+ Exception getLastException();
}
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/netty/NettyNetwork.java b/src/main/java/de/bixilon/minosoft/protocol/network/netty/NettyNetwork.java
new file mode 100644
index 000000000..989009233
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/netty/NettyNetwork.java
@@ -0,0 +1,101 @@
+/*
+ * Codename Minosoft
+ * Copyright (C) 2020 Moritz Zwerger
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If not, see .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.protocol.network.netty;
+
+import de.bixilon.minosoft.logging.Log;
+import de.bixilon.minosoft.protocol.network.Connection;
+import de.bixilon.minosoft.protocol.network.Network;
+import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
+import de.bixilon.minosoft.protocol.protocol.ConnectionStates;
+import de.bixilon.minosoft.util.ServerAddress;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+
+import javax.crypto.SecretKey;
+
+public class NettyNetwork implements Network {
+ final Connection connection;
+ NioSocketChannel nioSocketChannel;
+ int compressionThreshold = -1;
+
+ public NettyNetwork(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public void connect(ServerAddress address) {
+ EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
+
+ Bootstrap clientBootstrap = new Bootstrap();
+ clientBootstrap.group(eventLoopGroup)
+ .channel(NioSocketChannel.class)
+ .handler(new TCPClientChannelInitializer(connection, this));
+
+
+ try {
+ ChannelFuture channelFuture = clientBootstrap.connect(address.getHostname(), address.getPort()).sync();
+ if (channelFuture.isSuccess()) {
+ connection.setConnectionState(ConnectionStates.HANDSHAKING);
+ }
+ channelFuture.channel().closeFuture().sync();
+ } catch (InterruptedException e) {
+ Log.info(String.format("connection failed: %s", e));
+ connection.setConnectionState(ConnectionStates.FAILED);
+ } finally {
+ connection.setConnectionState(ConnectionStates.DISCONNECTED);
+ eventLoopGroup.shutdownGracefully();
+ }
+ }
+
+ @Override
+ public void sendPacket(ServerboundPacket packet) {
+ if (this.nioSocketChannel.eventLoop().inEventLoop()) {
+ this.nioSocketChannel.writeAndFlush(packet);
+ return;
+ }
+ this.nioSocketChannel.eventLoop().execute(() -> NettyNetwork.this.nioSocketChannel.writeAndFlush(packet));
+ }
+
+ @Override
+ public void disconnect() {
+
+ }
+
+ @Override
+ public Exception getLastException() {
+ return null;
+ }
+
+ public int getCompressionThreshold() {
+ return compressionThreshold;
+ }
+
+ public void setNioChannel(NioSocketChannel nioSocketChannel) {
+ this.nioSocketChannel = nioSocketChannel;
+ }
+
+ public void enableEncryption(SecretKey key) {
+ /*
+ this.nioSocketChannel.pipeline().addBefore("decoder", "decrypt", new EncryptionHandler(key));
+ this.nioSocketChannel.pipeline().addBefore("encoder", "encrypt", new DecryptionHandler(key));
+ Log.debug("Encryption enabled!");
+ */
+ //ToDo
+ Log.fatal("Encryption is not implemented in netty yet!");
+ disconnect();
+ }
+}
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/netty/PacketDecoder.java b/src/main/java/de/bixilon/minosoft/protocol/network/netty/PacketDecoder.java
new file mode 100644
index 000000000..eb9754d44
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/netty/PacketDecoder.java
@@ -0,0 +1,141 @@
+/*
+ * Codename Minosoft
+ * Copyright (C) 2020 Moritz Zwerger
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If not, see .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.protocol.network.netty;
+
+
+import de.bixilon.minosoft.logging.Log;
+import de.bixilon.minosoft.logging.LogLevels;
+import de.bixilon.minosoft.protocol.network.Connection;
+import de.bixilon.minosoft.protocol.packets.ClientboundPacket;
+import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.PacketCompressionInterface;
+import de.bixilon.minosoft.protocol.packets.clientbound.login.PacketEncryptionRequest;
+import de.bixilon.minosoft.protocol.packets.clientbound.login.PacketLoginSuccess;
+import de.bixilon.minosoft.protocol.protocol.*;
+import de.bixilon.minosoft.util.Util;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+public class PacketDecoder extends ByteToMessageDecoder {
+ final Connection connection;
+ final NettyNetwork nettyNetwork;
+
+ public PacketDecoder(Connection connection, NettyNetwork nettyNetwork) {
+ this.connection = connection;
+ this.nettyNetwork = nettyNetwork;
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List