From b3f8103a55c7116275fc9cacc5e5abb50cac6373 Mon Sep 17 00:00:00 2001 From: dorving Date: Mon, 11 Jul 2022 04:53:12 +0200 Subject: [PATCH 1/5] Rewritten NetworkExtension system to use Netty and cleaned up a lot of the code. --- .../java/gearth/extensions/Extension.java | 30 +- .../ExtensionProducerFactory.java | 10 +- .../network/NetworkExtension.java | 258 -------------- ...ava => NetworkExtensionAuthenticator.java} | 103 +++--- .../network/NetworkExtensionClient.java | 188 ++++++++++ .../network/NetworkExtensionCodec.java | 295 ++++++++++++++++ .../network/NetworkExtensionInfo.java | 110 ------ .../network/NetworkExtensionMessage.java | 323 ++++++++++++++++++ .../network/NetworkExtensionServer.java | 257 ++++++++++++++ .../network/NetworkExtensionsProducer.java | 167 --------- .../network/executer/ExecutionInfo.java | 44 +-- .../executer/ExtensionRunnerFactory.java | 4 +- .../executer/NormalExtensionRunner.java | 194 +++++------ .../StoreExtensionDetailsOverview.java | 7 +- .../tools/StoreExtensionTools.java | 6 +- .../extensions/ExtensionItemContainer.java | 2 +- .../extensions/ExtensionsController.java | 14 +- 17 files changed, 1267 insertions(+), 745 deletions(-) delete mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtension.java rename G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/{authentication/Authenticator.java => NetworkExtensionAuthenticator.java} (52%) create mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java create mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java delete mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionInfo.java create mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java create mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java delete mode 100644 G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionsProducer.java diff --git a/G-Earth/src/main/java/gearth/extensions/Extension.java b/G-Earth/src/main/java/gearth/extensions/Extension.java index 9409af0..55c80db 100644 --- a/G-Earth/src/main/java/gearth/extensions/Extension.java +++ b/G-Earth/src/main/java/gearth/extensions/Extension.java @@ -1,12 +1,14 @@ package gearth.extensions; import gearth.misc.HostInfo; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing; import gearth.services.packet_info.PacketInfoManager; import gearth.protocol.HMessage; import gearth.protocol.HPacket; import gearth.protocol.connection.HClient; import gearth.services.Constants; -import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionInfo; import java.io.*; import java.net.Socket; @@ -108,10 +110,10 @@ public abstract class Extension extends ExtensionBase { packet.fixLength(); - if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST) { + if (packet.headerId() == Outgoing.InfoRequest.HEADER_ID) { ExtensionInfo info = getInfoAnnotations(); - HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO); + HPacket response = new HPacket(Incoming.ExtensionInfo.HEADER_ID); response.appendString(info.Title()) .appendString(info.Author()) .appendString(info.Version()) @@ -124,7 +126,7 @@ public abstract class Extension extends ExtensionBase { .appendBoolean(canDelete()); writeToStream(response.toBytes()); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) { + else if (packet.headerId() == Outgoing.ConnectionStart.HEADER_ID) { String host = packet.readString(); int connectionPort = packet.readInteger(); String hotelVersion = packet.readString(); @@ -143,10 +145,10 @@ public abstract class Extension extends ExtensionBase { ); onStartConnection(); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) { + else if (packet.headerId() == Outgoing.ConnectionEnd.HEADER_ID) { onEndConnection(); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK) { + else if (packet.headerId() == Outgoing.FlagsCheck.HEADER_ID) { // body = an array of G-Earths gearth flags if (flagRequestCallback != null) { int arraysize = packet.readInteger(); @@ -158,7 +160,7 @@ public abstract class Extension extends ExtensionBase { } flagRequestCallback = null; } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT) { + else if (packet.headerId() == Outgoing.Init.HEADER_ID) { delayed_init = packet.readBoolean(); HostInfo hostInfo = HostInfo.fromPacket(packet); updateHostInfo(hostInfo); @@ -167,21 +169,21 @@ public abstract class Extension extends ExtensionBase { } writeToConsole("green","Extension \"" + getInfoAnnotations().Title() + "\" successfully initialized", false); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK) { + else if (packet.headerId() == Outgoing.OnDoubleClick.HEADER_ID) { onClick(); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) { + else if (packet.headerId() == Outgoing.PacketIntercept.HEADER_ID) { String stringifiedMessage = packet.readLongString(); HMessage habboMessage = new HMessage(stringifiedMessage); modifyMessage(habboMessage); - HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET); + HPacket response = new HPacket(Incoming.ManipulatedPacket.MANIPULATED_PACKET); response.appendLongString(habboMessage.stringify()); writeToStream(response.toBytes()); } - else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO) { + else if (packet.headerId() == Outgoing.UpdateHostInfo.HEADER_ID) { HostInfo hostInfo = HostInfo.fromPacket(packet); updateHostInfo(hostInfo); } @@ -231,7 +233,7 @@ public abstract class Extension extends ExtensionBase { if (!packet.isPacketComplete()) packet.completePacket(packetInfoManager); if (!packet.isPacketComplete()) return false; - HPacket packet1 = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE); + HPacket packet1 = new HPacket(Incoming.SendMessage.HEADER_ID); packet1.appendByte(direction == HMessage.Direction.TOCLIENT ? (byte)0 : (byte)1); packet1.appendInt(packet.getBytesLength()); packet1.appendBytes(packet.toBytes()); @@ -253,7 +255,7 @@ public abstract class Extension extends ExtensionBase { if (this.flagRequestCallback != null) return false; this.flagRequestCallback = flagRequestCallback; try { - writeToStream(new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS).toBytes()); + writeToStream(new HPacket(Incoming.RequestFlags.HEADER_ID).toBytes()); return true; } catch (IOException e) { e.printStackTrace(); @@ -279,7 +281,7 @@ public abstract class Extension extends ExtensionBase { private void writeToConsole(String colorClass, String s, boolean mentionTitle) { String text = "[" + colorClass + "]" + (mentionTitle ? (getInfoAnnotations().Title() + " --> ") : "") + s; - HPacket packet = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG); + HPacket packet = new HPacket(Incoming.ExtensionConsoleLog.HEADER_ID); packet.appendString(text); try { writeToStream(packet.toBytes()); diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/extensionproducers/ExtensionProducerFactory.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/extensionproducers/ExtensionProducerFactory.java index bfb8dc2..9600a9c 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/extensionproducers/ExtensionProducerFactory.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/extensionproducers/ExtensionProducerFactory.java @@ -1,6 +1,6 @@ package gearth.services.extension_handler.extensions.extensionproducers; -import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer; import gearth.services.extension_handler.extensions.implementations.simple.SimpleExtensionProducer; import java.util.ArrayList; @@ -9,13 +9,17 @@ import java.util.List; public class ExtensionProducerFactory { // returns one of every ExtensionProducer class we have created, to support all types of extensions + private final static NetworkExtensionServer EXTENSION_SERVER = new NetworkExtensionServer(); + public static List getAll() { List all = new ArrayList<>(); - all.add(new NetworkExtensionsProducer()); + all.add(EXTENSION_SERVER); all.add(new SimpleExtensionProducer()); return all; } - + public static NetworkExtensionServer getExtensionServer() { + return EXTENSION_SERVER; + } } diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtension.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtension.java deleted file mode 100644 index 8b55c95..0000000 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtension.java +++ /dev/null @@ -1,258 +0,0 @@ -package gearth.services.extension_handler.extensions.implementations.network; - -import gearth.misc.HostInfo; -import gearth.services.packet_info.PacketInfoManager; -import gearth.protocol.HMessage; -import gearth.protocol.connection.HClient; -import gearth.services.extension_handler.extensions.ExtensionType; -import gearth.services.extension_handler.extensions.GEarthExtension; -import gearth.protocol.HPacket; - -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -/** - * Created by Jonas on 21/06/18. - */ -public class NetworkExtension extends GEarthExtension { - - private String title; - private String author; - private String version; - private String description; - - private boolean fireEventButtonVisible; - private boolean leaveButtonVisible; - private boolean deleteButtonVisible; - - private boolean isInstalledExtension; // <- extension is in the extensions directory - private String fileName; - private String cookie; - - private Socket connection; - - NetworkExtension(HPacket extensionInfo, Socket connection) { - this.title = extensionInfo.readString(); - this.author = extensionInfo.readString(); - this.version = extensionInfo.readString(); - this.description = extensionInfo.readString(); - this.fireEventButtonVisible = extensionInfo.readBoolean(); - - this.isInstalledExtension = extensionInfo.readBoolean(); - this.fileName = extensionInfo.readString(); - this.cookie = extensionInfo.readString(); - - this.leaveButtonVisible = extensionInfo.readBoolean(); - this.deleteButtonVisible = extensionInfo.readBoolean(); - - this.connection = connection; - - NetworkExtension selff = this; - new Thread(() -> { - try { - InputStream inputStream = connection.getInputStream(); - DataInputStream dIn = new DataInputStream(inputStream); - - while (!connection.isClosed()) { - int length = dIn.readInt(); - byte[] headerandbody = new byte[length + 4]; - - int amountRead = 0; - while (amountRead < length) { - amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead)); - } - - HPacket message = new HPacket(headerandbody); - message.fixLength(); - - synchronized (selff.extensionObservable) { - if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS) { - requestFlags(); - } - else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE) { - byte side = message.readByte(); - int byteLength = message.readInteger(); - byte[] packetAsByteArray = message.readBytes(byteLength); - - HPacket packet = new HPacket(packetAsByteArray); - if (!packet.isCorrupted()) { - sendMessage( - side == 0 ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER, - packet - ); - } - } - else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET) { - String stringifiedresponse = message.readLongString(6); - HMessage responseMessage = new HMessage(stringifiedresponse); - sendManipulatedPacket(responseMessage); - } - else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG) { - log(message.readString()); - } - else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.PACKETTOSTRING_REQUEST) { - HPacket p = new HPacket(new byte[0]); - p.constructFromString(message.readLongString()); - packetToStringRequest(p); - } - else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.STRINGTOPACKET_REQUEST) { - stringToPacketRequest(message.readLongString(StandardCharsets.UTF_8)); - } - - } - - } - - } catch (IOException e) { - // An extension disconnected, which is OK - } finally { - synchronized (selff.extensionObservable) { - hasClosed(); - } - if (!connection.isClosed()) { - try { - connection.close(); - } catch (IOException e) { -// e.printStackTrace(); - } - } - } - }).start(); - - - } - - - public String getAuthor() { - return author; - } - public String getDescription() { - return description; - } - public String getTitle() { - return title; - } - public String getVersion() { - return version; - } - public boolean isFireButtonUsed() { - return fireEventButtonVisible; - } - public String getFileName() { - return fileName; - } - public String getCookie() { - return cookie; - } - public boolean isDeleteButtonVisible() { - return deleteButtonVisible; - } - public boolean isLeaveButtonVisible() { - return leaveButtonVisible; - } - - public boolean isInstalledExtension() { - return isInstalledExtension; - } - - - private boolean sendMessage(HPacket message) { - try { - synchronized (this) { - connection.getOutputStream().write(message.toBytes()); - } - return true; - } catch (IOException e) { - return false; - } - } - - @Override - public void doubleclick() { - sendMessage(new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK)); - } - - @Override - public void packetIntercept(HMessage hMessage) { - String stringified = hMessage.stringify(); - HPacket manipulatePacketRequest = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT); - manipulatePacketRequest.appendLongString(stringified); - sendMessage(manipulatePacketRequest); - } - - @Override - public void provideFlags(String[] flags) { - HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK); - packet.appendInt(flags.length); - for (String flag : flags) { - packet.appendString(flag); - } - sendMessage(packet); - } - - @Override - public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) { - HPacket connectionStartPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) - .appendString(host) - .appendInt(port) - .appendString(hotelVersion) - .appendString(clientIdentifier) - .appendString(clientType.name()); - - packetInfoManager.appendToPacket(connectionStartPacket); - sendMessage(connectionStartPacket); - } - - @Override - public void connectionEnd() { - sendMessage( - new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) - ); - } - - @Override - public void init(boolean isConnected, HostInfo hostInfo) { - HPacket initPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT); - initPacket.appendBoolean(isConnected); - hostInfo.appendToPacket(initPacket); - - sendMessage(initPacket); - } - - @Override - public void close() { - try { - connection.close(); - } catch (IOException ignored) { } - } - - @Override - public void updateHostInfo(HostInfo hostInfo) { - HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO); - hostInfo.appendToPacket(packet); - sendMessage(packet); - } - - @Override - public void packetToStringResponse(String string, String expression) { - HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETTOSTRING_RESPONSE); - packet.appendLongString(string); - packet.appendLongString(expression, StandardCharsets.UTF_8); - sendMessage(packet); - } - - @Override - public void stringToPacketResponse(HPacket packetFromString) { - HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.STRINGTOPACKET_RESPONSE); - packet.appendLongString(packetFromString.stringify()); - sendMessage(packet); - } - - @Override - public ExtensionType extensionType() { - return ExtensionType.EXTERNAL; - } -} \ No newline at end of file diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/authentication/Authenticator.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionAuthenticator.java similarity index 52% rename from G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/authentication/Authenticator.java rename to G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionAuthenticator.java index 5f6b694..8aff7a7 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/authentication/Authenticator.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionAuthenticator.java @@ -1,7 +1,6 @@ -package gearth.services.extension_handler.extensions.implementations.network.authentication; +package gearth.services.extension_handler.extensions.implementations.network; import gearth.misc.ConfirmationDialog; -import gearth.services.extension_handler.extensions.implementations.network.NetworkExtension; import gearth.ui.titlebar.TitleBarController; import javafx.application.Platform; import javafx.scene.control.Alert; @@ -10,70 +9,67 @@ import javafx.scene.control.Label; import java.io.IOException; import java.util.*; +import java.util.concurrent.CountDownLatch; /** * Created by Jonas on 16/10/18. */ -public class Authenticator { +public final class NetworkExtensionAuthenticator { - private static Map cookies = new HashMap<>(); - private static Set perma_cookies = new HashSet<>(); + private static final Map COOKIES = new HashMap<>(); + private static final Set PERSISTENT_COOKIES = new HashSet<>(); - public static String generateCookieForExtension(String filename) { - String cookie = getRandomCookie(); - cookies.put(filename, cookie); - return cookie; - } - public static String generatePermanentCookie() { - String cookie = getRandomCookie(); - perma_cookies.add(cookie); - return cookie; - } + private static volatile boolean rememberOption = false; - public static boolean evaluate(NetworkExtension extension) { - if (extension.getCookie() != null && perma_cookies.contains(extension.getCookie())) { + public static boolean evaluate(NetworkExtensionClient extension) { + + final String cookie = extension.getCookie(); + + if (cookie != null && PERSISTENT_COOKIES.contains(cookie)) return true; - } - if (extension.isInstalledExtension()) { - return claimSession(extension.getFileName(), extension.getCookie()); - } - else { - return askForPermission(extension); - } + return extension.isInstalledExtension() + ? claimSession(extension.getFileName(), cookie) + : askForPermission(extension); } /** - * authenticator: authenticate an extension and remove the cookie - * @param filename - * @param cookie - * @return if the extension is authenticated + * Authenticate an extension and remove the cookie + * + * @return {@code true} if the extension is authenticated. */ private static boolean claimSession(String filename, String cookie) { - if (cookies.containsKey(filename) && cookies.get(filename).equals(cookie)) { - cookies.remove(filename); + if (COOKIES.containsKey(filename) && COOKIES.get(filename).equals(cookie)) { + COOKIES.remove(filename); return true; } return false; } - private static volatile boolean rememberOption = false; - //for not-installed extensions, popup a dialog - private static boolean askForPermission(NetworkExtension extension) { + /** + * For not yet installed extensions, open a confirmation dialog. + * + * @param extension the {@link NetworkExtensionClient extension} to ask permission for. + * + * @return {@code true} if permission is granted, {@code false} if not. + */ + private static boolean askForPermission(NetworkExtensionClient extension) { boolean[] allowConnection = {true}; final String connectExtensionKey = "allow_extension_connection"; if (ConfirmationDialog.showDialog(connectExtensionKey)) { - boolean[] done = {false}; + + final CountDownLatch countDownLatch = new CountDownLatch(0); + Platform.runLater(() -> { Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey - ,"Confirmation Dialog", null, + , "Confirmation Dialog", null, "", "Remember my choice", ButtonType.YES, ButtonType.NO ); - alert.getDialogPane().setContent(new Label("Extension \""+extension.getTitle()+"\" tries to connect but isn't known to G-Earth,\n" + + alert.getDialogPane().setContent(new Label("Extension \"" + extension.getTitle() + "\" tries to connect but isn't known to G-Earth,\n" + "accept this connection?")); try { @@ -84,33 +80,40 @@ public class Authenticator { } catch (IOException e) { e.printStackTrace(); } - done[0] = true; + countDownLatch.countDown(); if (!ConfirmationDialog.showDialog(connectExtensionKey)) { rememberOption = allowConnection[0]; } }); - while (!done[0]) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + countDownLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); } - return allowConnection[0]; } return rememberOption; } - private static String getRandomCookie() { - StringBuilder builder = new StringBuilder(); - Random r = new Random(); - for (int i = 0; i < 40; i++) { - builder.append(r.nextInt(40)); - } + public static String generateCookieForExtension(String filename) { + final String cookie = generateRandomCookie(); + COOKIES.put(filename, cookie); + return cookie; + } + public static String generatePermanentCookie() { + final String cookie = generateRandomCookie(); + PERSISTENT_COOKIES.add(cookie); + return cookie; + } + + private static String generateRandomCookie() { + final StringBuilder builder = new StringBuilder(); + final Random r = new Random(); + for (int i = 0; i < 40; i++) + builder.append(r.nextInt(40)); return builder.toString(); } } diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java new file mode 100644 index 0000000..a821072 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java @@ -0,0 +1,188 @@ +package gearth.services.extension_handler.extensions.implementations.network; + +import gearth.misc.HostInfo; +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.connection.HClient; +import gearth.services.extension_handler.extensions.ExtensionType; +import gearth.services.extension_handler.extensions.GEarthExtension; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing; +import gearth.services.packet_info.PacketInfoManager; +import io.netty.channel.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +public final class NetworkExtensionClient extends GEarthExtension { + + private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionClient.class); + + private final String title; + private final String author; + private final String version; + private final String description; + private final boolean fireEventButtonVisible; + private final boolean leaveButtonVisible; + private final boolean deleteButtonVisible; + private final boolean isInstalledExtension; + private final String fileName; + private final String cookie; + private final Channel channel; + + public NetworkExtensionClient(Incoming.ExtensionInfo msg, Channel channel) { + title = msg.getTitle(); + author = msg.getAuthor(); + version = msg.getVersion(); + description = msg.getDescription(); + fireEventButtonVisible = msg.isOnClickUsed(); + leaveButtonVisible = msg.isCanLeave(); + deleteButtonVisible = msg.isCanDelete(); + isInstalledExtension = msg.getFile() != null; + fileName = msg.getFile(); + cookie = msg.getCookie(); + this.channel = channel; + } + + public void handleIncomingMessage(Incoming msg) { + try { + if (msg instanceof Incoming.RequestFlags) + requestFlags(); + else if (msg instanceof Incoming.SendMessage) { + final Incoming.SendMessage message = ((Incoming.SendMessage) msg); + final HPacket packet = message.getPacket(); + if (!packet.isCorrupted()) + sendMessage(message.getDirection(), packet); + } else if (msg instanceof Incoming.ManipulatedPacket) { + sendManipulatedPacket(((Incoming.ManipulatedPacket) msg).gethMessage()); + } else if (msg instanceof Incoming.ExtensionConsoleLog) { + log(((Incoming.ExtensionConsoleLog) msg).getContents()); + } else if (msg instanceof Incoming.PacketToStringRequest) { + final HPacket hPacket = new HPacket(new byte[0]); + hPacket.constructFromString(((Incoming.PacketToStringRequest) msg).getString()); + packetToStringRequest(hPacket); + } else if (msg instanceof Incoming.StringToPacketRequest) { + stringToPacketRequest(((Incoming.StringToPacketRequest) msg).getString()); + } + } catch (Exception e){ + LOGGER.error("Failed to handle incoming message {} (channel={})", msg, channel, e); + } + } + + @Override + public void init(boolean isConnected, HostInfo hostInfo) { + channel.writeAndFlush(new Outgoing.Init(isConnected, hostInfo)); + } + + @Override + public void close() { + try { + channel.close(); + } catch (Exception e){ + LOGGER.error("Failed to close client (channel={})", channel, e); + } + } + + @Override + public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) { + channel.writeAndFlush(new Outgoing.ConnectionStart( + host, + port, + hotelVersion, + clientIdentifier, + clientType, + packetInfoManager + )); + } + + @Override + public void connectionEnd() { + channel.writeAndFlush(new Outgoing.ConnectionEnd()); + } + + @Override + public void doubleclick() { + channel.writeAndFlush(new Outgoing.OnDoubleClick()); + } + + @Override + public void provideFlags(String[] flags) { + channel.writeAndFlush(new Outgoing.FlagsCheck(Arrays.asList(flags))); + } + + @Override + public void updateHostInfo(HostInfo hostInfo) { + channel.writeAndFlush(new Outgoing.UpdateHostInfo(hostInfo)); + } + + @Override + public void packetIntercept(HMessage hMessage) { + final String messageAsString = hMessage.stringify(); + channel.writeAndFlush(new Outgoing.PacketIntercept(messageAsString)); + } + + @Override + public void packetToStringResponse(String string, String expression) { + channel.writeAndFlush(new Outgoing.PacketToStringResponse(string, expression)); + } + + @Override + public void stringToPacketResponse(HPacket packet) { + channel.writeAndFlush(new Outgoing.StringToPacketResponse(packet.stringify())); + } + + @Override + public ExtensionType extensionType() { + return ExtensionType.EXTERNAL; + } + + @Override + public String getAuthor() { + return author; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public String getFileName() { + return fileName; + } + + public String getCookie() { + return cookie; + } + + @Override + public boolean isFireButtonUsed() { + return fireEventButtonVisible; + } + + @Override + public boolean isDeleteButtonVisible() { + return deleteButtonVisible; + } + + @Override + public boolean isLeaveButtonVisible() { + return leaveButtonVisible; + } + + @Override + public boolean isInstalledExtension() { + return isInstalledExtension; + } +} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java new file mode 100644 index 0000000..b23b096 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java @@ -0,0 +1,295 @@ +package gearth.services.extension_handler.extensions.implementations.network; + +import gearth.misc.HostInfo; +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.connection.HClient; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming; +import gearth.services.packet_info.PacketInfoManager; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import static gearth.protocol.HMessage.Direction.TOCLIENT; +import static gearth.protocol.HMessage.Direction.TOSERVER; + +/** + * THE EXTENSION COMMUNICATION PRINCIPLES & PROTOCOL: + * + * You will be able to write extensions in ANY language you want, but we will only provide an interface + * for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth. + * + * Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread. + * Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up. + * That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done. + * TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send + * the edited packet when you're done. + * + * + * You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java. + * ----------------------------------------------------------------------------------------------------------------- + * + * (0. We recommend to use a cross-platform language for your extension) + * + * 1. An extension will run as a seperate process on your device and has to be called with the flag "-p ", + * where is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server. + * + * 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension. + * Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth). + * + * You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth + * (but you have to configure the port yourself, which will be displayed in the extension page) + * + * 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does): + * (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that) + * + * INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested) + * ----------------------------------------------------------------------------------------------------- + * | ID | TITLE | BODY & DESCRIPTION | + * ----------------------------------------------------------------------------------------------------- + * | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here) + * ----------------------------------------------------------------------------------------------------- + * | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), | + * | | | exact implementation is found in the Java abstract Extension class | + * ----------------------------------------------------------------------------------------------------- + * | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the | + * | | | manipulated HMessage (OUTGOING id: 2) | + * ----------------------------------------------------------------------------------------------------- + * | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) | + * ----------------------------------------------------------------------------------------------------- + * | 5 | CONNECTION START | just a note that a new connection has been made, | + * | | | you could check this yourself as well (listen to out:4000 packet) | + * | | | host/port, hotel version | + * ----------------------------------------------------------------------------------------------------- + * | 6 | CONNECTION END | Empty body, just a note that a connection has ended | + * ----------------------------------------------------------------------------------------------------- + * | 7 | INIT | Empty body, a connection with G-Earth has been set up | + * ----------------------------------------------------------------------------------------------------- + * | 99 | FREE FLOW | extension-specific body | + * ----------------------------------------------------------------------------------------------------- + * + * OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above) + * ----------------------------------------------------------------------------------------------------- + * | ID | TITLE | BODY & DESCRIPTION | + * ----------------------------------------------------------------------------------------------------- + * | 1 | EXTENSION-INFO* | Response for INFO-REQUEST | + * ----------------------------------------------------------------------------------------------------- + * | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT | + * ----------------------------------------------------------------------------------------------------- + * | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response | + * ----------------------------------------------------------------------------------------------------- + * | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage | + * | | | to the client/server | + * ----------------------------------------------------------------------------------------------------- + * | 99 | FREE FLOW | extension-specific body | + * ----------------------------------------------------------------------------------------------------- + * + * 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth + */ +public final class NetworkExtensionCodec { + + private final static Map, PacketStructure> outgoingPacketStructures = new HashMap<>(); + private final static Map incomingPacketStructures = new HashMap<>(); + + public static PacketStructure getIncomingStructure(int headerId) { + return incomingPacketStructures.get(headerId); + } + + public static PacketStructure getOutgoingStructure(T message) { + return outgoingPacketStructures.get(message.getClass()); + } + + static { + // incoming + register(Outgoing.InfoRequest.HEADER_ID, + Outgoing.InfoRequest.class, + (message, hPacket) -> { + }, + (hPacket -> new Outgoing.InfoRequest())); + register(Outgoing.ConnectionStart.HEADER_ID, + Outgoing.ConnectionStart.class, + (message, hPacket) -> { + hPacket.appendString(message.getHost()); + hPacket.appendInt(message.getConnectionPort()); + hPacket.appendString(message.getHotelVersion()); + hPacket.appendString(message.getClientIdentifier()); + hPacket.appendString(message.getClientType().name()); + message.getPacketInfoManager().appendToPacket(hPacket); + }, + (hPacket -> new Outgoing.ConnectionStart( + hPacket.readString(), + hPacket.readInteger(), + hPacket.readString(), + hPacket.readString(), + HClient.valueOf(hPacket.readString()), + PacketInfoManager.readFromPacket(hPacket) + ))); + register(Outgoing.ConnectionEnd.HEADER_ID, + Outgoing.ConnectionEnd.class, + (message, hPacket) -> { + }, + (hPacket -> new Outgoing.ConnectionEnd())); + register(Outgoing.FlagsCheck.HEADER_ID, + Outgoing.FlagsCheck.class, + (message, hPacket) -> { + hPacket.appendInt(message.getFlags().size()); + message.getFlags().forEach(hPacket::appendString); + }, + (hPacket -> { + final int size = hPacket.readInteger(); + final List flags = new ArrayList<>(); + for (int i = 0; i < size; i++) + flags.add(hPacket.readString()); + return new Outgoing.FlagsCheck(flags); + })); + register(Outgoing.Init.HEADER_ID, + Outgoing.Init.class, + (message, hPacket) -> { + hPacket.appendBoolean(message.isDelayInit()); + message.getHostInfo().appendToPacket(hPacket); + }, + (hPacket -> new Outgoing.Init(hPacket.readBoolean(), HostInfo.fromPacket(hPacket)))); + register(Outgoing.OnDoubleClick.HEADER_ID, + Outgoing.OnDoubleClick.class, + (message, hPacket) -> { + }, + (hPacket -> new Outgoing.OnDoubleClick())); + register(Outgoing.PacketIntercept.HEADER_ID, + Outgoing.PacketIntercept.class, + (message, hPacket) -> hPacket.appendLongString(message.getPacketString()), + (hPacket -> new Outgoing.PacketIntercept(hPacket.readLongString()))); + register(Outgoing.UpdateHostInfo.HEADER_ID, + Outgoing.UpdateHostInfo.class, + (message, hPacket) -> message.getHostInfo().appendToPacket(hPacket), + (hPacket -> new Outgoing.UpdateHostInfo(HostInfo.fromPacket(hPacket)))); + register(Outgoing.PacketToStringResponse.HEADER_ID, + Outgoing.PacketToStringResponse.class, + (message, hPacket) -> { + hPacket.appendLongString(message.getString()); + hPacket.appendLongString(message.getExpression(), StandardCharsets.UTF_8); + }, + (hPacket -> new Outgoing.PacketToStringResponse(hPacket.readLongString(), hPacket.readLongString(StandardCharsets.UTF_8))) + ); + register(Outgoing.StringToPacketResponse.HEADER_ID, + Outgoing.StringToPacketResponse.class, + (message, hPacket) -> hPacket.appendLongString(message.getString()), + (hPacket -> new Outgoing.StringToPacketResponse(hPacket.readLongString())) + ); + // outgoing + register(Incoming.ExtensionInfo.HEADER_ID, + Incoming.ExtensionInfo.class, + (message, hPacket) -> { + hPacket.appendString(message.getTitle()); + hPacket.appendString(message.getAuthor()); + hPacket.appendString(message.getVersion()); + hPacket.appendString(message.getDescription()); + hPacket.appendBoolean(message.isOnClickUsed()); + hPacket.appendBoolean(message.getFile() != null); + hPacket.appendString(Optional.ofNullable(message.getFile()).orElse("")); + hPacket.appendString(Optional.ofNullable(message.getCookie()).orElse("")); + hPacket.appendBoolean(message.isCanLeave()); + hPacket.appendBoolean(message.isCanDelete()); + }, + (hPacket -> { + final String title = hPacket.readString(); + final String author = hPacket.readString(); + final String version = hPacket.readString(); + final String description = hPacket.readString(); + final boolean isOnClickUsed = hPacket.readBoolean(); + final boolean hasFile = hPacket.readBoolean(); + String file = hPacket.readString(); + if (!hasFile) + file = null; + String cookie = hPacket.readString(); + if (cookie.isEmpty()) + cookie = null; + final boolean canLeave = hPacket.readBoolean(); + final boolean canDelete = hPacket.readBoolean(); + return new Incoming.ExtensionInfo(title, author, version, description, isOnClickUsed, file, cookie, canLeave, canDelete); + })); + register(Incoming.ManipulatedPacket.MANIPULATED_PACKET, + Incoming.ManipulatedPacket.class, + (message, hPacket) -> hPacket.appendLongString(message.gethMessage().stringify()), + (hPacket -> { + final String packetString = hPacket.readLongString(6); + final HMessage hMessage = new HMessage(packetString); + return new Incoming.ManipulatedPacket(hMessage); + })); + register(Incoming.SendMessage.HEADER_ID, + Incoming.SendMessage.class, + ((message, hPacket) -> { + hPacket.appendByte((byte) (message.getDirection() == TOCLIENT ? 0 : 1)); + hPacket.appendInt(message.getPacket().getBytesLength()); + hPacket.appendBytes(message.getPacket().toBytes()); + }), + (hPacket -> { + final byte side = hPacket.readByte(); + final int length = hPacket.readInteger(); + final byte[] data = hPacket.readBytes(length); + final HPacket packet = new HPacket(data); + return new Incoming.SendMessage(packet, side == 0 ? TOCLIENT : TOSERVER); + })); + register(Incoming.RequestFlags.HEADER_ID, + Incoming.RequestFlags.class, + (message, hPacket) -> { + }, + (hPacket -> new Incoming.RequestFlags())); + register(Incoming.ExtensionConsoleLog.HEADER_ID, + Incoming.ExtensionConsoleLog.class, + (message, hPacket) -> hPacket.appendString(message.getContents()), + (hPacket -> new Incoming.ExtensionConsoleLog(hPacket.readString()))); + register(Incoming.PacketToStringRequest.HEADER_ID, + Incoming.PacketToStringRequest.class, + (message, hPacket) -> hPacket.appendLongString(message.getString()), + (hPacket -> new Incoming.PacketToStringRequest(hPacket.readLongString()))); + register(Incoming.StringToPacketRequest.HEADER_ID, + Incoming.StringToPacketRequest.class, + (message, hPacket) -> hPacket.appendLongString(message.getString(), StandardCharsets.UTF_8), + (hPacket -> new Incoming.StringToPacketRequest(hPacket.readLongString(StandardCharsets.UTF_8)))); + } + + private static void register(final int headerId, Class tClass, BiConsumer writer, Function reader) { + final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), + writer, + reader); + if (tClass.getSuperclass() == Outgoing.class) + incomingPacketStructures.put(headerId, packetStructure); + else + outgoingPacketStructures.put(tClass, packetStructure); + } + + + static class PacketStructure { + + private final int headerId; + private final String name; + private final BiConsumer writer; + private final Function reader; + + public PacketStructure(int headerId, String name, BiConsumer writer, Function reader) { + this.headerId = headerId; + this.name = name; + this.writer = writer; + this.reader = reader; + } + + public int getHeaderId() { + return headerId; + } + + public String getName() { + return name; + } + + public BiConsumer getWriter() { + return writer; + } + + public Function getReader() { + return reader; + } + } +} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionInfo.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionInfo.java deleted file mode 100644 index 50282cf..0000000 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionInfo.java +++ /dev/null @@ -1,110 +0,0 @@ -package gearth.services.extension_handler.extensions.implementations.network; - -public class NetworkExtensionInfo { - - /** - * THE EXTENSION COMMUNCATION PRINCIPLES & PROTOCOL: - * - * You will be able to write extensions in ANY language you want, but we will only provide an interface - * for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth. - * - * Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread. - * Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up. - * That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done. - * TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send - * the editted packet when you're done. - * - * - * You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java. - * ----------------------------------------------------------------------------------------------------------------- - * - * (0. We recommend to use a cross-platform language for your extension) - * - * 1. An extension will run as a seperate process on your device and has to be called with the flag "-p ", - * where is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server. - * - * 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension. - * Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth). - * - * You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth - * (but you have to configure the port yourself, which will be displayed in the extension page) - * - * 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does): - * (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that) - * - * INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested) - * ----------------------------------------------------------------------------------------------------- - * | ID | TITLE | BODY & DESCRIPTION | - * ----------------------------------------------------------------------------------------------------- - * | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here) - * ----------------------------------------------------------------------------------------------------- - * | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), | - * | | | exact implementation is found in the Java abstract Extension class | - * ----------------------------------------------------------------------------------------------------- - * | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the | - * | | | manipulated HMessage (OUTGOING id: 2) | - * ----------------------------------------------------------------------------------------------------- - * | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) | - * ----------------------------------------------------------------------------------------------------- - * | 5 | CONNECTION START | just a note that a new connection has been made, | - * | | | you could check this yourself as well (listen to out:4000 packet) | - * | | | host/port, hotel version | - * ----------------------------------------------------------------------------------------------------- - * | 6 | CONNECTION END | Empty body, just a note that a connection has ended | - * ----------------------------------------------------------------------------------------------------- - * | 7 | INIT | Empty body, a connection with G-Earth has been set up | - * ----------------------------------------------------------------------------------------------------- - * | 99 | FREE FLOW | extension-specific body | - * ----------------------------------------------------------------------------------------------------- - * - * OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above) - * ----------------------------------------------------------------------------------------------------- - * | ID | TITLE | BODY & DESCRIPTION | - * ----------------------------------------------------------------------------------------------------- - * | 1 | EXTENSION-INFO* | Response for INFO-REQUEST | - * ----------------------------------------------------------------------------------------------------- - * | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT | - * ----------------------------------------------------------------------------------------------------- - * | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response | - * ----------------------------------------------------------------------------------------------------- - * | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage | - * | | | to the client/server | - * ----------------------------------------------------------------------------------------------------- - * | 99 | FREE FLOW | extension-specific body | - * ----------------------------------------------------------------------------------------------------- - * - * 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth - * - * - */ - - - public static class OUTGOING_MESSAGES_IDS { - public static final int ONDOUBLECLICK = 1; - public static final int INFOREQUEST = 2; - public static final int PACKETINTERCEPT = 3; - public static final int FLAGSCHECK = 4; - public static final int CONNECTIONSTART = 5; - public static final int CONNECTIONEND = 6; - public static final int INIT = 7; - - public static final int UPDATEHOSTINFO = 10; - - public static final int PACKETTOSTRING_RESPONSE = 20; - public static final int STRINGTOPACKET_RESPONSE = 21; - } - - - public static class INCOMING_MESSAGES_IDS { - public static final int EXTENSIONINFO = 1; - public static final int MANIPULATEDPACKET = 2; - public static final int REQUESTFLAGS = 3; - public static final int SENDMESSAGE = 4; - - public static final int PACKETTOSTRING_REQUEST = 20; - public static final int STRINGTOPACKET_REQUEST = 21; - - public static final int EXTENSIONCONSOLELOG = 98; - } - -} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java new file mode 100644 index 0000000..de97f0b --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java @@ -0,0 +1,323 @@ +package gearth.services.extension_handler.extensions.implementations.network; + +import gearth.misc.HostInfo; +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.connection.HClient; +import gearth.services.packet_info.PacketInfoManager; + +import java.util.List; + +public class NetworkExtensionMessage { + + public static class Incoming extends NetworkExtensionMessage { + + public static class ExtensionInfo extends Incoming { + + public static final int HEADER_ID = 1; + + private final String title; + private final String author; + private final String version; + private final String description; + private final boolean onClickUsed; + private final String file; + private final String cookie; + private final boolean canLeave; + private final boolean canDelete; + + public ExtensionInfo(String title, String author, String version, String description, boolean onClickUsed, String file, String cookie, boolean canLeave, boolean canDelete) { + this.title = title; + this.author = author; + this.version = version; + this.description = description; + this.onClickUsed = onClickUsed; + this.file = file; + this.cookie = cookie; + this.canLeave = canLeave; + this.canDelete = canDelete; + } + + public String getTitle() { + return title; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getDescription() { + return description; + } + + public boolean isOnClickUsed() { + return onClickUsed; + } + + public String getFile() { + return file; + } + + public String getCookie() { + return cookie; + } + + public boolean isCanLeave() { + return canLeave; + } + + public boolean isCanDelete() { + return canDelete; + } + } + + public static class RequestFlags extends Incoming { + public static final int HEADER_ID = 3; + } + + public static class SendMessage extends Incoming { + + public static final int HEADER_ID = 4; + + private final HPacket packet; + private final HMessage.Direction direction; + + public SendMessage(HPacket packet, HMessage.Direction direction) { + this.packet = packet; + this.direction = direction; + } + + public HPacket getPacket() { + return packet; + } + + public HMessage.Direction getDirection() { + return direction; + } + } + + public static class PacketToStringRequest extends Incoming { + + public static final int HEADER_ID = 20; + + private final String string; + + public PacketToStringRequest(String string) { + this.string = string; + } + + public String getString() { + return string; + } + } + + public static class StringToPacketRequest extends Incoming { + + public static final int HEADER_ID = 21; + + private final String string; + + public StringToPacketRequest(String string) { + this.string = string; + } + + public String getString() { + return string; + } + } + + public static class ExtensionConsoleLog extends Incoming { + + public static final int HEADER_ID = 98; + + private final String contents; + + public ExtensionConsoleLog(String contents) { + this.contents = contents; + } + + public String getContents() { + return contents; + } + } + + public static class ManipulatedPacket extends Incoming { + public static final int MANIPULATED_PACKET = 2; + private final HMessage hMessage; + + public ManipulatedPacket(HMessage hMessage) { + this.hMessage = hMessage; + } + + public HMessage gethMessage() { + return hMessage; + } + } + } + + public static class Outgoing extends NetworkExtensionMessage{ + + public static class OnDoubleClick extends Outgoing { + public static final int HEADER_ID = 1; + } + + public static class InfoRequest extends Outgoing { + public static final int HEADER_ID = 2; + } + + public static class PacketIntercept extends Outgoing { + + public static final int HEADER_ID = 3; + + private final String packetString; + + public PacketIntercept(String packetString) { + this.packetString = packetString; + } + + public String getPacketString() { + return packetString; + } + } + + public static class FlagsCheck extends Outgoing { + + public static final int HEADER_ID = 4; + + private final List flags; + + public FlagsCheck(List flags) { + this.flags = flags; + } + + public List getFlags() { + return flags; + } + } + + public static class ConnectionStart extends Outgoing { + + public static final int HEADER_ID = 5; + + private final String host; + private final int connectionPort; + private final String hotelVersion; + private final String clientIdentifier; + private final HClient clientType; + private final PacketInfoManager packetInfoManager; + + public ConnectionStart(String host, int connectionPort, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) { + this.host = host; + this.connectionPort = connectionPort; + this.hotelVersion = hotelVersion; + this.clientIdentifier = clientIdentifier; + this.clientType = clientType; + this.packetInfoManager = packetInfoManager; + } + + public String getHost() { + return host; + } + + public int getConnectionPort() { + return connectionPort; + } + + public String getHotelVersion() { + return hotelVersion; + } + + public String getClientIdentifier() { + return clientIdentifier; + } + + public HClient getClientType() { + return clientType; + } + + public PacketInfoManager getPacketInfoManager() { + return packetInfoManager; + } + } + + public static class ConnectionEnd extends Outgoing { + public static final int HEADER_ID = 6; + } + + public static class Init extends Outgoing { + + public static final int HEADER_ID = 7; + + private final boolean delayInit; + private final HostInfo hostInfo; + + public Init(boolean delayInit, HostInfo hostInfo) { + this.delayInit = delayInit; + this.hostInfo = hostInfo; + } + + public boolean isDelayInit() { + return delayInit; + } + + public HostInfo getHostInfo() { + return hostInfo; + } + } + + public static class UpdateHostInfo extends Outgoing { + + public static final int HEADER_ID = 10; + + private final HostInfo hostInfo; + + public UpdateHostInfo(HostInfo hostInfo) { + this.hostInfo = hostInfo; + } + + public HostInfo getHostInfo() { + return hostInfo; + } + } + + public static class PacketToStringResponse extends Outgoing { + + public static final int HEADER_ID = 20; + + private final String string; + private final String expression; + + public PacketToStringResponse(String string, String expression) { + this.string = string; + this.expression = expression; + } + + public String getString() { + return string; + } + + public String getExpression() { + return expression; + } + } + + public static class StringToPacketResponse extends Outgoing { + + public static final int HEADER_ID = 21; + + private final String string; + + public StringToPacketResponse(String string) { + this.string = string; + } + + public String getString() { + return string; + } + } + } +} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java new file mode 100644 index 0000000..671af59 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java @@ -0,0 +1,257 @@ +package gearth.services.extension_handler.extensions.implementations.network; + +import gearth.protocol.HPacket; +import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer; +import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory; +import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionCodec.PacketStructure; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.util.List; +import java.util.function.BiConsumer; + +import static gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.*; + +/** + * Represents an {@link ExtensionProducer} that implements a server to which + * remotely-ran extensions can connect. + * + * @see ExtensionProducerFactory#getAll() for instance creation. + * + * @author Dorving, Jonas + */ +public final class NetworkExtensionServer implements ExtensionProducer { + + private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionServer.class); + + /** + * Initial port server tries to listen at, if {@link ServerSocket} creation fails, + * it tries next port. + */ + private static final int PORT_ONSET = 9092; + + /** + * The port at which the server is listening. + */ + private int port = -1; + + @Override + public void startProducing(ExtensionProducerObserver observer) { + + final ServerBootstrap bootstrap = new ServerBootstrap() + .option(ChannelOption.TCP_NODELAY, true) + .childHandler(new Initializer(observer)) + .group(new NioEventLoopGroup()); + + port = PORT_ONSET; + while (!available(port)) + port++; + LOGGER.debug("Found open port {}, attempting to bind...", port); + + final ChannelFuture channelFuture = bootstrap.bind(port).awaitUninterruptibly(); + if (!channelFuture.isSuccess()) + LOGGER.error("Failed to bind to port {}", port); + else + LOGGER.debug("Successfully bound to port {}", port); + } + + /** + * The port that the server is bound to. + * + * @return the port number to which the server is bound or -1 if the socket is not bound (yet). + */ + public int getPort() { + return port; + } + + /** + * Checks to see if a specific port is available. + * + * Taken from http://svn.apache.org/viewvc/camel/trunk/components/camel-test/src/main/java/org/apache/camel/test/AvailablePortFinder.java?view=markup#l130 + * + * @param port the port to check for availability + */ + private static boolean available(int port) { + ServerSocket ss = null; + DatagramSocket ds = null; + try { + ss = new ServerSocket(port); + ss.setReuseAddress(true); + ds = new DatagramSocket(port); + ds.setReuseAddress(true); + return true; + } catch (IOException ignored) { + } finally { + if (ds != null) { + ds.close(); + } + + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + /* should not be thrown */ + } + } + } + + return false; + } + + static class Initializer extends ChannelInitializer { + + private final ExtensionProducerObserver observer; + + public Initializer(ExtensionProducerObserver observer) { + this.observer = observer; + } + + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline() + .addLast("decoder", new Decoder()) + .addLast("encoder", new Encoder()) + .addLast("handler", new Handler(observer)); + ch.writeAndFlush(new Outgoing.InfoRequest()); + } + } + + static class Decoder extends ByteToMessageDecoder { + + private final static int HEADER_LENGTH = Integer.BYTES; + private final static Logger LOGGER = LoggerFactory.getLogger(Decoder.class); + + private volatile Stage stage = Stage.LENGTH; + private volatile int payloadLength = 0; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + switch (stage) { + case LENGTH: + payloadLength = in.readInt(); + stage = Stage.PAYLOAD; + break; + case PAYLOAD: + + if (in.readableBytes() < payloadLength) + return; + + try { + + final byte[] data = new byte[HEADER_LENGTH + payloadLength]; + in.readBytes(data, HEADER_LENGTH, payloadLength); + + final HPacket hPacket = new HPacket(data); + hPacket.fixLength(); + + final PacketStructure incomingPacketStructure = NetworkExtensionCodec.getIncomingStructure(hPacket.headerId()); + if (incomingPacketStructure != null) { + final NetworkExtensionMessage message = incomingPacketStructure.getReader().apply(hPacket); + out.add(message); + } else { + LOGGER.error("Did not find decoder for packet {}", hPacket); + } + } catch (Exception e) { + LOGGER.error("Failed to decode message", e); + } finally { + payloadLength = 0; + stage = Stage.LENGTH; + } + break; + } + } + + enum Stage { + LENGTH, + PAYLOAD + } + } + + static class Encoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, Outgoing msg, ByteBuf out) { + final PacketStructure structure = NetworkExtensionCodec.getOutgoingStructure(msg); + if (structure == null){ + LOGGER.error("Structure for Outgoing message not defined (msg={})", msg); + return; + } + try { + final HPacket hPacket = new HPacket(structure.getHeaderId()); + final BiConsumer writer = (BiConsumer) structure.getWriter(); + writer.accept(msg, hPacket); + out.writeBytes(hPacket.toBytes()); + } catch (Exception e) { + LOGGER.error("Failed to encode Outgoing message as a HPacket (msg={})", msg, e); + } + } + } + + static class Handler extends ChannelInboundHandlerAdapter { + + private static final AttributeKey CLIENT = AttributeKey.valueOf("client"); + + private final ExtensionProducerObserver observer; + + public Handler(ExtensionProducerObserver observer) { + this.observer = observer; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + LOGGER.trace("Channel registered (channel={})", ctx.channel()); + super.handlerAdded(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + LOGGER.trace("Channel unregistered (channel={})", ctx.channel()); + super.channelUnregistered(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + final Channel channel = ctx.channel(); + final Attribute clientAttribute = ctx.attr(CLIENT); + NetworkExtensionClient client = clientAttribute.get(); + if (msg instanceof Incoming.ExtensionInfo) { + if (client != null) + LOGGER.warn("Overriding pre-existing CLIENT for channel (client={}, channel={})", client, channel); + client = new NetworkExtensionClient((Incoming.ExtensionInfo) msg, channel); + if (NetworkExtensionAuthenticator.evaluate(client)) { + LOGGER.info("Successfully authenticated client {}", client); + clientAttribute.set(client); + observer.onExtensionProduced(client); + } else { + LOGGER.warn("Failed to authenticate client {}, closing connection", client); + client.close(); + } + } + else if (client == null) + LOGGER.error("Client was null, could not handle incoming message {}, expected {} first", msg, Incoming.ExtensionInfo.class); + else if (msg instanceof Incoming) + client.handleIncomingMessage((Incoming) msg); + else + LOGGER.error("Read invalid message type (message={})", msg); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + LOGGER.error("Channel exception caught (channel={}), closing channel", ctx.channel(), cause); + ctx.channel().close(); + } + } +} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionsProducer.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionsProducer.java deleted file mode 100644 index 1500e12..0000000 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionsProducer.java +++ /dev/null @@ -1,167 +0,0 @@ -package gearth.services.extension_handler.extensions.implementations.network; - -import gearth.protocol.HPacket; -import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer; -import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory; -import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver; -import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator; - -import java.io.DataInputStream; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * Represents an {@link ExtensionProducer} that implements a server to which - * remotely-ran extensions can connect. - * - * @see ExtensionProducerFactory#getAll() for instance creation. - * - * Created by Jonas on 21/06/18. - */ -public final class NetworkExtensionsProducer implements ExtensionProducer { - - /** - * Initial port server tries to listen at, if {@link ServerSocket} creation fails, - * it tries next port. - */ - private static final int PORT_ONSET = 9092; - - /** - * Represents the number of bytes per boolean encoded in an incoming packet. - */ - private static final int BOOLEAN_SIZE = 1; - - /** - * Represents the maximum number of bytes per string encoded in an incoming packet. - */ - private static final int MAX_STRING_SIZE = Character.BYTES * 4_000; - - /** - * Length is encoded as an {@link Integer} and header id as an {@link Short}. - */ - private static final int PACKET_HEADER_SIZE = Integer.BYTES + Short.BYTES; - - /** - * Represents the maximum number of bytes in the body of an incoming packet. - *

- * Used as a form of validation for packets, prevents other Apps that connect - * with the server from sending unexpected data and inexplicably causing huge byte array allocations. - *

- * Since the server only accepts {@link NetworkExtensionInfo.INCOMING_MESSAGES_IDS#EXTENSIONINFO} packets, - * this value is calculated based on that packet. - */ - private static final int MAX_PACKET_BODY_SIZE = (MAX_STRING_SIZE * 6) + (BOOLEAN_SIZE * 4); - - /** - * The port at which the {@link #serverSocket} is listening for incoming connections. - */ - public static int extensionPort = -1; - - private ServerSocket serverSocket; - - @Override - public void startProducing(ExtensionProducerObserver observer) { - - /* - Initialise the serverSocket at the argued port. - */ - int port = PORT_ONSET; - while (!createServer(port)) - ++port; - - /* - Start connection listener thread. - */ - new Thread(() -> { - - try { - - while (!serverSocket.isClosed()) { - - // accept a new connection - final Socket extensionSocket = serverSocket.accept(); - extensionSocket.setTcpNoDelay(true); - - /* - Start client session handler thread. - */ - new Thread(() -> { - - try { - - // write INFOREQUEST packet to client - synchronized (extensionSocket) { - extensionSocket.getOutputStream().write((new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes()); - } - - final DataInputStream dIn = new DataInputStream(extensionSocket.getInputStream()); - - // listen to incoming data from client - while (!extensionSocket.isClosed()) { - - final int bodyLength = dIn.readInt() - Short.BYTES; - final short headerId = dIn.readShort(); - - if (headerId == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO) { - - if (bodyLength > MAX_PACKET_BODY_SIZE) { - System.err.printf("Incoming packet(h=%d, l=%d) exceeds max packet body size %d.\n", headerId, bodyLength, MAX_PACKET_BODY_SIZE); - break; - } - - final HPacket packet = readPacket(dIn, bodyLength, headerId); - - final NetworkExtension gEarthExtension = new NetworkExtension(packet, extensionSocket); - - if (Authenticator.evaluate(gEarthExtension)) - observer.onExtensionProduced(gEarthExtension); - else - gEarthExtension.close(); - - break; - } - } - } catch (IOException ignored) { - } - }).start(); - } - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - } - - private boolean createServer(int port) { - try { - serverSocket = new ServerSocket(port); - extensionPort = port; - return true; - } catch (IOException e) { - return false; - } - } - - private HPacket readPacket(DataInputStream dIn, int amountToRead, short id) throws IOException { - final byte[] headerAndBody = new byte[amountToRead + PACKET_HEADER_SIZE]; - - int amountRead = 0; - while (amountRead < amountToRead) - amountRead += dIn.read(headerAndBody, amountRead + PACKET_HEADER_SIZE, Math.min(dIn.available(), amountToRead - amountRead)); - - final HPacket packet = new HPacket(headerAndBody); - packet.fixLength(); - packet.replaceShort(4, id); // add header id - - return packet; - } - - /** - * Retrieves the {@link ServerSocket#getLocalPort()} of {@link #serverSocket}. - * - * @return the port number to which {@link #serverSocket} is listening or -1 if the socket is not bound yet. - */ - public int getPort() { - return serverSocket.getLocalPort(); - } -} \ No newline at end of file diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExecutionInfo.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExecutionInfo.java index 6732de1..c2c7a2d 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExecutionInfo.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExecutionInfo.java @@ -8,39 +8,39 @@ import java.util.Map; /** * Created by Jonas on 22/09/18. */ -public class ExecutionInfo { +public final class ExecutionInfo { - private static Map extensionTypeToExecutionCommand; - public final static List ALLOWEDEXTENSIONTYPES; - public final static String EXTENSIONSDIRECTORY = "Extensions"; + private static final Map EXTENSION_TYPE_TO_EXECUTION_COMMAND; + + public final static List ALLOWED_EXTENSION_TYPES; + public final static String EXTENSIONS_DIRECTORY = "Extensions"; static { - extensionTypeToExecutionCommand = new HashMap<>(); - extensionTypeToExecutionCommand.put("*.jar", new String[]{"java", "-jar", "{path}"}); - extensionTypeToExecutionCommand.put("*.py", new String[]{"python", "{path}"}); - extensionTypeToExecutionCommand.put("*.py3", new String[]{"python3", "{path}"}); - extensionTypeToExecutionCommand.put("*.sh", new String[]{"{path}"}); - extensionTypeToExecutionCommand.put("*.exe", new String[]{"{path}"}); - extensionTypeToExecutionCommand.put("*.js", new String[]{"node", "{path}"}); - String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"}; - for(String type : extensionTypeToExecutionCommand.keySet()) { - String[] commandShort = extensionTypeToExecutionCommand.get(type); - String[] combined = new String[extraArgs.length + commandShort.length]; + EXTENSION_TYPE_TO_EXECUTION_COMMAND = new HashMap<>(); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.jar", new String[]{"java", "-jar", "{path}"}); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py", new String[]{"python", "{path}"}); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py3", new String[]{"python3", "{path}"}); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.sh", new String[]{"{path}"}); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.exe", new String[]{"{path}"}); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.js", new String[]{"node", "{path}"}); + + final String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"}; + + for(String type : EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet()) { + + final String[] commandShort = EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type); + final String[] combined = new String[extraArgs.length + commandShort.length]; System.arraycopy(commandShort, 0, combined, 0, commandShort.length); System.arraycopy(extraArgs, 0, combined, commandShort.length, extraArgs.length); - extensionTypeToExecutionCommand.put( - type, - combined - ); + EXTENSION_TYPE_TO_EXECUTION_COMMAND.put(type, combined); } - ALLOWEDEXTENSIONTYPES = new ArrayList<>(extensionTypeToExecutionCommand.keySet()); + ALLOWED_EXTENSION_TYPES = new ArrayList<>(EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet()); } public static String[] getExecutionCommand(String type) { - return extensionTypeToExecutionCommand.get(type); + return EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type); } - } diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExtensionRunnerFactory.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExtensionRunnerFactory.java index fd26dcb..2b569c2 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExtensionRunnerFactory.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/ExtensionRunnerFactory.java @@ -3,9 +3,9 @@ package gearth.services.extension_handler.extensions.implementations.network.exe /** * Created by Jonas on 22/09/18. */ -public class ExtensionRunnerFactory { +public final class ExtensionRunnerFactory { - private static ExtensionRunner runner = new NormalExtensionRunner(); + private static final ExtensionRunner runner = new NormalExtensionRunner(); public static ExtensionRunner get() { return runner; diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java index ba8bc29..15b5228 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java @@ -1,14 +1,17 @@ package gearth.services.extension_handler.extensions.implementations.network.executer; import gearth.GEarth; -import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator; import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.*; import java.util.Arrays; import java.util.Random; @@ -16,87 +19,85 @@ import java.util.Random; /** * Created by Jonas on 22/09/18. */ -public class NormalExtensionRunner implements ExtensionRunner { +public final class NormalExtensionRunner implements ExtensionRunner { - public static final String JARPATH; + private final static Logger LOGGER = LoggerFactory.getLogger(NormalExtensionRunner.class); + + public static final String JAR_PATH; static { + final URL url = getLocation(); String value; try { - value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); + value = new File(url.toURI()).getParent(); } catch (URISyntaxException e) { - value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent(); - e.printStackTrace(); + value = new File(url.getPath()).getParent(); + LOGGER.warn("Failed to load JAR_PATH from url {} as URI, using Path instead", url, e); } - JARPATH = value; + JAR_PATH = value; + LOGGER.debug("Set JAR_PATH as {}", JAR_PATH); } @Override public void runAllExtensions(int port) { - if (dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)){ - File folder = - new File(JARPATH + - FileSystems.getDefault().getSeparator()+ - ExecutionInfo.EXTENSIONSDIRECTORY); - File[] childs = folder.listFiles(); - for (File file : childs) { - tryRunExtension(file.getPath(), port); + if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)){ + + final File extensionsDirectory = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toFile(); + final File[] extensionFiles = extensionsDirectory.listFiles(); + + if (extensionFiles == null) { + LOGGER.error("Provided extensionsDirectory does not exist (extensionsDirectory={})", extensionsDirectory); + return; } - } + + for (File file : extensionFiles) + tryRunExtension(file.getPath(), port); + } else + LOGGER.warn("Did not run extensions because extensions directory does not exist at {}", ExecutionInfo.EXTENSIONS_DIRECTORY); } @Override - public void installAndRunExtension(String path, int port) { - if (!dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)) { - createDirectory(ExecutionInfo.EXTENSIONSDIRECTORY); - } + public void installAndRunExtension(String stringPath, int port) { + if (!dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)) + tryCreateDirectory(ExecutionInfo.EXTENSIONS_DIRECTORY); - String name = Paths.get(path).getFileName().toString(); - String[] split = name.split("\\."); - String ext = "*." + split[split.length - 1]; + final Path path = Paths.get(stringPath); + final String name = path.getFileName().toString(); + final String[] split = name.split("\\."); + final String ext = "*." + split[split.length - 1]; - String realname = String.join(".",Arrays.copyOf(split, split.length-1)); - String newname = realname + "-" + getRandomString() + ext.substring(1); + final String realName = String.join(".",Arrays.copyOf(split, split.length-1)); + final String newName = realName + "-" + getRandomString() + ext.substring(1); - Path originalPath = Paths.get(path); - Path newPath = Paths.get( - JARPATH, - ExecutionInfo.EXTENSIONSDIRECTORY, - newname - ); + final Path newPath = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, newName); try { - Files.copy( - originalPath, - newPath - ); -// addExecPermission(newPath.toString()); + Files.copy(path, newPath); + tryRunExtension(newPath.toString(), port); } catch (IOException e) { e.printStackTrace(); } - - } public void tryRunExtension(String path, int port) { try { + if (new File(path).isDirectory()) { - // this extension is installed from the extension store and requires - // different behavior + // this extension is installed from the extension store and requires different behavior StoreExtensionTools.executeExtension(path, port); return; } - String filename = Paths.get(path).getFileName().toString(); - - String[] execCommand = ExecutionInfo.getExecutionCommand(getFileExtension(path)); - execCommand = Arrays.copyOf(execCommand, execCommand.length); - String cookie = Authenticator.generateCookieForExtension(filename); + final String filename = Paths.get(path).getFileName().toString(); + final String[] execCommand = ExecutionInfo + .getExecutionCommand(getFileExtension(path)) + .clone(); + final String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(filename); for (int i = 0; i < execCommand.length; i++) { execCommand[i] = execCommand[i] .replace("{path}", path) @@ -104,104 +105,91 @@ public class NormalExtensionRunner implements ExtensionRunner { .replace("{filename}", filename) .replace("{cookie}", cookie); } - ProcessBuilder pb = new ProcessBuilder(execCommand); -// Process proc = Runtime.getRuntime().exec(execCommand); - Process proc = pb.start(); - maybeLogExtension(path, proc); + final ProcessBuilder processBuilder = new ProcessBuilder(execCommand); + final Process process = processBuilder.start(); + + maybeLogExtension(path, process); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Failed to run extension at path {} using port {}", path, port, e); } } - public static void maybeLogExtension(String path, Process proc) { + public static void maybeLogExtension(String path, Process process) { if (GEarth.hasFlag(ExtensionRunner.SHOW_EXTENSIONS_LOG)) { - String sep = "" + System.lineSeparator(); - synchronized (System.out) { - System.out.println(path + sep + "Launching" + sep + "----------" + sep); - } - BufferedReader stdInput = new BufferedReader(new - InputStreamReader(proc.getInputStream())); + final String separator = "" + System.lineSeparator(); + LOGGER.info(path + separator + "Launching" + separator + "----------" + separator); + + final BufferedReader processInputReader = new BufferedReader(new InputStreamReader(process.getInputStream())); new Thread(() -> { try { String line; - while((line = stdInput.readLine()) != null) { - synchronized (System.out) { - System.out.println(path + sep + "Output" + sep + line + sep + "----------" + sep); - } - } + while((line = processInputReader.readLine()) != null) + LOGGER.info(path + separator + "Output" + separator + line + separator + "----------" + separator); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Failed to read input line from process {}", process, e); } }).start(); - BufferedReader stdError = new BufferedReader(new - InputStreamReader(proc.getErrorStream())); - + final BufferedReader processErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); new Thread(() -> { try { String line; - while((line = stdError.readLine()) != null) { - synchronized (System.out) { - System.out.println(path + sep + "Error" + sep + line + sep + "----------" + sep); - } + while((line = processErrorReader.readLine()) != null) { + LOGGER.error(path + separator + "Error" + separator + line + separator + "----------" + separator); } } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Failed to read error line from process {}", process, e); } }).start(); - } } @Override public void uninstallExtension(String filename) { try { - Path path = Paths.get(JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, filename); + final Path path = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, filename); if (new File(path.toString()).isDirectory()) { // is installed through extension store StoreExtensionTools.removeExtension(path.toString()); - } - else { + } else Files.delete(path); - } - } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Failed to uninstall extension at {}", filename, e); } } -// private void addExecPermission(String path) { -// //not needed at first sight -// } + private static void tryCreateDirectory(String path) { + if (!dirExists(path)) { + try { + Files.createDirectories(Paths.get(JAR_PATH, path)); + } catch (IOException e) { + LOGGER.error("Failed to create directory at {}", path, e); + } + } + } - private String getFileExtension(String path) { - String name = Paths.get(path).getFileName().toString(); - String[] split = name.split("\\."); + private static boolean dirExists(String dir) { + return Files.isDirectory(Paths.get(JAR_PATH, dir)); + } + + private static URL getLocation() { + return GEarth.class.getProtectionDomain().getCodeSource().getLocation(); + } + + private static String getFileExtension(String path) { + final String name = Paths.get(path).getFileName().toString(); + final String[] split = name.split("\\."); return "*." + split[split.length - 1]; } - private boolean dirExists(String dir) { - return Files.isDirectory(Paths.get(JARPATH, dir)); - } - private void createDirectory(String dir) { - if (!dirExists(dir)) { - try { - Files.createDirectories(Paths.get(JARPATH, dir)); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - private String getRandomString() { - StringBuilder builder = new StringBuilder(); - Random r = new Random(); - for (int i = 0; i < 12; i++) { - builder.append(r.nextInt(10)); - } - + private static String getRandomString() { + final StringBuilder builder = new StringBuilder(); + final Random random = new Random(); + for (int i = 0; i < 12; i++) + builder.append(random.nextInt(10)); return builder.toString(); } } diff --git a/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/application/entities/extensiondetails/StoreExtensionDetailsOverview.java b/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/application/entities/extensiondetails/StoreExtensionDetailsOverview.java index 7c06d56..0af0e46 100644 --- a/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/application/entities/extensiondetails/StoreExtensionDetailsOverview.java +++ b/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/application/entities/extensiondetails/StoreExtensionDetailsOverview.java @@ -1,7 +1,6 @@ package gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails; -import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer; -import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner; +import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory; import gearth.services.internal_extensions.extensionstore.GExtensionStore; import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController; import gearth.services.internal_extensions.extensionstore.application.WebUtils; @@ -14,13 +13,11 @@ import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTo import gearth.ui.titlebar.TitleBarController; import javafx.application.Platform; import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; import org.apache.maven.artifact.versioning.ComparableVersion; import org.w3c.dom.Element; import java.io.IOException; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -135,7 +132,7 @@ public class StoreExtensionDetailsOverview extends HOverview { @Override public void success(String installationFolder) { Platform.runLater(() -> successPopup(modeString)); - StoreExtensionTools.executeExtension(installationFolder, NetworkExtensionsProducer.extensionPort); + StoreExtensionTools.executeExtension(installationFolder, ExtensionProducerFactory.getExtensionServer().getPort()); } @Override diff --git a/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/tools/StoreExtensionTools.java b/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/tools/StoreExtensionTools.java index eb70240..f34e18b 100644 --- a/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/tools/StoreExtensionTools.java +++ b/G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/tools/StoreExtensionTools.java @@ -2,7 +2,7 @@ package gearth.services.internal_extensions.extensionstore.tools; import gearth.GEarth; import gearth.misc.OSValidator; -import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator; import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo; import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner; import gearth.services.internal_extensions.extensionstore.repository.StoreFetch; @@ -33,7 +33,7 @@ public class StoreExtensionTools { } - public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY).toString(); + public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toString(); public static void executeExtension(String extensionPath, int port) { @@ -41,7 +41,7 @@ public class StoreExtensionTools { String installedExtensionId = Paths.get(extensionPath).getFileName().toString(); String commandPath = Paths.get(extensionPath, "command.txt").toString(); - String cookie = Authenticator.generateCookieForExtension(installedExtensionId); + String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(installedExtensionId); List command = new JSONArray(FileUtils.readFileToString(new File(commandPath), "UTF-8")) .toList().stream().map(o -> (String)o).map(s -> s .replace("{port}", port+"") diff --git a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionItemContainer.java b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionItemContainer.java index b1623fe..cd115f4 100644 --- a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionItemContainer.java +++ b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionItemContainer.java @@ -95,7 +95,7 @@ public class ExtensionItemContainer extends GridPane { reloadButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { reloadButton.setVisible(false); ExtensionRunner runner = ExtensionRunnerFactory.get(); - runner.tryRunExtension(Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, item.getFileName()).toString(), port); + runner.tryRunExtension(Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, item.getFileName()).toString(), port); }); DeleteButton deleteButton = new DeleteButton(); diff --git a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java index d51f7fd..36eef13 100644 --- a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java +++ b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java @@ -2,8 +2,8 @@ package gearth.ui.subforms.extensions; import gearth.services.extension_handler.ExtensionHandler; import gearth.services.extension_handler.extensions.ExtensionListener; -import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer; -import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator; import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo; import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunner; import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory; @@ -36,7 +36,7 @@ public class ExtensionsController extends SubForm { private ExtensionRunner extensionRunner = null; private ExtensionHandler extensionHandler; - private NetworkExtensionsProducer networkExtensionsProducer; // needed for port + private NetworkExtensionServer networkExtensionsProducer; // needed for port private ExtensionLogger extensionLogger = null; @@ -56,8 +56,8 @@ public class ExtensionsController extends SubForm { //noinspection OptionalGetWithoutIsPresent networkExtensionsProducer - = (NetworkExtensionsProducer) extensionHandler.getExtensionProducers().stream() - .filter(producer1 -> producer1 instanceof NetworkExtensionsProducer) + = (NetworkExtensionServer) extensionHandler.getExtensionProducers().stream() + .filter(producer1 -> producer1 instanceof NetworkExtensionServer) .findFirst().get(); @@ -82,7 +82,7 @@ public class ExtensionsController extends SubForm { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Install extension"); fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("G-Earth extensions", ExecutionInfo.ALLOWEDEXTENSIONTYPES)); + new FileChooser.ExtensionFilter("G-Earth extensions", ExecutionInfo.ALLOWED_EXTENSION_TYPES)); File selectedFile = fileChooser.showOpenDialog(parentController.getStage()); if (selectedFile != null) { extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort()); @@ -118,7 +118,7 @@ public class ExtensionsController extends SubForm { GPythonShell shell = new GPythonShell( "Scripting shell " + gpytonShellCounter++, networkExtensionsProducer.getPort(), - Authenticator.generatePermanentCookie() + NetworkExtensionAuthenticator.generatePermanentCookie() ); shell.launch((b) -> { pythonShellLaunching = false; From 5f08eb599b538de1173556c788a7da5340b71298 Mon Sep 17 00:00:00 2001 From: dorving Date: Mon, 11 Jul 2022 05:23:03 +0200 Subject: [PATCH 2/5] Added documentation to NetworkExtensionMessage.java --- .../network/NetworkExtensionCodec.java | 4 +- .../network/NetworkExtensionMessage.java | 96 ++++++++++++++++++- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java index b23b096..8be3150 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java @@ -252,9 +252,7 @@ public final class NetworkExtensionCodec { } private static void register(final int headerId, Class tClass, BiConsumer writer, Function reader) { - final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), - writer, - reader); + final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), writer, reader); if (tClass.getSuperclass() == Outgoing.class) incomingPacketStructures.put(headerId, packetStructure); else diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java index de97f0b..460aca0 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionMessage.java @@ -8,10 +8,30 @@ import gearth.services.packet_info.PacketInfoManager; import java.util.List; +/** + * Represents a message send or received by G-Earth and a remote extension. + * + * @see NetworkExtensionCodec the encoding/decoding structures + * @see Incoming messages coming from the remote extension to G-Earth + * @see Outgoing messages coming from G-Earth to the remote extension + * + * @author Dorving, Jonas + */ public class NetworkExtensionMessage { + /** + * Represents {@link NetworkExtensionMessage messages} coming from the remote extension to G-Earth. + */ public static class Incoming extends NetworkExtensionMessage { + /** + * This contains info about the remote extension trying to connect. + * + * Once this {@link NetworkExtensionMessage message} is received, + * a new {@link NetworkExtensionClient} is created to handle the communication. + * + * @see Outgoing.InfoRequest the request. + */ public static class ExtensionInfo extends Incoming { public static final int HEADER_ID = 1; @@ -75,10 +95,20 @@ public class NetworkExtensionMessage { } } + /** + * Remote extension request G-Earth's flags. + * + * @see Outgoing.FlagsCheck the response. + */ public static class RequestFlags extends Incoming { public static final int HEADER_ID = 3; } + /** + * Received a {@link HPacket} from the remote connection + * and forward it either to the game {@link HMessage.Direction#TOSERVER server} + * or game {@link HMessage.Direction#TOCLIENT client}. + */ public static class SendMessage extends Incoming { public static final int HEADER_ID = 4; @@ -100,6 +130,11 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + * + * @see Outgoing.PacketToStringResponse the response. + */ public static class PacketToStringRequest extends Incoming { public static final int HEADER_ID = 20; @@ -115,6 +150,11 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + * + * @see Outgoing.StringToPacketResponse the response. + */ public static class StringToPacketRequest extends Incoming { public static final int HEADER_ID = 21; @@ -130,6 +170,9 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + */ public static class ExtensionConsoleLog extends Incoming { public static final int HEADER_ID = 98; @@ -145,6 +188,11 @@ public class NetworkExtensionMessage { } } + /** + * Represents a packet modified by the remote extension. + * + * @see Outgoing.PacketIntercept the ougoing message containing the original packet. + */ public static class ManipulatedPacket extends Incoming { public static final int MANIPULATED_PACKET = 2; private final HMessage hMessage; @@ -158,17 +206,34 @@ public class NetworkExtensionMessage { } } } - + /** + * Represents {@link NetworkExtensionMessage messages} coming from G-Earth to the remote extension. + */ public static class Outgoing extends NetworkExtensionMessage{ + /** + * The extension has been double-clicked from within G-Earth. + */ public static class OnDoubleClick extends Outgoing { public static final int HEADER_ID = 1; } + /** + * Request for remote extension to send {@link Incoming.ExtensionInfo}. + * + * This is the very first message send after a connection is established. + * + * @see Incoming.ExtensionInfo the response. + */ public static class InfoRequest extends Outgoing { public static final int HEADER_ID = 2; } + /** + * Forwards a packet intercepted by G-Earth to the remote extension. + * + * @see Incoming.ManipulatedPacket the response. + */ public static class PacketIntercept extends Outgoing { public static final int HEADER_ID = 3; @@ -184,6 +249,11 @@ public class NetworkExtensionMessage { } } + /** + * Contains program arguments of G-Earth. + * + * @see Incoming.RequestFlags the request. + */ public static class FlagsCheck extends Outgoing { public static final int HEADER_ID = 4; @@ -199,6 +269,11 @@ public class NetworkExtensionMessage { } } + /** + * Notifies remote extension that a connection to a hotel has been established. + * + * @apiNote could check this yourself as well (listen to out:4000 packet) + */ public static class ConnectionStart extends Outgoing { public static final int HEADER_ID = 5; @@ -244,10 +319,16 @@ public class NetworkExtensionMessage { } } + /** + * Notifies a remote extension that the connection to the hotel has been closed. + */ public static class ConnectionEnd extends Outgoing { public static final int HEADER_ID = 6; } + /** + * Notifies a remote extension that it has been accepted by G-Earth. + */ public static class Init extends Outgoing { public static final int HEADER_ID = 7; @@ -269,6 +350,9 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + */ public static class UpdateHostInfo extends Outgoing { public static final int HEADER_ID = 10; @@ -284,6 +368,11 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + * + * @see Incoming.PacketToStringRequest the request. + */ public static class PacketToStringResponse extends Outgoing { public static final int HEADER_ID = 20; @@ -305,6 +394,11 @@ public class NetworkExtensionMessage { } } + /** + * TODO: add documentation. + * + * @see Incoming.StringToPacketRequest the request. + */ public static class StringToPacketResponse extends Outgoing { public static final int HEADER_ID = 21; From 047d948086144bb85eb285a6786052d6dc658891 Mon Sep 17 00:00:00 2001 From: dorving Date: Mon, 11 Jul 2022 22:09:51 +0200 Subject: [PATCH 3/5] Slight housekeeping in NetworkExtensionClient --- .../network/NetworkExtensionClient.java | 71 ++++++++++++------- .../network/NetworkExtensionServer.java | 2 +- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java index a821072..6a0e910 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java @@ -15,58 +15,75 @@ import org.slf4j.LoggerFactory; import java.util.Arrays; +/** + * A client for managing remote extensions. + * + * @author Dorving + */ public final class NetworkExtensionClient extends GEarthExtension { private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionClient.class); + private final Channel channel; + private final String title; private final String author; private final String version; private final String description; + private final String fileName; + private final String cookie; private final boolean fireEventButtonVisible; private final boolean leaveButtonVisible; private final boolean deleteButtonVisible; private final boolean isInstalledExtension; - private final String fileName; - private final String cookie; - private final Channel channel; - public NetworkExtensionClient(Incoming.ExtensionInfo msg, Channel channel) { - title = msg.getTitle(); - author = msg.getAuthor(); - version = msg.getVersion(); - description = msg.getDescription(); - fireEventButtonVisible = msg.isOnClickUsed(); - leaveButtonVisible = msg.isCanLeave(); - deleteButtonVisible = msg.isCanDelete(); - isInstalledExtension = msg.getFile() != null; - fileName = msg.getFile(); - cookie = msg.getCookie(); + /** + * Create a new {@link NetworkExtensionClient} instance. + * + * @param channel the channel through which to communicate with the remote extension. + * @param info the {@link Incoming.ExtensionInfo} detailing the extension. + */ + public NetworkExtensionClient(Channel channel, Incoming.ExtensionInfo info) { this.channel = channel; + title = info.getTitle(); + author = info.getAuthor(); + version = info.getVersion(); + description = info.getDescription(); + fireEventButtonVisible = info.isOnClickUsed(); + leaveButtonVisible = info.isCanLeave(); + deleteButtonVisible = info.isCanDelete(); + isInstalledExtension = info.getFile() != null; + fileName = info.getFile(); + cookie = info.getCookie(); } - public void handleIncomingMessage(Incoming msg) { + /** + * Handles {@link Incoming incoming messages}. + * + * @param incoming the {@link Incoming message} to be handled. + */ + public void handleIncomingMessage(Incoming incoming) { try { - if (msg instanceof Incoming.RequestFlags) + if (incoming instanceof Incoming.RequestFlags) requestFlags(); - else if (msg instanceof Incoming.SendMessage) { - final Incoming.SendMessage message = ((Incoming.SendMessage) msg); + else if (incoming instanceof Incoming.SendMessage) { + final Incoming.SendMessage message = ((Incoming.SendMessage) incoming); final HPacket packet = message.getPacket(); if (!packet.isCorrupted()) sendMessage(message.getDirection(), packet); - } else if (msg instanceof Incoming.ManipulatedPacket) { - sendManipulatedPacket(((Incoming.ManipulatedPacket) msg).gethMessage()); - } else if (msg instanceof Incoming.ExtensionConsoleLog) { - log(((Incoming.ExtensionConsoleLog) msg).getContents()); - } else if (msg instanceof Incoming.PacketToStringRequest) { + } else if (incoming instanceof Incoming.ManipulatedPacket) { + sendManipulatedPacket(((Incoming.ManipulatedPacket) incoming).gethMessage()); + } else if (incoming instanceof Incoming.ExtensionConsoleLog) { + log(((Incoming.ExtensionConsoleLog) incoming).getContents()); + } else if (incoming instanceof Incoming.PacketToStringRequest) { final HPacket hPacket = new HPacket(new byte[0]); - hPacket.constructFromString(((Incoming.PacketToStringRequest) msg).getString()); + hPacket.constructFromString(((Incoming.PacketToStringRequest) incoming).getString()); packetToStringRequest(hPacket); - } else if (msg instanceof Incoming.StringToPacketRequest) { - stringToPacketRequest(((Incoming.StringToPacketRequest) msg).getString()); + } else if (incoming instanceof Incoming.StringToPacketRequest) { + stringToPacketRequest(((Incoming.StringToPacketRequest) incoming).getString()); } } catch (Exception e){ - LOGGER.error("Failed to handle incoming message {} (channel={})", msg, channel, e); + LOGGER.error("Failed to handle incoming message {} (channel={})", incoming, channel, e); } } diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java index 671af59..e27fbc8 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java @@ -230,7 +230,7 @@ public final class NetworkExtensionServer implements ExtensionProducer { if (msg instanceof Incoming.ExtensionInfo) { if (client != null) LOGGER.warn("Overriding pre-existing CLIENT for channel (client={}, channel={})", client, channel); - client = new NetworkExtensionClient((Incoming.ExtensionInfo) msg, channel); + client = new NetworkExtensionClient(channel, (Incoming.ExtensionInfo) msg); if (NetworkExtensionAuthenticator.evaluate(client)) { LOGGER.info("Successfully authenticated client {}", client); clientAttribute.set(client); From c603cdbd61c80362b29186fe7f3812b318bd1cea Mon Sep 17 00:00:00 2001 From: dorving Date: Tue, 12 Jul 2022 00:10:46 +0200 Subject: [PATCH 4/5] Fixed issues with network extensions rewrite, see desc - specified NioServerSocketChannel in ServerBootstrap - moved TCP_NO_DELAY option to child channels in ServerBootstrap - swapped outgoing/incoming packet structure registration (also added some documentation) --- .../network/NetworkExtensionClient.java | 2 + .../network/NetworkExtensionCodec.java | 164 ++++++++++-------- .../network/NetworkExtensionServer.java | 24 ++- 3 files changed, 113 insertions(+), 77 deletions(-) diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java index 6a0e910..7b1fa3d 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionClient.java @@ -98,6 +98,8 @@ public final class NetworkExtensionClient extends GEarthExtension { channel.close(); } catch (Exception e){ LOGGER.error("Failed to close client (channel={})", channel, e); + } finally { + hasClosed(); } } diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java index 8be3150..6fb1807 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionCodec.java @@ -103,7 +103,84 @@ public final class NetworkExtensionCodec { } static { - // incoming + registerOutgoingMessages(); + registerIncomingMessages(); + } + + private static void registerIncomingMessages() { + register(Incoming.ExtensionInfo.HEADER_ID, + Incoming.ExtensionInfo.class, + (message, hPacket) -> { + hPacket.appendString(message.getTitle()); + hPacket.appendString(message.getAuthor()); + hPacket.appendString(message.getVersion()); + hPacket.appendString(message.getDescription()); + hPacket.appendBoolean(message.isOnClickUsed()); + hPacket.appendBoolean(message.getFile() != null); + hPacket.appendString(Optional.ofNullable(message.getFile()).orElse("")); + hPacket.appendString(Optional.ofNullable(message.getCookie()).orElse("")); + hPacket.appendBoolean(message.isCanLeave()); + hPacket.appendBoolean(message.isCanDelete()); + }, + (hPacket -> { + final String title = hPacket.readString(); + final String author = hPacket.readString(); + final String version = hPacket.readString(); + final String description = hPacket.readString(); + final boolean isOnClickUsed = hPacket.readBoolean(); + final boolean hasFile = hPacket.readBoolean(); + String file = hPacket.readString(); + if (!hasFile) + file = null; + String cookie = hPacket.readString(); + if (cookie.isEmpty()) + cookie = null; + final boolean canLeave = hPacket.readBoolean(); + final boolean canDelete = hPacket.readBoolean(); + return new Incoming.ExtensionInfo(title, author, version, description, isOnClickUsed, file, cookie, canLeave, canDelete); + })); + register(Incoming.ManipulatedPacket.MANIPULATED_PACKET, + Incoming.ManipulatedPacket.class, + (message, hPacket) -> hPacket.appendLongString(message.gethMessage().stringify()), + (hPacket -> { + final String packetString = hPacket.readLongString(6); + final HMessage hMessage = new HMessage(packetString); + return new Incoming.ManipulatedPacket(hMessage); + })); + register(Incoming.SendMessage.HEADER_ID, + Incoming.SendMessage.class, + ((message, hPacket) -> { + hPacket.appendByte((byte) (message.getDirection() == TOCLIENT ? 0 : 1)); + hPacket.appendInt(message.getPacket().getBytesLength()); + hPacket.appendBytes(message.getPacket().toBytes()); + }), + (hPacket -> { + final byte side = hPacket.readByte(); + final int length = hPacket.readInteger(); + final byte[] data = hPacket.readBytes(length); + final HPacket packet = new HPacket(data); + return new Incoming.SendMessage(packet, side == 0 ? TOCLIENT : TOSERVER); + })); + register(Incoming.RequestFlags.HEADER_ID, + Incoming.RequestFlags.class, + (message, hPacket) -> { + }, + (hPacket -> new Incoming.RequestFlags())); + register(Incoming.ExtensionConsoleLog.HEADER_ID, + Incoming.ExtensionConsoleLog.class, + (message, hPacket) -> hPacket.appendString(message.getContents()), + (hPacket -> new Incoming.ExtensionConsoleLog(hPacket.readString()))); + register(Incoming.PacketToStringRequest.HEADER_ID, + Incoming.PacketToStringRequest.class, + (message, hPacket) -> hPacket.appendLongString(message.getString()), + (hPacket -> new Incoming.PacketToStringRequest(hPacket.readLongString()))); + register(Incoming.StringToPacketRequest.HEADER_ID, + Incoming.StringToPacketRequest.class, + (message, hPacket) -> hPacket.appendLongString(message.getString(), StandardCharsets.UTF_8), + (hPacket -> new Incoming.StringToPacketRequest(hPacket.readLongString(StandardCharsets.UTF_8)))); + } + + private static void registerOutgoingMessages() { register(Outgoing.InfoRequest.HEADER_ID, Outgoing.InfoRequest.class, (message, hPacket) -> { @@ -178,88 +255,25 @@ public final class NetworkExtensionCodec { (message, hPacket) -> hPacket.appendLongString(message.getString()), (hPacket -> new Outgoing.StringToPacketResponse(hPacket.readLongString())) ); - // outgoing - register(Incoming.ExtensionInfo.HEADER_ID, - Incoming.ExtensionInfo.class, - (message, hPacket) -> { - hPacket.appendString(message.getTitle()); - hPacket.appendString(message.getAuthor()); - hPacket.appendString(message.getVersion()); - hPacket.appendString(message.getDescription()); - hPacket.appendBoolean(message.isOnClickUsed()); - hPacket.appendBoolean(message.getFile() != null); - hPacket.appendString(Optional.ofNullable(message.getFile()).orElse("")); - hPacket.appendString(Optional.ofNullable(message.getCookie()).orElse("")); - hPacket.appendBoolean(message.isCanLeave()); - hPacket.appendBoolean(message.isCanDelete()); - }, - (hPacket -> { - final String title = hPacket.readString(); - final String author = hPacket.readString(); - final String version = hPacket.readString(); - final String description = hPacket.readString(); - final boolean isOnClickUsed = hPacket.readBoolean(); - final boolean hasFile = hPacket.readBoolean(); - String file = hPacket.readString(); - if (!hasFile) - file = null; - String cookie = hPacket.readString(); - if (cookie.isEmpty()) - cookie = null; - final boolean canLeave = hPacket.readBoolean(); - final boolean canDelete = hPacket.readBoolean(); - return new Incoming.ExtensionInfo(title, author, version, description, isOnClickUsed, file, cookie, canLeave, canDelete); - })); - register(Incoming.ManipulatedPacket.MANIPULATED_PACKET, - Incoming.ManipulatedPacket.class, - (message, hPacket) -> hPacket.appendLongString(message.gethMessage().stringify()), - (hPacket -> { - final String packetString = hPacket.readLongString(6); - final HMessage hMessage = new HMessage(packetString); - return new Incoming.ManipulatedPacket(hMessage); - })); - register(Incoming.SendMessage.HEADER_ID, - Incoming.SendMessage.class, - ((message, hPacket) -> { - hPacket.appendByte((byte) (message.getDirection() == TOCLIENT ? 0 : 1)); - hPacket.appendInt(message.getPacket().getBytesLength()); - hPacket.appendBytes(message.getPacket().toBytes()); - }), - (hPacket -> { - final byte side = hPacket.readByte(); - final int length = hPacket.readInteger(); - final byte[] data = hPacket.readBytes(length); - final HPacket packet = new HPacket(data); - return new Incoming.SendMessage(packet, side == 0 ? TOCLIENT : TOSERVER); - })); - register(Incoming.RequestFlags.HEADER_ID, - Incoming.RequestFlags.class, - (message, hPacket) -> { - }, - (hPacket -> new Incoming.RequestFlags())); - register(Incoming.ExtensionConsoleLog.HEADER_ID, - Incoming.ExtensionConsoleLog.class, - (message, hPacket) -> hPacket.appendString(message.getContents()), - (hPacket -> new Incoming.ExtensionConsoleLog(hPacket.readString()))); - register(Incoming.PacketToStringRequest.HEADER_ID, - Incoming.PacketToStringRequest.class, - (message, hPacket) -> hPacket.appendLongString(message.getString()), - (hPacket -> new Incoming.PacketToStringRequest(hPacket.readLongString()))); - register(Incoming.StringToPacketRequest.HEADER_ID, - Incoming.StringToPacketRequest.class, - (message, hPacket) -> hPacket.appendLongString(message.getString(), StandardCharsets.UTF_8), - (hPacket -> new Incoming.StringToPacketRequest(hPacket.readLongString(StandardCharsets.UTF_8)))); } private static void register(final int headerId, Class tClass, BiConsumer writer, Function reader) { final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), writer, reader); if (tClass.getSuperclass() == Outgoing.class) - incomingPacketStructures.put(headerId, packetStructure); - else outgoingPacketStructures.put(tClass, packetStructure); + else + incomingPacketStructures.put(headerId, packetStructure); } - + /** + * Represents the packet structure of a {@link NetworkExtensionMessage}. + * + * Can be used to {@link PacketStructure#writer write} messages to packets + * and {@link PacketStructure#reader read} messages from packets. + * + * @apiNote At the moment both outgoing and incoming messages have a reader and writer defined, + * this is so that in the future the same codec can be used for the Extensions API. + */ static class PacketStructure { private final int headerId; diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java index e27fbc8..54c8bc2 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionServer.java @@ -10,6 +10,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.util.Attribute; @@ -21,6 +22,7 @@ import java.io.IOException; import java.net.DatagramSocket; import java.net.ServerSocket; import java.util.List; +import java.util.Optional; import java.util.function.BiConsumer; import static gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.*; @@ -52,8 +54,9 @@ public final class NetworkExtensionServer implements ExtensionProducer { public void startProducing(ExtensionProducerObserver observer) { final ServerBootstrap bootstrap = new ServerBootstrap() - .option(ChannelOption.TCP_NODELAY, true) + .channel(NioServerSocketChannel.class) .childHandler(new Initializer(observer)) + .childOption(ChannelOption.TCP_NODELAY, true) .group(new NioEventLoopGroup()); port = PORT_ONSET; @@ -141,6 +144,10 @@ public final class NetworkExtensionServer implements ExtensionProducer { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { switch (stage) { case LENGTH: + + if (in.readableBytes() < HEADER_LENGTH) + return; + payloadLength = in.readInt(); stage = Stage.PAYLOAD; break; @@ -219,6 +226,7 @@ public final class NetworkExtensionServer implements ExtensionProducer { @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { LOGGER.trace("Channel unregistered (channel={})", ctx.channel()); + close(ctx); super.channelUnregistered(ctx); } @@ -251,7 +259,19 @@ public final class NetworkExtensionServer implements ExtensionProducer { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { LOGGER.error("Channel exception caught (channel={}), closing channel", ctx.channel(), cause); - ctx.channel().close(); + close(ctx); + } + + private void close(ChannelHandlerContext ctx) { + final Optional optionalClient = findClient(ctx); + if (optionalClient.isPresent()) + optionalClient.get().close(); + else + ctx.channel().close(); + } + + private Optional findClient(ChannelHandlerContext ctx) { + return Optional.ofNullable(ctx.attr(CLIENT).get()); } } } From 8b8c4fff72bb2bb957bd3e0deb5af899cf125361 Mon Sep 17 00:00:00 2001 From: dorving Date: Tue, 12 Jul 2022 01:39:01 +0200 Subject: [PATCH 5/5] Added dedicated loggers for each extension if logger flag is set --- .../executer/NormalExtensionRunner.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java index 15b5228..4f3a113 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/executer/NormalExtensionRunner.java @@ -12,7 +12,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Random; @@ -41,7 +43,7 @@ public final class NormalExtensionRunner implements ExtensionRunner { @Override public void runAllExtensions(int port) { - if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)){ + if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)) { final File extensionsDirectory = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toFile(); final File[] extensionFiles = extensionsDirectory.listFiles(); @@ -68,7 +70,7 @@ public final class NormalExtensionRunner implements ExtensionRunner { final String[] split = name.split("\\."); final String ext = "*." + split[split.length - 1]; - final String realName = String.join(".",Arrays.copyOf(split, split.length-1)); + final String realName = String.join(".", Arrays.copyOf(split, split.length - 1)); final String newName = realName + "-" + getRandomString() + ext.substring(1); final Path newPath = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, newName); @@ -101,7 +103,7 @@ public final class NormalExtensionRunner implements ExtensionRunner { for (int i = 0; i < execCommand.length; i++) { execCommand[i] = execCommand[i] .replace("{path}", path) - .replace("{port}", port+"") + .replace("{port}", port + "") .replace("{filename}", filename) .replace("{cookie}", cookie); } @@ -110,36 +112,37 @@ public final class NormalExtensionRunner implements ExtensionRunner { final Process process = processBuilder.start(); maybeLogExtension(path, process); + } catch (IOException e) { LOGGER.error("Failed to run extension at path {} using port {}", path, port, e); } } + public static void maybeLogExtension(String path, Process process) { if (GEarth.hasFlag(ExtensionRunner.SHOW_EXTENSIONS_LOG)) { - final String separator = "" + System.lineSeparator(); - LOGGER.info(path + separator + "Launching" + separator + "----------" + separator); + final Logger logger = LoggerFactory.getLogger(path); + + logger.info("Launching..."); final BufferedReader processInputReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - new Thread(() -> { try { String line; - while((line = processInputReader.readLine()) != null) - LOGGER.info(path + separator + "Output" + separator + line + separator + "----------" + separator); + while ((line = processInputReader.readLine()) != null) + logger.info(line); } catch (IOException e) { LOGGER.error("Failed to read input line from process {}", process, e); } - }).start(); + }, path+"-input").start(); final BufferedReader processErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); new Thread(() -> { try { String line; - while((line = processErrorReader.readLine()) != null) { - LOGGER.error(path + separator + "Error" + separator + line + separator + "----------" + separator); - } + while ((line = processErrorReader.readLine()) != null) + logger.error(line); } catch (IOException e) { LOGGER.error("Failed to read error line from process {}", process, e); }