From 06b794dcea806150770fb88d43e366a3496a9d0f Mon Sep 17 00:00:00 2001 From: lucko Date: Sat, 28 Jan 2023 11:07:45 +0000 Subject: Stream live data to the viewer using WebSockets (#294) --- .../me/lucko/spark/common/ws/CryptoAlgorithm.java | 90 ++++++++ .../me/lucko/spark/common/ws/TrustedKeyStore.java | 139 +++++++++++ .../me/lucko/spark/common/ws/ViewerSocket.java | 255 +++++++++++++++++++++ .../spark/common/ws/ViewerSocketConnection.java | 218 ++++++++++++++++++ 4 files changed, 702 insertions(+) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/ws/CryptoAlgorithm.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/ws/TrustedKeyStore.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java (limited to 'spark-common/src/main/java/me/lucko/spark/common/ws') diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/CryptoAlgorithm.java b/spark-common/src/main/java/me/lucko/spark/common/ws/CryptoAlgorithm.java new file mode 100644 index 0000000..f6cf1db --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/CryptoAlgorithm.java @@ -0,0 +1,90 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.ws; + +import com.google.protobuf.ByteString; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; + +/** + * An algorithm for keypair/signature cryptography. + */ +public enum CryptoAlgorithm { + + Ed25519("Ed25519", 255, "Ed25519"), + RSA2048("RSA", 2048, "SHA256withRSA"); + + private final String keyAlgorithm; + private final int keySize; + private final String signatureAlgorithm; + + CryptoAlgorithm(String keyAlgorithm, int keySize, String signatureAlgorithm) { + this.keyAlgorithm = keyAlgorithm; + this.keySize = keySize; + this.signatureAlgorithm = signatureAlgorithm; + } + + public KeyPairGenerator createKeyPairGenerator() throws NoSuchAlgorithmException { + return KeyPairGenerator.getInstance(this.keyAlgorithm); + } + + public KeyFactory createKeyFactory() throws NoSuchAlgorithmException { + return KeyFactory.getInstance(this.keyAlgorithm); + } + + public Signature createSignature() throws NoSuchAlgorithmException { + return Signature.getInstance(this.signatureAlgorithm); + } + + public KeyPair generateKeyPair() { + try { + KeyPairGenerator generator = createKeyPairGenerator(); + generator.initialize(this.keySize); + return generator.generateKeyPair(); + } catch (Exception e) { + throw new RuntimeException("Exception generating keypair", e); + } + } + + public PublicKey decodePublicKey(byte[] bytes) throws IllegalArgumentException { + try { + X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); + KeyFactory factory = createKeyFactory(); + return factory.generatePublic(spec); + } catch (Exception e) { + throw new IllegalArgumentException("Exception parsing public key", e); + } + } + + public PublicKey decodePublicKey(ByteString bytes) throws IllegalArgumentException { + if (bytes == null) { + return null; + } + return decodePublicKey(bytes.toByteArray()); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/TrustedKeyStore.java b/spark-common/src/main/java/me/lucko/spark/common/ws/TrustedKeyStore.java new file mode 100644 index 0000000..1605a38 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/TrustedKeyStore.java @@ -0,0 +1,139 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.ws; + +import me.lucko.spark.common.util.Configuration; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +/** + * A store of trusted public keys. + */ +public class TrustedKeyStore { + private static final String TRUSTED_KEYS_OPTION = "trustedKeys"; + + /** The spark configuration */ + private final Configuration configuration; + /** Gets the local public/private key */ + private final CompletableFuture localKeyPair; + /** A set of remote public keys to trust */ + private final Set remoteTrustedKeys; + /** A mpa of pending remote public keys */ + private final Map remotePendingKeys = new HashMap<>(); + + public TrustedKeyStore(Configuration configuration) { + this.configuration = configuration; + this.localKeyPair = CompletableFuture.supplyAsync(ViewerSocketConnection.CRYPTO::generateKeyPair); + this.remoteTrustedKeys = new HashSet<>(); + readTrustedKeys(); + } + + /** + * Gets the local public key. + * + * @return the local public key + */ + public PublicKey getLocalPublicKey() { + return this.localKeyPair.join().getPublic(); + } + + /** + * Gets the local private key. + * + * @return the local private key + */ + public PrivateKey getLocalPrivateKey() { + return this.localKeyPair.join().getPrivate(); + } + + /** + * Checks if a remote public key is trusted + * + * @param publicKey the public key + * @return if the key is trusted + */ + public boolean isKeyTrusted(PublicKey publicKey) { + return publicKey != null && this.remoteTrustedKeys.contains(publicKey); + } + + /** + * Adds a pending public key to be trusted in the future. + * + * @param clientId the client id submitting the key + * @param publicKey the public key + */ + public void addPendingKey(String clientId, PublicKey publicKey) { + this.remotePendingKeys.put(clientId, publicKey); + } + + /** + * Trusts a previously submitted remote public key + * + * @param clientId the id of the client that submitted the key + * @return true if the key was found and trusted + */ + public boolean trustPendingKey(String clientId) { + PublicKey key = this.remotePendingKeys.remove(clientId); + if (key == null) { + return false; + } + + this.remoteTrustedKeys.add(key); + writeTrustedKeys(); + return true; + } + + /** + * Reads trusted keys from the configuration + */ + private void readTrustedKeys() { + for (String encodedKey : this.configuration.getStringList(TRUSTED_KEYS_OPTION)) { + try { + PublicKey publicKey = ViewerSocketConnection.CRYPTO.decodePublicKey(Base64.getDecoder().decode(encodedKey)); + this.remoteTrustedKeys.add(publicKey); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Writes trusted keys to the configuration + */ + private void writeTrustedKeys() { + List encodedKeys = this.remoteTrustedKeys.stream() + .map(key -> Base64.getEncoder().encodeToString(key.getEncoded())) + .collect(Collectors.toList()); + + this.configuration.setStringList(TRUSTED_KEYS_OPTION, encodedKeys); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java new file mode 100644 index 0000000..5c7e08c --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java @@ -0,0 +1,255 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.ws; + +import com.google.protobuf.ByteString; + +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.sampler.AbstractSampler; +import me.lucko.spark.common.sampler.Sampler; +import me.lucko.spark.common.sampler.window.ProfilingWindowUtils; +import me.lucko.spark.common.util.MediaTypes; +import me.lucko.spark.common.util.ws.BytesocksClient; +import me.lucko.spark.proto.SparkProtos; +import me.lucko.spark.proto.SparkSamplerProtos; +import me.lucko.spark.proto.SparkWebSocketProtos.ClientConnect; +import me.lucko.spark.proto.SparkWebSocketProtos.ClientPing; +import me.lucko.spark.proto.SparkWebSocketProtos.PacketWrapper; +import me.lucko.spark.proto.SparkWebSocketProtos.ServerConnectResponse; +import me.lucko.spark.proto.SparkWebSocketProtos.ServerPong; +import me.lucko.spark.proto.SparkWebSocketProtos.ServerUpdateSamplerData; +import me.lucko.spark.proto.SparkWebSocketProtos.ServerUpdateStatistics; + +import java.security.PublicKey; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +/** + * Represents a connection with the spark viewer. + */ +public class ViewerSocket implements ViewerSocketConnection.Listener, AutoCloseable { + + /** Allow 60 seconds for the first client to connect */ + private static final long SOCKET_INITIAL_TIMEOUT = TimeUnit.SECONDS.toMillis(60); + + /** Once established, expect a ping at least once every 30 seconds */ + private static final long SOCKET_ESTABLISHED_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + + /** The spark platform */ + private final SparkPlatform platform; + /** The export props to use when exporting the sampler data */ + private final Sampler.ExportProps exportProps; + /** The underlying connection */ + private final ViewerSocketConnection socket; + + private boolean closed = false; + private final long socketOpenTime = System.currentTimeMillis(); + private long lastPing = 0; + private String lastPayloadId = null; + + public ViewerSocket(SparkPlatform platform, BytesocksClient client, Sampler.ExportProps exportProps) throws Exception { + this.platform = platform; + this.exportProps = exportProps; + this.socket = new ViewerSocketConnection(platform, client, this); + } + + private void log(String message) { + this.platform.getPlugin().log(Level.INFO, "[Viewer - " + this.socket.getChannelId() + "] " + message); + } + + /** + * Gets the initial payload to send to the viewer. + * + * @return the payload + */ + public SparkSamplerProtos.SocketChannelInfo getPayload() { + return SparkSamplerProtos.SocketChannelInfo.newBuilder() + .setChannelId(this.socket.getChannelId()) + .setPublicKey(ByteString.copyFrom(this.platform.getTrustedKeyStore().getLocalPublicKey().getEncoded())) + .build(); + } + + public boolean isOpen() { + return !this.closed && this.socket.isOpen(); + } + + /** + * Called each time the sampler rotates to a new window. + * + * @param sampler the sampler + */ + public void processWindowRotate(AbstractSampler sampler) { + if (this.closed) { + return; + } + + long time = System.currentTimeMillis(); + if ((time - this.socketOpenTime) > SOCKET_INITIAL_TIMEOUT && (time - this.lastPing) > SOCKET_ESTABLISHED_TIMEOUT) { + log("No clients have pinged for 30s, closing socket"); + close(); + return; + } + + // no clients connected yet! + if (this.lastPing == 0) { + return; + } + + try { + SparkSamplerProtos.SamplerData samplerData = sampler.toProto(this.platform, this.exportProps); + String key = this.platform.getBytebinClient().postContent(samplerData, MediaTypes.SPARK_SAMPLER_MEDIA_TYPE, "live").key(); + sendUpdatedSamplerData(key); + } catch (Exception e) { + this.platform.getPlugin().log(Level.WARNING, "Error whilst sending updated sampler data to the socket"); + e.printStackTrace(); + } + } + + /** + * Called when the sampler stops. + * + * @param sampler the sampler + */ + public void processSamplerStopped(AbstractSampler sampler) { + if (this.closed) { + return; + } + + close(); + } + + @Override + public void close() { + this.socket.sendPacket(builder -> builder.setServerPong(ServerPong.newBuilder() + .setOk(false) + .build() + )); + this.socket.close(); + this.closed = true; + } + + @Override + public boolean isKeyTrusted(PublicKey publicKey) { + return this.platform.getTrustedKeyStore().isKeyTrusted(publicKey); + } + + /** + * Sends a message to the socket to say that the given client is now trusted. + * + * @param clientId the client id + */ + public void sendClientTrustedMessage(String clientId) { + this.socket.sendPacket(builder -> builder.setServerConnectResponse(ServerConnectResponse.newBuilder() + .setClientId(clientId) + .setState(ServerConnectResponse.State.ACCEPTED) + .build() + )); + } + + /** + * Sends a message to the socket to indicate that updated sampler data is available + * + * @param payloadId the payload id of the updated data + */ + public void sendUpdatedSamplerData(String payloadId) { + this.socket.sendPacket(builder -> builder.setServerUpdateSampler(ServerUpdateSamplerData.newBuilder() + .setPayloadId(payloadId) + .build() + )); + this.lastPayloadId = payloadId; + } + + /** + * Sends a message to the socket with updated statistics + * + * @param platform the platform statistics + * @param system the system statistics + */ + public void sendUpdatedStatistics(SparkProtos.PlatformStatistics platform, SparkProtos.SystemStatistics system) { + this.socket.sendPacket(builder -> builder.setServerUpdateStatistics(ServerUpdateStatistics.newBuilder() + .setPlatform(platform) + .setSystem(system) + .build() + )); + } + + @Override + public void onPacket(PacketWrapper packet, boolean verified, PublicKey publicKey) throws Exception { + switch (packet.getPacketCase()) { + case CLIENT_PING: + onClientPing(packet.getClientPing(), publicKey); + break; + case CLIENT_CONNECT: + onClientConnect(packet.getClientConnect(), verified, publicKey); + break; + default: + throw new IllegalArgumentException("Unexpected packet: " + packet.getPacketCase()); + } + } + + private void onClientPing(ClientPing packet, PublicKey publicKey) { + this.lastPing = System.currentTimeMillis(); + this.socket.sendPacket(builder -> builder.setServerPong(ServerPong.newBuilder() + .setOk(!this.closed) + .setData(packet.getData()) + .build() + )); + } + + private void onClientConnect(ClientConnect packet, boolean verified, PublicKey publicKey) { + if (publicKey == null) { + throw new IllegalStateException("Missing public key"); + } + + this.lastPing = System.currentTimeMillis(); + + String clientId = packet.getClientId(); + log("Client connected: clientId=" + clientId + ", keyhash=" + hashPublicKey(publicKey) + ", desc=" + packet.getDescription()); + + ServerConnectResponse.Builder resp = ServerConnectResponse.newBuilder() + .setClientId(clientId) + .setSettings(ServerConnectResponse.Settings.newBuilder() + .setSamplerInterval(ProfilingWindowUtils.WINDOW_SIZE_SECONDS) + .setStatisticsInterval(10) + .build() + ); + + if (this.lastPayloadId != null) { + resp.setLastPayloadId(this.lastPayloadId); + } + + if (this.closed) { + resp.setState(ServerConnectResponse.State.REJECTED); + } else if (verified) { + resp.setState(ServerConnectResponse.State.ACCEPTED); + } else { + resp.setState(ServerConnectResponse.State.UNTRUSTED); + this.platform.getTrustedKeyStore().addPendingKey(clientId, publicKey); + } + + this.socket.sendPacket(builder -> builder.setServerConnectResponse(resp.build())); + } + + private static String hashPublicKey(PublicKey publicKey) { + return publicKey == null ? "null" : Integer.toHexString(publicKey.hashCode()); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java new file mode 100644 index 0000000..f870cb7 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java @@ -0,0 +1,218 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.ws; + +import com.google.protobuf.ByteString; + +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.util.ws.BytesocksClient; +import me.lucko.spark.proto.SparkWebSocketProtos.PacketWrapper; +import me.lucko.spark.proto.SparkWebSocketProtos.RawPacket; + +import java.io.IOException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.util.Base64; +import java.util.function.Consumer; +import java.util.logging.Level; + +/** + * Controls a websocket connection between a spark server (the plugin/mod) and a spark client (the web viewer). + */ +public class ViewerSocketConnection implements BytesocksClient.Listener, AutoCloseable { + + /** The protocol version */ + public static final int VERSION_1 = 1; + /** The crypto algorithm used to sign/verify messages sent between the server and client */ + public static final CryptoAlgorithm CRYPTO = CryptoAlgorithm.RSA2048; + + /** The platform */ + private final SparkPlatform platform; + /** The underlying listener */ + private final Listener listener; + /** The private key used to sign messages sent from this connection */ + private final PrivateKey privateKey; + /** The bytesocks socket */ + private final BytesocksClient.Socket socket; + + public ViewerSocketConnection(SparkPlatform platform, BytesocksClient client, Listener listener) throws Exception { + this.platform = platform; + this.listener = listener; + this.privateKey = platform.getTrustedKeyStore().getLocalPrivateKey(); + this.socket = client.createAndConnect(this); + } + + public interface Listener { + + /** + * Checks if the given public key is trusted + * + * @param publicKey the public key + * @return true if trusted + */ + boolean isKeyTrusted(PublicKey publicKey); + + /** + * Handles a packet sent to the socket + * + * @param packet the packet that was sent + * @param verified if the packet was signed by a trusted key + * @param publicKey the public key the packet was signed with + */ + void onPacket(PacketWrapper packet, boolean verified, PublicKey publicKey) throws Exception; + } + + /** + * Gets the bytesocks channel id + * + * @return the channel id + */ + public String getChannelId() { + return this.socket.getChannelId(); + } + + /** + * Gets if the underlying socket is open + * + * @return true if the socket is open + */ + public boolean isOpen() { + return this.socket.isOpen(); + } + + @Override + public void onText(CharSequence data) { + try { + RawPacket packet = decodeRawPacket(data); + handleRawPacket(packet); + } catch (Exception e) { + this.platform.getPlugin().log(Level.WARNING, "Exception occurred while reading data from the socket"); + e.printStackTrace(); + } + } + + @Override + public void onError(Throwable error) { + this.platform.getPlugin().log(Level.INFO, "Socket error: " + error.getClass().getName() + " " + error.getMessage()); + error.printStackTrace(); + } + + @Override + public void onClose(int statusCode, String reason) { + //this.platform.getPlugin().log(Level.INFO, "Socket closed with status " + statusCode + " and reason " + reason); + } + + /** + * Sends a packet to the socket. + * + * @param packetBuilder the builder to construct the wrapper packet + */ + public void sendPacket(Consumer packetBuilder) { + PacketWrapper.Builder builder = PacketWrapper.newBuilder(); + packetBuilder.accept(builder); + PacketWrapper wrapper = builder.build(); + + try { + sendPacket(wrapper); + } catch (Exception e) { + this.platform.getPlugin().log(Level.WARNING, "Exception occurred while sending data to the socket"); + e.printStackTrace(); + } + } + + /** + * Sends a packet to the socket. + * + * @param packet the packet to send + */ + private void sendPacket(PacketWrapper packet) throws Exception { + ByteString msg = packet.toByteString(); + + // sign the message using the server private key + Signature sign = CRYPTO.createSignature(); + sign.initSign(this.privateKey); + sign.update(msg.asReadOnlyByteBuffer()); + byte[] signature = sign.sign(); + + sendRawPacket(RawPacket.newBuilder() + .setVersion(VERSION_1) + .setSignature(ByteString.copyFrom(signature)) + .setMessage(msg) + .build() + ); + } + + /** + * Sends a raw packet to the socket. + * + * @param packet the packet to send + */ + private void sendRawPacket(RawPacket packet) throws IOException { + byte[] buf = packet.toByteArray(); + String encoded = Base64.getEncoder().encodeToString(buf); + this.socket.send(encoded); + } + + /** + * Decodes a raw packet sent to the socket. + * + * @param data the encoded data + * @return the decoded packet + */ + private RawPacket decodeRawPacket(CharSequence data) throws IOException { + byte[] buf = Base64.getDecoder().decode(data.toString()); + return RawPacket.parseFrom(buf); + } + + /** + * Handles a raw packet sent to the socket + * + * @param packet the packet + */ + private void handleRawPacket(RawPacket packet) throws Exception { + int version = packet.getVersion(); + if (version != VERSION_1) { + throw new IllegalArgumentException("Unsupported packet version " + version); + } + + ByteString message = packet.getMessage(); + PublicKey publicKey = CRYPTO.decodePublicKey(packet.getPublicKey()); + ByteString signature = packet.getSignature(); + + boolean verified = false; + if (signature != null && publicKey != null && this.listener.isKeyTrusted(publicKey)) { + Signature sign = CRYPTO.createSignature(); + sign.initVerify(publicKey); + sign.update(message.asReadOnlyByteBuffer()); + + verified = sign.verify(signature.toByteArray()); + } + + PacketWrapper wrapper = PacketWrapper.parseFrom(message); + this.listener.onPacket(wrapper, verified, publicKey); + } + + @Override + public void close() { + this.socket.close(1001 /* going away */, "spark plugin disconnected"); + } +} -- cgit From 0f30d2983ec6ef487fd1966c1c22fa4a73e081f9 Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 7 Mar 2023 21:54:14 +0000 Subject: Don't use multi-release jar for websocket code --- build.gradle | 6 - spark-bukkit/build.gradle | 1 + spark-bungeecord/build.gradle | 1 + spark-common/build.gradle | 23 +--- .../java/me/lucko/spark/common/SparkPlatform.java | 5 +- .../common/command/modules/SamplerModule.java | 2 +- .../spark/common/util/ws/BytesocksClient.java | 118 ----------------- .../spark/common/util/ws/BytesocksClientImpl.java | 40 ------ .../me/lucko/spark/common/ws/ViewerSocket.java | 2 +- .../spark/common/ws/ViewerSocketConnection.java | 2 +- .../spark/common/util/ws/BytesocksClientImpl.java | 146 --------------------- spark-fabric/build.gradle | 1 + spark-forge/build.gradle | 1 + spark-minestom/build.gradle | 1 + spark-nukkit/build.gradle | 1 + spark-sponge7/build.gradle | 1 + spark-sponge8/build.gradle | 1 + spark-velocity/build.gradle | 1 + spark-velocity4/build.gradle | 1 + spark-waterdog/build.gradle | 1 + 20 files changed, 18 insertions(+), 337 deletions(-) delete mode 100644 spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClient.java delete mode 100644 spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClientImpl.java delete mode 100644 spark-common/src/main/java11/me/lucko/spark/common/util/ws/BytesocksClientImpl.java (limited to 'spark-common/src/main/java/me/lucko/spark/common/ws') diff --git a/build.gradle b/build.gradle index 8984ca1..f1a0a8e 100644 --- a/build.gradle +++ b/build.gradle @@ -29,12 +29,6 @@ subprojects { options.release = 8 } - jar { - manifest { - attributes('Multi-Release': 'true') - } - } - processResources { duplicatesStrategy = DuplicatesStrategy.INCLUDE } diff --git a/spark-bukkit/build.gradle b/spark-bukkit/build.gradle index 92b65cc..1646bf2 100644 --- a/spark-bukkit/build.gradle +++ b/spark-bukkit/build.gradle @@ -37,6 +37,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-bungeecord/build.gradle b/spark-bungeecord/build.gradle index 885de55..7e6b93f 100644 --- a/spark-bungeecord/build.gradle +++ b/spark-bungeecord/build.gradle @@ -27,6 +27,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-common/build.gradle b/spark-common/build.gradle index 742d943..1713168 100644 --- a/spark-common/build.gradle +++ b/spark-common/build.gradle @@ -4,24 +4,6 @@ plugins { id 'com.google.protobuf' version '0.9.1' } -sourceSets { - java11 { - java { - srcDirs = ['src/main/java11'] - } - } -} - -compileJava11Java { - options.release = 11 -} - -jar { - into('META-INF/versions/11') { - from sourceSets.java11.output - } -} - license { exclude '**/sampler/async/jfr/**' } @@ -37,9 +19,8 @@ dependencies { implementation 'com.github.jvm-profiling-tools:async-profiler:v2.8.3' implementation 'org.ow2.asm:asm:9.1' implementation 'net.bytebuddy:byte-buddy-agent:1.11.0' - implementation 'com.google.protobuf:protobuf-javalite:3.21.11' - java11Implementation 'com.google.protobuf:protobuf-javalite:3.21.11' + implementation 'me.lucko:bytesocks-java-client:1.0-SNAPSHOT' api('net.kyori:adventure-api:4.12.0') { exclude(module: 'adventure-bom') @@ -61,8 +42,6 @@ dependencies { compileOnly 'com.google.code.gson:gson:2.7' compileOnly 'com.google.guava:guava:19.0' compileOnly 'org.checkerframework:checker-qual:3.8.0' - - java11Implementation files(sourceSets.main.output.classesDirs) { builtBy compileJava } } protobuf { diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index 61c6062..f609719 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -23,6 +23,8 @@ package me.lucko.spark.common; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import me.lucko.bytesocks.client.BytesocksClient; +import me.lucko.bytesocks.client.BytesocksClientFactory; import me.lucko.spark.common.activitylog.ActivityLog; import me.lucko.spark.common.api.SparkApi; import me.lucko.spark.common.command.Arguments; @@ -53,7 +55,6 @@ import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.common.util.BytebinClient; import me.lucko.spark.common.util.Configuration; import me.lucko.spark.common.util.TemporaryFiles; -import me.lucko.spark.common.util.ws.BytesocksClient; import me.lucko.spark.common.ws.TrustedKeyStore; import net.kyori.adventure.text.Component; @@ -126,7 +127,7 @@ public class SparkPlatform { String bytesocksHost = this.configuration.getString("bytesocksHost", "spark-usersockets.lucko.me"); this.bytebinClient = new BytebinClient(bytebinUrl, "spark-plugin"); - this.bytesocksClient = BytesocksClient.create(bytesocksHost, "spark-plugin"); + this.bytesocksClient = BytesocksClientFactory.newClient(bytesocksHost, "spark-plugin"); this.trustedKeyStore = new TrustedKeyStore(this.configuration); this.disableResponseBroadcast = this.configuration.getBoolean("disableResponseBroadcast", false); diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index 049c817..27e790f 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -22,6 +22,7 @@ package me.lucko.spark.common.command.modules; import com.google.common.collect.Iterables; +import me.lucko.bytesocks.client.BytesocksClient; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.activitylog.Activity; import me.lucko.spark.common.command.Arguments; @@ -43,7 +44,6 @@ import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.util.FormatUtil; import me.lucko.spark.common.util.MediaTypes; import me.lucko.spark.common.util.MethodDisambiguator; -import me.lucko.spark.common.util.ws.BytesocksClient; import me.lucko.spark.common.ws.ViewerSocket; import me.lucko.spark.proto.SparkSamplerProtos; diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClient.java b/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClient.java deleted file mode 100644 index 1db7a67..0000000 --- a/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClient.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package me.lucko.spark.common.util.ws; - -import java.util.concurrent.CompletableFuture; - -/** - * A client that can interact with bytesocks. - * - * @see https://github.com/lucko/bytesocks - */ -public interface BytesocksClient { - - /** - * Creates a new {@link BytesocksClient}. - * - *

Returns {@code null} on Java versions before 11.

- * - * @param host the host - * @param userAgent the user agent - * @return the client - */ - static BytesocksClient create(String host, String userAgent) { - try { - return new BytesocksClientImpl(host, userAgent); - } catch (UnsupportedOperationException e) { - return null; - } - } - - /** - * Creates a new bytesocks channel and returns a socket connected to it. - * - * @param listener the listener - * @return the socket - * @throws Exception if something goes wrong - */ - Socket createAndConnect(Listener listener) throws Exception; - - /** - * Connects to an existing bytesocks channel. - * - * @param channelId the channel id - * @param listener the listener - * @return the socket - * @throws Exception if something goes wrong - */ - Socket connect(String channelId, Listener listener) throws Exception; - - /** - * A socket connected to a bytesocks channel. - */ - interface Socket { - - /** - * Gets the id of the connected channel. - * - * @return the id of the channel - */ - String getChannelId(); - - /** - * Gets if the socket is open. - * - * @return true if the socket is open - */ - boolean isOpen(); - - /** - * Sends a message to the channel using the socket. - * - * @param msg the message to send - * @return a future to encapsulate the progress of sending the message - */ - CompletableFuture send(CharSequence msg); - - /** - * Closes the socket. - * - * @param statusCode the status code - * @param reason the reason - */ - void close(int statusCode, String reason); - } - - /** - * Socket listener - */ - interface Listener { - - default void onOpen() {} - - default void onError(Throwable error) {} - - default void onText(CharSequence data) {} - - default void onClose(int statusCode, String reason) {} - } - -} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClientImpl.java b/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClientImpl.java deleted file mode 100644 index cf1489c..0000000 --- a/spark-common/src/main/java/me/lucko/spark/common/util/ws/BytesocksClientImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package me.lucko.spark.common.util.ws; - -// Overridden by java 11 source set - -class BytesocksClientImpl implements BytesocksClient { - - BytesocksClientImpl(String host, String userAgent) { - throw new UnsupportedOperationException(); - } - - @Override - public Socket createAndConnect(Listener listener) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public Socket connect(String channelId, Listener listener) throws Exception { - throw new UnsupportedOperationException(); - } -} diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java index 5c7e08c..6a9c2b7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocket.java @@ -22,12 +22,12 @@ package me.lucko.spark.common.ws; import com.google.protobuf.ByteString; +import me.lucko.bytesocks.client.BytesocksClient; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.sampler.AbstractSampler; import me.lucko.spark.common.sampler.Sampler; import me.lucko.spark.common.sampler.window.ProfilingWindowUtils; import me.lucko.spark.common.util.MediaTypes; -import me.lucko.spark.common.util.ws.BytesocksClient; import me.lucko.spark.proto.SparkProtos; import me.lucko.spark.proto.SparkSamplerProtos; import me.lucko.spark.proto.SparkWebSocketProtos.ClientConnect; diff --git a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java index f870cb7..9079860 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java +++ b/spark-common/src/main/java/me/lucko/spark/common/ws/ViewerSocketConnection.java @@ -22,8 +22,8 @@ package me.lucko.spark.common.ws; import com.google.protobuf.ByteString; +import me.lucko.bytesocks.client.BytesocksClient; import me.lucko.spark.common.SparkPlatform; -import me.lucko.spark.common.util.ws.BytesocksClient; import me.lucko.spark.proto.SparkWebSocketProtos.PacketWrapper; import me.lucko.spark.proto.SparkWebSocketProtos.RawPacket; diff --git a/spark-common/src/main/java11/me/lucko/spark/common/util/ws/BytesocksClientImpl.java b/spark-common/src/main/java11/me/lucko/spark/common/util/ws/BytesocksClientImpl.java deleted file mode 100644 index 7adb809..0000000 --- a/spark-common/src/main/java11/me/lucko/spark/common/util/ws/BytesocksClientImpl.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package me.lucko.spark.common.util.ws; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.http.WebSocket; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -/** - * Java 11 implementation of {@link BytesocksClient}. - */ -class BytesocksClientImpl implements BytesocksClient { - - private final HttpClient client; - - /* The bytesocks urls */ - private final String httpUrl; - private final String wsUrl; - - /** The client user agent */ - private final String userAgent; - - BytesocksClientImpl(String host, String userAgent) { - this.client = HttpClient.newHttpClient(); - - this.httpUrl = "https://" + host + "/"; - this.wsUrl = "wss://" + host + "/"; - this.userAgent = userAgent; - } - - @Override - public Socket createAndConnect(Listener listener) throws Exception { - HttpRequest createRequest = HttpRequest.newBuilder() - .uri(URI.create(this.httpUrl + "create")) - .header("User-Agent", this.userAgent) - .build(); - - HttpResponse resp = this.client.send(createRequest, HttpResponse.BodyHandlers.discarding()); - if (resp.statusCode() != 201) { - throw new RuntimeException("Request failed: " + resp); - } - - String channelId = resp.headers().firstValue("Location").orElse(null); - if (channelId == null) { - throw new RuntimeException("Location header not returned: " + resp); - } - - return connect(channelId, listener); - } - - @Override - public Socket connect(String channelId, Listener listener) throws Exception { - WebSocket socket = this.client.newWebSocketBuilder() - .header("User-Agent", this.userAgent) - .connectTimeout(Duration.ofSeconds(5)) - .buildAsync(URI.create(this.wsUrl + channelId), new ListenerImpl(listener)) - .join(); - - return new SocketImpl(channelId, socket); - } - - private static final class SocketImpl implements Socket { - private final String id; - private final WebSocket ws; - - private SocketImpl(String id, WebSocket ws) { - this.id = id; - this.ws = ws; - } - - @Override - public String getChannelId() { - return this.id; - } - - @Override - public boolean isOpen() { - return !this.ws.isOutputClosed() && !this.ws.isInputClosed(); - } - - @Override - public CompletableFuture send(CharSequence msg) { - return this.ws.sendText(msg, true); - } - - @Override - public void close(int statusCode, String reason) { - this.ws.sendClose(statusCode, reason); - } - } - - private static final class ListenerImpl implements WebSocket.Listener { - private final Listener listener; - - private ListenerImpl(Listener listener) { - this.listener = listener; - } - - @Override - public void onOpen(WebSocket webSocket) { - this.listener.onOpen(); - webSocket.request(1); - } - - @Override - public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { - return CompletableFuture.runAsync(() -> this.listener.onClose(statusCode, reason)); - } - - @Override - public void onError(WebSocket webSocket, Throwable error) { - this.listener.onError(error); - } - - @Override - public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { - webSocket.request(1); - return CompletableFuture.runAsync(() -> this.listener.onText(data)); - } - } - - -} diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index 0421f68..b59ee1d 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -80,6 +80,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' // relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-forge/build.gradle b/spark-forge/build.gradle index 47c4c22..733a0f7 100644 --- a/spark-forge/build.gradle +++ b/spark-forge/build.gradle @@ -58,6 +58,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-minestom/build.gradle b/spark-minestom/build.gradle index a673106..788201b 100644 --- a/spark-minestom/build.gradle +++ b/spark-minestom/build.gradle @@ -35,6 +35,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-nukkit/build.gradle b/spark-nukkit/build.gradle index fbdf2b8..d2e4833 100644 --- a/spark-nukkit/build.gradle +++ b/spark-nukkit/build.gradle @@ -31,6 +31,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-sponge7/build.gradle b/spark-sponge7/build.gradle index b06d3bd..0610a9a 100644 --- a/spark-sponge7/build.gradle +++ b/spark-sponge7/build.gradle @@ -28,6 +28,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-sponge8/build.gradle b/spark-sponge8/build.gradle index 202c308..cbd922f 100644 --- a/spark-sponge8/build.gradle +++ b/spark-sponge8/build.gradle @@ -33,6 +33,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-velocity/build.gradle b/spark-velocity/build.gradle index 275d3df..5aa15a5 100644 --- a/spark-velocity/build.gradle +++ b/spark-velocity/build.gradle @@ -31,6 +31,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-velocity4/build.gradle b/spark-velocity4/build.gradle index 1f8e8ee..d30f28b 100644 --- a/spark-velocity4/build.gradle +++ b/spark-velocity4/build.gradle @@ -36,6 +36,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' diff --git a/spark-waterdog/build.gradle b/spark-waterdog/build.gradle index b8f5331..c4bd382 100644 --- a/spark-waterdog/build.gradle +++ b/spark-waterdog/build.gradle @@ -37,6 +37,7 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' exclude 'module-info.class' exclude 'META-INF/maven/**' -- cgit