diff --git a/core/build.gradle.kts b/core/build.gradle.kts index a9e6c66a..26178445 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation("org.apache.logging.log4j:log4j-core:2.20.0") implementation("com.google.code.gson:gson:2.10.1") implementation("io.netty:netty-all:4.1.118.Final") - implementation("org.bouncycastle:bcprov-jdk18on:1.80") +// implementation("org.bouncycastle:bcprov-jdk18on:1.80") implementation("org.bouncycastle:bcpkix-jdk18on:1.80") implementation("com.github.luben:zstd-jni:1.5.7-1") implementation("org.tomlj:tomlj:1.1.1") diff --git a/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java b/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java index 904df9ed..0a381797 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java +++ b/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java @@ -5,14 +5,13 @@ import pl.skidam.automodpack_core.config.Jsons; import pl.skidam.automodpack_core.loader.*; import pl.skidam.automodpack_core.modpack.Modpack; -import pl.skidam.protocol.netty.NettyServer; +import pl.skidam.automodpack_core.protocol.netty.NettyServer; import java.nio.file.Path; public class GlobalVariables { public static final Logger LOGGER = LogManager.getLogger("AutoModpack"); public static final String MOD_ID = "automodpack"; - public static final String SECRET_REQUEST_HEADER = "AutoModpack-Secret"; public static Boolean DEBUG = false; public static Boolean preload; public static String MC_VERSION; diff --git a/core/src/main/java/pl/skidam/automodpack_core/auth/Secrets.java b/core/src/main/java/pl/skidam/automodpack_core/auth/Secrets.java index 2ab68dfd..2bf9d9a4 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/auth/Secrets.java +++ b/core/src/main/java/pl/skidam/automodpack_core/auth/Secrets.java @@ -1,6 +1,6 @@ package pl.skidam.automodpack_core.auth; -import pl.skidam.protocol.NetUtils; +import pl.skidam.automodpack_core.protocol.NetUtils; import java.net.SocketAddress; import java.security.SecureRandom; @@ -51,7 +51,7 @@ public static boolean isSecretValid(String secretStr, SocketAddress address) { return false; String playerUuid = playerSecretPair.getKey(); - if (!GAME_CALL.canPlayerJoin(address, playerUuid)) // check if associated player is still whitelisted + if (!GAME_CALL.isPlayerAuthorized(address, playerUuid)) // check if associated player is still whitelisted return false; long secretLifetime = serverConfig.secretLifetime * 3600; // in seconds diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/GameCallService.java b/core/src/main/java/pl/skidam/automodpack_core/loader/GameCallService.java index 34ee0232..2b6863b9 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/GameCallService.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/GameCallService.java @@ -3,5 +3,5 @@ import java.net.SocketAddress; public interface GameCallService { - boolean canPlayerJoin(SocketAddress address, String id); + boolean isPlayerAuthorized(SocketAddress address, String id); } \ No newline at end of file diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/NullGameCall.java b/core/src/main/java/pl/skidam/automodpack_core/loader/NullGameCall.java index e9cb9363..d30f1673 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/NullGameCall.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/NullGameCall.java @@ -4,7 +4,7 @@ public class NullGameCall implements GameCallService { @Override - public boolean canPlayerJoin(SocketAddress address, String id) { + public boolean isPlayerAuthorized(SocketAddress address, String id) { return true; } } diff --git a/core/src/main/java/pl/skidam/protocol/DownloadClient.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java similarity index 76% rename from core/src/main/java/pl/skidam/protocol/DownloadClient.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java index b67b0ef2..51510a1d 100644 --- a/core/src/main/java/pl/skidam/protocol/DownloadClient.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol; +package pl.skidam.automodpack_core.protocol; import pl.skidam.automodpack_core.auth.Secrets; import com.github.luben.zstd.Zstd; @@ -12,14 +12,11 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Base64; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import static pl.skidam.protocol.NetUtils.*; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; /** * A DownloadClient that creates a pool of connections. @@ -124,6 +121,7 @@ public Connection(InetSocketAddress remoteAddress, Secrets.Secret secret) throws sslSocket.close(); throw new IOException("Invalid server certificate chain"); } + boolean validated = false; for (Certificate cert : certs) { if (cert instanceof X509Certificate x509Cert) { @@ -161,6 +159,7 @@ public void setBusy(boolean value) { */ public CompletableFuture sendDownloadFile(byte[] fileHash, Path destination, IntCallback chunkCallback) { return CompletableFuture.supplyAsync(() -> { + Exception exception = null; try { // Build File Request message: // [protocolVersion][FILE_REQUEST_TYPE][secret][int: fileHash.length][fileHash] @@ -177,9 +176,10 @@ public CompletableFuture sendDownloadFile(byte[] fileHash, Path destinat writeProtocolMessage(payload); return readFileResponse(destination, chunkCallback); } catch (Exception e) { + exception = e; throw new CompletionException(e); } finally { - setBusy(false); + finalBlock(exception); } }, executor); } @@ -189,6 +189,7 @@ public CompletableFuture sendDownloadFile(byte[] fileHash, Path destinat */ public CompletableFuture sendRefreshRequest(byte[][] fileHashes) { return CompletableFuture.supplyAsync(() -> { + Exception exception = null; try { // Build Refresh Request message: // [protocolVersion][REFRESH_REQUEST_TYPE][secret][int: fileHashesCount] @@ -211,13 +212,32 @@ public CompletableFuture sendRefreshRequest(byte[][] fileHashes) { writeProtocolMessage(payload); return readFileResponse(null, null); } catch (Exception e) { + exception = e; throw new CompletionException(e); } finally { - setBusy(false); + finalBlock(exception); } }, executor); } + private void finalBlock(Exception exception) { + // skip any remaining data + try { + while (in.available() > 0) { + in.skipBytes(in.available()); + } + } catch (IOException e) { + if (exception == null) { + exception = e; + throw new CompletionException(e); + } + } finally { + if (exception == null) { + setBusy(false); + } + } + } + /** * Compresses and writes a protocol message using Zstd. * Message framing: [int: compressedLength][int: originalLength][compressed payload]. @@ -251,65 +271,66 @@ private byte[] readProtocolMessageFrame() throws IOException { private Object readFileResponse(Path destination, IntCallback chunkCallback) throws IOException { // Header frame byte[] headerFrame = readProtocolMessageFrame(); - DataInputStream headerIn = new DataInputStream(new ByteArrayInputStream(headerFrame)); - byte version = headerIn.readByte(); - byte messageType = headerIn.readByte(); - if (messageType == ERROR) { - int errLen = headerIn.readInt(); - byte[] errBytes = new byte[errLen]; - headerIn.readFully(errBytes); - throw new IOException("Server error: " + new String(errBytes)); - } - if (messageType != FILE_RESPONSE_TYPE) { - throw new IOException("Unexpected message type: " + messageType); - } - long expectedFileSize = headerIn.readLong(); - - long receivedBytes = 0; - OutputStream fos = null; - List rawData = null; - if (destination != null) { - fos = new FileOutputStream(destination.toFile()); - } else { - rawData = new LinkedList<>(); - } + try (DataInputStream headerIn = new DataInputStream(new ByteArrayInputStream(headerFrame))) { + byte version = headerIn.readByte(); + byte messageType = headerIn.readByte(); + + if (messageType == ERROR) { + int errLen = headerIn.readInt(); + byte[] errBytes = new byte[errLen]; + headerIn.readFully(errBytes); + throw new IOException("Server error: " + new String(errBytes)); + } + + long receivedBytes = 0; + OutputStream fos = (destination != null) ? new FileOutputStream(destination.toFile()) : null; + List rawData = (fos == null) ? new LinkedList<>() : null; - // Read data frames until the expected file size is received. - while (receivedBytes < expectedFileSize) { - byte[] dataFrame = readProtocolMessageFrame(); - int toWrite = dataFrame.length; - if (receivedBytes + toWrite > expectedFileSize) { - toWrite = (int)(expectedFileSize - receivedBytes); + if (messageType == END_OF_TRANSMISSION) { + if (fos != null) fos.close(); + return (rawData != null) ? rawData : destination; } - if (fos != null) { - fos.write(dataFrame, 0, toWrite); - } else { - byte[] chunk = new byte[toWrite]; - System.arraycopy(dataFrame, 0, chunk, 0, toWrite); - rawData.add(chunk); + + if (messageType != FILE_RESPONSE_TYPE) { + if (fos != null) fos.close(); + throw new IOException("Unexpected message type: " + messageType); } - receivedBytes += toWrite; - if (chunkCallback != null) { - chunkCallback.run(toWrite); + long expectedFileSize = headerIn.readLong(); + + // Read data frames until the expected file size is received. + while (receivedBytes < expectedFileSize) { + byte[] dataFrame = readProtocolMessageFrame(); + int toWrite = Math.min(dataFrame.length, (int)(expectedFileSize - receivedBytes)); + + if (fos != null) { + fos.write(dataFrame, 0, toWrite); + } else { + byte[] chunk = Arrays.copyOfRange(dataFrame, 0, toWrite); + rawData.add(chunk); + } + receivedBytes += toWrite; + + if (chunkCallback != null) { + chunkCallback.run(toWrite); + } } - } - // Read EOT frame - byte[] eotFrame = readProtocolMessageFrame(); - DataInputStream eotIn = new DataInputStream(new ByteArrayInputStream(eotFrame)); - byte ver = eotIn.readByte(); - byte eotType = eotIn.readByte(); - if (ver != version || eotType != END_OF_TRANSMISSION) { - throw new IOException("Invalid end-of-transmission marker. Expected version " + version + - " and type " + END_OF_TRANSMISSION + ", got version " + ver + " and type " + eotType); - } + if (fos != null) fos.close(); + + // Read EOT frame + byte[] eotFrame = readProtocolMessageFrame(); + try (DataInputStream eotIn = new DataInputStream(new ByteArrayInputStream(eotFrame))) { + byte ver = eotIn.readByte(); + byte eotType = eotIn.readByte(); + + if (ver != version || eotType != END_OF_TRANSMISSION) { + throw new IOException("Invalid end-of-transmission marker. Expected version " + version + + " and type " + END_OF_TRANSMISSION + ", got version " + ver + " and type " + eotType); + } + } - if (fos != null) { - fos.close(); - return destination; - } else { - return rawData; + return (rawData != null) ? rawData : destination; } } diff --git a/core/src/main/java/pl/skidam/protocol/NetUtils.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java similarity index 99% rename from core/src/main/java/pl/skidam/protocol/NetUtils.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java index 9e97badb..7d109e62 100644 --- a/core/src/main/java/pl/skidam/protocol/NetUtils.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol; +package pl.skidam.automodpack_core.protocol; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; diff --git a/core/src/main/java/pl/skidam/protocol/netty/NettyServer.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/NettyServer.java similarity index 97% rename from core/src/main/java/pl/skidam/protocol/netty/NettyServer.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/NettyServer.java index 59348d4f..31ffd6e2 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/NettyServer.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/NettyServer.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty; +package pl.skidam.automodpack_core.protocol.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; @@ -12,8 +12,8 @@ import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import pl.skidam.automodpack_core.config.ConfigTools; -import pl.skidam.protocol.NetUtils; -import pl.skidam.protocol.netty.handler.ProtocolServerHandler; +import pl.skidam.automodpack_core.protocol.NetUtils; +import pl.skidam.automodpack_core.protocol.netty.handler.ProtocolServerHandler; import pl.skidam.automodpack_core.utils.CustomThreadFactoryBuilder; import pl.skidam.automodpack_core.utils.Ip; import pl.skidam.automodpack_core.utils.ObservableMap; diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageDecoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java similarity index 82% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageDecoder.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java index c8fdbe63..1a3a7274 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageDecoder.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java @@ -1,17 +1,17 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; -import pl.skidam.protocol.NetUtils; -import pl.skidam.protocol.netty.message.EchoMessage; -import pl.skidam.protocol.netty.message.FileRequestMessage; -import pl.skidam.protocol.netty.message.FileResponseMessage; -import pl.skidam.protocol.netty.message.RefreshRequestMessage; +import pl.skidam.automodpack_core.protocol.NetUtils; +import pl.skidam.automodpack_core.protocol.netty.message.EchoMessage; +import pl.skidam.automodpack_core.protocol.netty.message.FileRequestMessage; +import pl.skidam.automodpack_core.protocol.netty.message.FileResponseMessage; +import pl.skidam.automodpack_core.protocol.netty.message.RefreshRequestMessage; import java.util.List; -import static pl.skidam.protocol.NetUtils.*; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; public class ProtocolMessageDecoder extends ByteToMessageDecoder { @Override diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageEncoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java similarity index 91% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageEncoder.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java index b05cf0b4..2bb5ea08 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolMessageEncoder.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java @@ -1,11 +1,11 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import pl.skidam.protocol.netty.message.*; +import pl.skidam.automodpack_core.protocol.netty.message.*; -import static pl.skidam.protocol.NetUtils.*; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; public class ProtocolMessageEncoder extends MessageToByteEncoder { @Override diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolServerHandler.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolServerHandler.java similarity index 94% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolServerHandler.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolServerHandler.java index e915362f..d563f9ea 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolServerHandler.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolServerHandler.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -8,7 +8,7 @@ import java.util.List; -import static pl.skidam.protocol.NetUtils.*; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; public class ProtocolServerHandler extends ByteToMessageDecoder { diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ServerMessageHandler.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java similarity index 84% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ServerMessageHandler.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java index 506c857a..539c2571 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ServerMessageHandler.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -10,12 +10,11 @@ import pl.skidam.automodpack_core.GlobalVariables; import pl.skidam.automodpack_core.auth.Secrets; import pl.skidam.automodpack_core.modpack.ModpackContent; -import pl.skidam.protocol.netty.message.EchoMessage; -import pl.skidam.protocol.netty.message.FileRequestMessage; -import pl.skidam.protocol.netty.message.ProtocolMessage; -import pl.skidam.protocol.netty.message.RefreshRequestMessage; +import pl.skidam.automodpack_core.protocol.netty.message.EchoMessage; +import pl.skidam.automodpack_core.protocol.netty.message.FileRequestMessage; +import pl.skidam.automodpack_core.protocol.netty.message.ProtocolMessage; +import pl.skidam.automodpack_core.protocol.netty.message.RefreshRequestMessage; -import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.SocketAddress; @@ -25,7 +24,7 @@ import java.util.concurrent.CompletableFuture; import static pl.skidam.automodpack_core.GlobalVariables.*; -import static pl.skidam.protocol.NetUtils.*; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; public class ServerMessageHandler extends SimpleChannelInboundHandler { @@ -72,7 +71,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } - private void refreshModpackFiles(ChannelHandlerContext context, byte[][] FileHashesList) { + private void refreshModpackFiles(ChannelHandlerContext context, byte[][] FileHashesList) throws IOException { List hashes = new ArrayList<>(); for (byte[] hash : FileHashesList) { hashes.add(new String(hash)); @@ -118,7 +117,7 @@ private boolean validateSecret(SocketAddress address, byte[] secret) { return Secrets.isSecretValid(decodedSecret, address); } - private void sendFile(ChannelHandlerContext ctx, byte[] bsha1) { + private void sendFile(ChannelHandlerContext ctx, byte[] bsha1) throws IOException { final String sha1 = new String(bsha1, CharsetUtil.UTF_8); final Optional optionalPath = resolvePath(sha1); @@ -127,26 +126,26 @@ private void sendFile(ChannelHandlerContext ctx, byte[] bsha1) { return; } - final File file = optionalPath.get().toFile(); + final Path path = optionalPath.get(); + final long fileSize = Files.size(path); // Send file response header: version, FILE_RESPONSE type, then file size (8 bytes) ByteBuf responseHeader = Unpooled.buffer(1 + 1 + 8); responseHeader.writeByte(clientProtocolVersion); responseHeader.writeByte(FILE_RESPONSE_TYPE); - responseHeader.writeLong(file.length()); + responseHeader.writeLong(fileSize); ctx.writeAndFlush(responseHeader); + if (fileSize == 0) { + sendEOT(ctx); + return; + } + // Stream the file using ChunkedFile (chunk size set to 131072 bytes = 128 KB) - suitable value for zstd try { - RandomAccessFile raf = new RandomAccessFile(file, "r"); + RandomAccessFile raf = new RandomAccessFile(path.toFile(), "r"); ChunkedFile chunkedFile = new ChunkedFile(raf, 0, raf.length(), 131072); - ctx.writeAndFlush(chunkedFile).addListener((ChannelFutureListener) future -> { - // After the file is sent, send an End-of-Transmission message. - ByteBuf eot = Unpooled.buffer(2); - eot.writeByte((byte) 1); - eot.writeByte(END_OF_TRANSMISSION); - ctx.writeAndFlush(eot); - }); + ctx.writeAndFlush(chunkedFile).addListener((ChannelFutureListener) future -> sendEOT(ctx)); } catch (IOException e) { sendError(ctx, (byte) 1, "File transfer error: " + e.getMessage()); } @@ -170,4 +169,11 @@ private void sendError(ChannelHandlerContext ctx, byte version, String errorMess ctx.writeAndFlush(errorBuf); ctx.channel().close(); } + + private void sendEOT(ChannelHandlerContext ctx) { + ByteBuf eot = Unpooled.buffer(2); + eot.writeByte((byte) 1); + eot.writeByte(END_OF_TRANSMISSION); + ctx.writeAndFlush(eot); + } } diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ZstdDecoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdDecoder.java similarity index 95% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ZstdDecoder.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdDecoder.java index 72d7806e..a5f33778 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ZstdDecoder.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdDecoder.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import com.github.luben.zstd.Zstd; import io.netty.buffer.ByteBuf; diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ZstdEncoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdEncoder.java similarity index 93% rename from core/src/main/java/pl/skidam/protocol/netty/handler/ZstdEncoder.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdEncoder.java index f8181a7a..4a3f5c47 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ZstdEncoder.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ZstdEncoder.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty.handler; +package pl.skidam.automodpack_core.protocol.netty.handler; import com.github.luben.zstd.Zstd; import io.netty.buffer.ByteBuf; diff --git a/core/src/main/java/pl/skidam/protocol/netty/message/EchoMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java similarity index 76% rename from core/src/main/java/pl/skidam/protocol/netty/message/EchoMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java index 52c410b6..e13a0110 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/message/EchoMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java @@ -1,6 +1,6 @@ -package pl.skidam.protocol.netty.message; +package pl.skidam.automodpack_core.protocol.netty.message; -import static pl.skidam.protocol.NetUtils.ECHO_TYPE; +import static pl.skidam.automodpack_core.protocol.NetUtils.ECHO_TYPE; public class EchoMessage extends ProtocolMessage { private final int dataLength; diff --git a/core/src/main/java/pl/skidam/protocol/netty/message/FileRequestMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileRequestMessage.java similarity index 78% rename from core/src/main/java/pl/skidam/protocol/netty/message/FileRequestMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileRequestMessage.java index 69cf3f82..82d1ef27 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/message/FileRequestMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileRequestMessage.java @@ -1,6 +1,6 @@ -package pl.skidam.protocol.netty.message; +package pl.skidam.automodpack_core.protocol.netty.message; -import static pl.skidam.protocol.NetUtils.FILE_REQUEST_TYPE; +import static pl.skidam.automodpack_core.protocol.NetUtils.FILE_REQUEST_TYPE; public class FileRequestMessage extends ProtocolMessage { private final int fileHashLength; diff --git a/core/src/main/java/pl/skidam/protocol/netty/message/FileResponseMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileResponseMessage.java similarity index 76% rename from core/src/main/java/pl/skidam/protocol/netty/message/FileResponseMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileResponseMessage.java index d33ddecf..eb568d34 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/message/FileResponseMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/FileResponseMessage.java @@ -1,6 +1,6 @@ -package pl.skidam.protocol.netty.message; +package pl.skidam.automodpack_core.protocol.netty.message; -import static pl.skidam.protocol.NetUtils.FILE_RESPONSE_TYPE; +import static pl.skidam.automodpack_core.protocol.NetUtils.FILE_RESPONSE_TYPE; public class FileResponseMessage extends ProtocolMessage { private final int dataLength; diff --git a/core/src/main/java/pl/skidam/protocol/netty/message/ProtocolMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/ProtocolMessage.java similarity index 91% rename from core/src/main/java/pl/skidam/protocol/netty/message/ProtocolMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/ProtocolMessage.java index 1385a6f4..69130883 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/message/ProtocolMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/ProtocolMessage.java @@ -1,4 +1,4 @@ -package pl.skidam.protocol.netty.message; +package pl.skidam.automodpack_core.protocol.netty.message; public abstract class ProtocolMessage { private final byte version; // 1 byte diff --git a/core/src/main/java/pl/skidam/protocol/netty/message/RefreshRequestMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/RefreshRequestMessage.java similarity index 83% rename from core/src/main/java/pl/skidam/protocol/netty/message/RefreshRequestMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/RefreshRequestMessage.java index 7aec6287..173efa2d 100644 --- a/core/src/main/java/pl/skidam/protocol/netty/message/RefreshRequestMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/RefreshRequestMessage.java @@ -1,6 +1,6 @@ -package pl.skidam.protocol.netty.message; +package pl.skidam.automodpack_core.protocol.netty.message; -import static pl.skidam.protocol.NetUtils.REFRESH_REQUEST_TYPE; +import static pl.skidam.automodpack_core.protocol.NetUtils.REFRESH_REQUEST_TYPE; public class RefreshRequestMessage extends ProtocolMessage { private final int fileHashesCount; diff --git a/core/src/main/java/pl/skidam/protocol/netty/client/DownloadClient.java b/core/src/main/java/pl/skidam/protocol/netty/client/DownloadClient.java deleted file mode 100644 index 32bf8422..00000000 --- a/core/src/main/java/pl/skidam/protocol/netty/client/DownloadClient.java +++ /dev/null @@ -1,183 +0,0 @@ -package pl.skidam.protocol.netty.client; - -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.channel.*; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SslProvider; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import io.netty.handler.stream.ChunkedWriteHandler; -import pl.skidam.automodpack_core.auth.Secrets; -import pl.skidam.protocol.netty.handler.*; -import pl.skidam.protocol.netty.message.FileRequestMessage; -import pl.skidam.protocol.netty.message.RefreshRequestMessage; - -import javax.net.ssl.SSLException; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -import static pl.skidam.automodpack_core.GlobalVariables.MOD_ID; -import static pl.skidam.protocol.NetUtils.MAGIC_AMMC; - -public class DownloadClient extends NettyClient { - private final Map channels = new HashMap<>(); // channel, isBusy - private final EventLoopGroup group; - private final SslContext sslCtx; - private final Secrets.Secret secret; - private final DownloadClient downloadClient; - private final Semaphore channelLock = new Semaphore(0); - - public DownloadClient(InetSocketAddress remoteAddress, Secrets.Secret secret, int poolSize) throws InterruptedException, SSLException { - this.downloadClient = this; - this.secret = secret; - - // Yes, we use the insecure because server uses self-signed cert and we have different way to verify the authenticity - // Via secret and fingerprint, so the encryption strength should be the same, correct me if I'm wrong, thanks - sslCtx = SslContextBuilder.forClient() - .trustManager(InsecureTrustManagerFactory.INSTANCE) - .sslProvider(SslProvider.JDK) - .protocols("TLSv1.3") - .ciphers(Arrays.asList( - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256")) - .build(); - - group = new NioEventLoopGroup(); - Bootstrap bootstrap = new Bootstrap(); - bootstrap.group(group) - .channel(NioSocketChannel.class) - .option(ChannelOption.SO_KEEPALIVE, true) - .handler(new ChannelInitializer<>() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().addLast(MOD_ID, new ProtocolClientHandler(downloadClient)); - } - }); - - // Initialize channels and wait for the channels in pool. - for (int i = 0; i < poolSize; i++) { - Channel channel = bootstrap.connect(remoteAddress).sync().channel(); - ByteBuf msg = channel.alloc().buffer(4); - msg.writeInt(MAGIC_AMMC); - channel.writeAndFlush(msg); - } - - channelLock.acquire(poolSize); - } - - @Override - public void secureInit(ChannelHandlerContext ctx) { - ctx.pipeline().addLast("zstd-encoder", new ZstdEncoder()); - ctx.pipeline().addLast("zstd-decoder", new ZstdDecoder()); - ctx.pipeline().addLast("chunked-write", new ChunkedWriteHandler()); - ctx.pipeline().addLast("protocol-msg-decoder", new ProtocolMessageEncoder()); - } - - @Override - public void addChannel(Channel channel) { - channels.put(channel, new AtomicBoolean(false)); - } - - @Override - public void removeChannel(Channel channel) { - channels.remove(channel); - } - - @Override - public void releaseChannel() { - channelLock.release(); - } - - @Override - public Secrets.Secret getSecret() { - return secret; - } - - /** - * Downloads a file by its SHA-1 hash to the specified destination. - * Returns a CompletableFuture that completes when the download finishes. - */ - public CompletableFuture downloadFile(byte[] fileHash, Path destination) { - // Select first not busy channel - Channel channel = channels.entrySet().stream() - .filter(entry -> !entry.getValue().get()) - .findFirst() - .map(Map.Entry::getKey) - .orElseThrow(() -> new IllegalStateException("No available channels")); - - // Mark channel as busy - channels.get(channel).set(true); - - // Add a new FileDownloadHandler to process this download. - FileDownloadHandler downloadHandler = new FileDownloadHandler(destination); - channel.pipeline().addLast("download-handler", downloadHandler); - - byte[] bsecret = Base64.getUrlDecoder().decode(secret.secret()); - - // Build and send the file request (which carries the secret and file hash). - FileRequestMessage request = new FileRequestMessage((byte) 1, bsecret, fileHash); - channel.writeAndFlush(request); - - // Return the future that will complete when the download finishes. - return downloadHandler.getDownloadFuture().whenComplete((result, throwable) -> { - // Mark channel as not busy - channels.get(channel).set(false); - }); - } - - /** - * Downloads a file by its SHA-1 hash to the specified destination. - * Returns a CompletableFuture that completes when the download finishes. - */ - public CompletableFuture requestRefresh(byte[][] fileHashes) { - // Select first not busy channel - Channel channel = channels.entrySet().stream() - .filter(entry -> !entry.getValue().get()) - .findFirst() - .map(Map.Entry::getKey) - .orElseThrow(() -> new IllegalStateException("No available channels")); - - // Mark channel as busy - channels.get(channel).set(true); - - // Add a new FileDownloadHandler to process this download. - FileDownloadHandler downloadHandler = new FileDownloadHandler(null); - channel.pipeline().addLast("download-handler", downloadHandler); - - byte[] bsecret = Base64.getUrlDecoder().decode(secret.secret()); - - // Build and send the file request (which carries the secret and file hash). - RefreshRequestMessage request = new RefreshRequestMessage((byte) 1, bsecret, fileHashes); - channel.writeAndFlush(request); - - // Return the future that will complete when the download finishes. - return downloadHandler.getDownloadFuture().whenComplete((result, throwable) -> { - // Mark channel as not busy - channels.get(channel).set(false); - }); - } - - /** - * Closes all channels in the pool and shuts down the event loop. - */ - public void close() { - for (Channel channel : channels.keySet()) { - if (channel.isOpen()) { - channel.close(); - } - } - group.shutdownGracefully(); - } - - public SslContext getSslCtx() { - return sslCtx; - } -} diff --git a/core/src/main/java/pl/skidam/protocol/netty/client/EchoClient.java b/core/src/main/java/pl/skidam/protocol/netty/client/EchoClient.java deleted file mode 100644 index 151913f5..00000000 --- a/core/src/main/java/pl/skidam/protocol/netty/client/EchoClient.java +++ /dev/null @@ -1,125 +0,0 @@ -package pl.skidam.protocol.netty.client; - -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.channel.*; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SslProvider; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import pl.skidam.automodpack_core.auth.Secrets; -import pl.skidam.protocol.netty.handler.ProtocolClientHandler; -import pl.skidam.protocol.netty.handler.ProtocolMessageEncoder; -import pl.skidam.protocol.netty.message.EchoMessage; - -import javax.net.ssl.SSLException; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Semaphore; - -import static pl.skidam.protocol.NetUtils.MAGIC_AMMC; - -public class EchoClient extends NettyClient { - private final List channels = new ArrayList<>(); - private final EventLoopGroup group; - private final SslContext sslCtx; - private final EchoClient echoClient; - private final Semaphore channelLock = new Semaphore(0); - - public EchoClient(InetSocketAddress remoteAddress) throws InterruptedException, SSLException { - this.echoClient = this; - - // Yes, we use the insecure because server uses self-signed cert and we have different way to verify the authenticity - // Via secret and fingerprint, so the encryption strength should be the same, correct me if I'm wrong, thanks - sslCtx = SslContextBuilder.forClient() - .trustManager(InsecureTrustManagerFactory.INSTANCE) - .sslProvider(SslProvider.JDK) - .protocols("TLSv1.3") - .ciphers(Arrays.asList( - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256")) - .build(); - - group = new NioEventLoopGroup(); - Bootstrap bootstrap = new Bootstrap(); - bootstrap.group(group) - .channel(NioSocketChannel.class) - .option(ChannelOption.SO_KEEPALIVE, true) - .handler(new ChannelInitializer<>() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().addLast(new ProtocolClientHandler(echoClient)); - } - }); - - // Initialize channels and wait for the channels in pool. - Channel channel = bootstrap.connect(remoteAddress).sync().channel(); - ByteBuf msg = channel.alloc().buffer(4); - msg.writeInt(MAGIC_AMMC); - channel.writeAndFlush(msg); - - channelLock.acquire(); - } - - @Override - public void secureInit(ChannelHandlerContext ctx) { - ctx.pipeline().addLast(new ProtocolMessageEncoder()); - } - - @Override - public void addChannel(Channel channel) { - channels.add(channel); - } - - @Override - public void removeChannel(Channel channel) { - channels.remove(channel); - } - - @Override - public void releaseChannel() { - channelLock.release(); - } - - @Override - public Secrets.Secret getSecret() { - return null; - } - - /** - * Downloads a file by its SHA-1 hash to the specified destination. - * Returns a CompletableFuture that completes when the download finishes. - */ - public CompletableFuture sendEcho(byte[] secret, byte[] data) { - Channel channel = channels.get(0); - - // Build and send the file request (which carries the secret and file hash). - EchoMessage request = new EchoMessage((byte) 1, secret, data); - channel.writeAndFlush(request); - - // Return the future that will complete when the download finishes. - return null; - } - - /** - * Closes all channels in the pool and shuts down the event loop. - */ - public void close() { - for (Channel channel : channels) { - if (channel.isOpen()) { - channel.close(); - } - } - group.shutdownGracefully(); - } - - public SslContext getSslCtx() { - return sslCtx; - } -} diff --git a/core/src/main/java/pl/skidam/protocol/netty/client/NettyClient.java b/core/src/main/java/pl/skidam/protocol/netty/client/NettyClient.java deleted file mode 100644 index 2b2cadd0..00000000 --- a/core/src/main/java/pl/skidam/protocol/netty/client/NettyClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package pl.skidam.protocol.netty.client; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.ssl.SslContext; -import pl.skidam.automodpack_core.auth.Secrets; - -public abstract class NettyClient { - public abstract SslContext getSslCtx(); - public abstract void secureInit(ChannelHandlerContext ctx); - public abstract void addChannel(Channel channel); - public abstract void removeChannel(Channel channel); - public abstract void releaseChannel(); - public abstract Secrets.Secret getSecret(); -} diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/FileDownloadHandler.java b/core/src/main/java/pl/skidam/protocol/netty/handler/FileDownloadHandler.java deleted file mode 100644 index e12a6acb..00000000 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/FileDownloadHandler.java +++ /dev/null @@ -1,154 +0,0 @@ -package pl.skidam.protocol.netty.handler; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.util.ReferenceCountUtil; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Path; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static pl.skidam.automodpack_core.GlobalVariables.LOGGER; -import static pl.skidam.protocol.NetUtils.*; - -public class FileDownloadHandler extends ChannelInboundHandlerAdapter { - - private enum State { - WAITING_HEADER, - RECEIVING_FILE, - WAITING_EOT, - COMPLETED, - ERROR - } - - private State state = State.WAITING_HEADER; - private long expectedFileSize; - private long receivedBytes = 0; - private final Path destination; - private FileOutputStream fos; - private List rawFileData; - private final CompletableFuture downloadFuture = new CompletableFuture<>(); - private byte protocolVersion = 0; - - public FileDownloadHandler(Path destination) { - this.destination = destination; - } - - public CompletableFuture getDownloadFuture() { - return downloadFuture; - } - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - if (destination != null) { - fos = new FileOutputStream(destination.toFile()); - } else { - rawFileData = new LinkedList<>(); - } - } - - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - if (fos != null) { - fos.close(); - } - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (!(msg instanceof ByteBuf buf)) { - LOGGER.warn("Received non-ByteBuf message: {}", msg); - ctx.fireChannelRead(msg); - return; - } - - try { - // State machine to process the response - if (state == State.WAITING_HEADER) { - if (buf.readableBytes() < 10) { - return; - } - - protocolVersion = buf.readByte(); - byte type = buf.readByte(); - if (type == ERROR) { - int errLen = buf.readInt(); - byte[] errBytes = new byte[errLen]; - buf.readBytes(errBytes); - downloadFuture.completeExceptionally(new IOException("Server error: " + new String(errBytes))); - state = State.ERROR; - return; - } - if (type != FILE_RESPONSE_TYPE) { - downloadFuture.completeExceptionally(new IOException("Unexpected message type: " + type)); - state = State.ERROR; - return; - } - expectedFileSize = buf.readLong(); - state = State.RECEIVING_FILE; - } else if (state == State.RECEIVING_FILE) { - // In RECEIVING_FILE state, we write raw file data. - int readable = buf.readableBytes(); - long remaining = expectedFileSize - receivedBytes; - if (readable <= remaining) { - byte[] data = new byte[readable]; - buf.readBytes(data); - if (fos != null) { - fos.write(data); - } else { - rawFileData.add(data); - } - receivedBytes += readable; - } else { - // Read only the bytes that belong to the file. - byte[] data = new byte[(int) remaining]; - buf.readBytes(data); - if (fos != null) { - fos.write(data); - } else { - rawFileData.add(data); - } - receivedBytes += remaining; - state = State.WAITING_EOT; - } - if (receivedBytes == expectedFileSize) { - state = State.WAITING_EOT; - } - } else if (state == State.WAITING_EOT) { - if (buf.readableBytes() < 2) { - return; - } - - byte ver = buf.readByte(); - if (ver != protocolVersion) { - downloadFuture.completeExceptionally(new IOException("Expected protocol version: " + protocolVersion + ", got: " + ver)); - state = State.ERROR; - return; - } - byte type = buf.readByte(); - if (type != END_OF_TRANSMISSION) { - downloadFuture.completeExceptionally(new IOException("Expected EOT, got type: " + type)); - state = State.ERROR; - return; - } - state = State.COMPLETED; - Object result = destination != null ? destination : rawFileData; - downloadFuture.complete(result); - // Remove this handler now that download is complete. - ctx.pipeline().remove(this); - } - } finally { - ReferenceCountUtil.release(buf); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - downloadFuture.completeExceptionally(cause); - ctx.close(); - } -} diff --git a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolClientHandler.java b/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolClientHandler.java deleted file mode 100644 index 40063f77..00000000 --- a/core/src/main/java/pl/skidam/protocol/netty/handler/ProtocolClientHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -package pl.skidam.protocol.netty.handler; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.ssl.SslHandler; -import pl.skidam.protocol.NetUtils; -import pl.skidam.protocol.netty.client.NettyClient; - -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.List; - -import static pl.skidam.protocol.NetUtils.MAGIC_AMOK; - -public class ProtocolClientHandler extends ByteToMessageDecoder { - - private final NettyClient client; - - public ProtocolClientHandler(NettyClient client) { - this.client = client; - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - try { - if (in.readableBytes() < 4) { - return; - } - - int magic = in.getInt(0); - if (magic != MAGIC_AMOK) { - client.releaseChannel(); - } else { - // Consume the packet - in.skipBytes(in.readableBytes()); - - // Set up the pipeline for the protocol - SslHandler sslHandler = client.getSslCtx().newHandler(ctx.alloc()); - ctx.pipeline().addLast("tls", sslHandler); - - // Wait for SSL handshake to complete before sending data - sslHandler.handshakeFuture().addListener(future -> { - if (!future.isSuccess()) { - ctx.close(); - System.err.println("SSL handshake failed"); - return; - } - - try { - Certificate[] certs = sslHandler.engine().getSession().getPeerCertificates(); - if (certs == null || certs.length == 0 || certs.length > 3) { - return; - } - - for (Certificate cert : certs) { - if (cert instanceof X509Certificate x509Cert) { - String fingerprint = NetUtils.getFingerprint(x509Cert, client.getSecret().secret()); - if (fingerprint.equals(client.getSecret().fingerprint())) { - System.out.println("Server certificate verified, fingerprint: " + fingerprint); - client.secureInit(ctx); - break; - } - } - } - } catch (Exception e) { - e.printStackTrace(); - ctx.close(); - } finally { - if (ctx.channel().isOpen()) { - client.addChannel(ctx.channel()); - } - client.releaseChannel(); - } - }); - } - - ctx.pipeline().remove(this); // Always remove this handler after processing - } catch (Exception e) { - e.printStackTrace(); - ctx.close(); - } - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - client.removeChannel(ctx.channel()); - client.releaseChannel(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - cause.printStackTrace(); - ctx.close(); - } -} \ No newline at end of file diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java index 04fcac7b..ea1990b0 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java @@ -3,7 +3,7 @@ import pl.skidam.automodpack_core.auth.Secrets; import pl.skidam.automodpack_core.config.Jsons; import pl.skidam.automodpack_core.config.ConfigTools; -import pl.skidam.protocol.DownloadClient; +import pl.skidam.automodpack_core.protocol.DownloadClient; import pl.skidam.automodpack_core.utils.*; import pl.skidam.automodpack_loader_core.ReLauncher; import pl.skidam.automodpack_loader_core.screen.ScreenManager; @@ -219,7 +219,7 @@ public void startUpdate() { newDownloadedFiles.clear(); int wholeQueue = serverModpackContent.list.size(); - LOGGER.info("In queue left {} files to download ({}kb)", wholeQueue, totalBytesToDownload / 1024); + LOGGER.info("In queue left {} files to download ({}MB)", wholeQueue, totalBytesToDownload / 1024 / 1024); downloadManager = new DownloadManager(totalBytesToDownload); new ScreenManager().download(downloadManager, getModpackName()); diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java index 6758869f..59bb0254 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java @@ -6,14 +6,13 @@ import pl.skidam.automodpack_core.auth.Secrets; import pl.skidam.automodpack_core.config.ConfigTools; import pl.skidam.automodpack_core.config.Jsons; -import pl.skidam.protocol.DownloadClient; +import pl.skidam.automodpack_core.protocol.DownloadClient; import pl.skidam.automodpack_core.utils.CustomFileUtils; import pl.skidam.automodpack_core.utils.FileInspection; import pl.skidam.automodpack_core.utils.ModpackContentTools; import java.io.*; import java.net.*; -import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; @@ -462,66 +461,6 @@ public static Optional refreshServerModpackContent(I return Optional.empty(); } - - public static Optional refreshServerModpackContent(String link, Secrets.Secret secret, String body) { - // send custom http body request to get modpack content, rest the same as getServerModpackContent - if (link == null || body == null) { - throw new IllegalArgumentException("Link or body is null"); - } - - HttpURLConnection connection = null; - - try { - connection = (HttpURLConnection) new URL(link + "refresh").openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty(SECRET_REQUEST_HEADER, secret.secret()); - - return connectionToModpack(connection, body); - } catch (Exception e) { - LOGGER.error("Error while getting server modpack content", e); - } finally { - if (connection != null) { - connection.disconnect(); - } - } - - return Optional.empty(); - } - - public static Optional connectionToModpack(HttpURLConnection connection) { - return connectionToModpack(connection, null); - } - - public static Optional connectionToModpack(HttpURLConnection connection, String body) { - int responseCode = -1; - try { - connection.setConnectTimeout(10000); - connection.setReadTimeout(10000); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "github/skidamek/automodpack/" + AM_VERSION); - if (body != null) { - connection.setDoOutput(true); - connection.getOutputStream().write(body.getBytes(StandardCharsets.UTF_8)); - } - connection.connect(); - - responseCode = connection.getResponseCode(); - - if (responseCode == 200) { - return parseStreamToModpack(connection.getInputStream()); - } else { - LOGGER.error("Couldn't connect to modpack server: {} Response Code: {}", connection.getURL(), responseCode); - } - - } catch (SocketException | SocketTimeoutException e) { - LOGGER.error("Couldn't connect to modpack server: {} Response Code: {} Error: {}", connection.getURL(), responseCode, e.getCause()); - } catch (Exception e) { - LOGGER.error("Error while getting server modpack content", e); - } - - return Optional.empty(); - } - public static Optional parseStreamToModpack(List rawBytes) { String response = null; @@ -569,44 +508,6 @@ public static Optional parseStreamToModpack(List parseStreamToModpack(InputStream stream) { - - String response = null; - - try (InputStreamReader isr = new InputStreamReader(stream)) { - JsonElement element = JsonParser.parseReader(isr); // Needed to parse by deprecated method because of older minecraft versions (<1.17.1) - if (element != null && !element.isJsonArray()) { - JsonObject obj = element.getAsJsonObject(); - response = obj.toString(); - } - } catch (Exception e) { - LOGGER.error("Couldn't parse modpack content", e); - } - - if (response == null) { - LOGGER.error("Couldn't parse modpack content"); - return Optional.empty(); - } - - Jsons.ModpackContentFields serverModpackContent = GSON.fromJson(response, Jsons.ModpackContentFields.class); - - if (serverModpackContent == null) { - LOGGER.error("Couldn't parse modpack content"); - return Optional.empty(); - } - - if (serverModpackContent.list.isEmpty()) { - LOGGER.error("Modpack content is empty!"); - return Optional.empty(); - } - - if (potentiallyMalicious(serverModpackContent)) { - return Optional.empty(); - } - - return Optional.of(serverModpackContent); - } - // check if modpackContent is valid/isn't malicious public static boolean potentiallyMalicious(Jsons.ModpackContentFields serverModpackContent) { String modpackName = serverModpackContent.modpackName; diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/utils/DownloadManager.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/utils/DownloadManager.java index 84b31f9a..062cb0da 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/utils/DownloadManager.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/utils/DownloadManager.java @@ -3,7 +3,7 @@ import pl.skidam.automodpack_core.utils.CustomFileUtils; import pl.skidam.automodpack_core.utils.CustomThreadFactoryBuilder; import pl.skidam.automodpack_core.utils.FileInspection; -import pl.skidam.protocol.DownloadClient; +import pl.skidam.automodpack_core.protocol.DownloadClient; import java.io.*; import java.net.*; @@ -70,9 +70,9 @@ private void downloadTask(FileInspection.HashPathPair hashPathPair, QueuedDownlo } catch (InterruptedException e) { interrupted = true; } catch (SocketTimeoutException e) { - LOGGER.warn("Timeout - {} - {} - {}", queuedDownload.file, e, e.getStackTrace()); + LOGGER.warn("Timeout - {} - {} - {}", queuedDownload.file, e, e.fillInStackTrace()); } catch (Exception e) { - LOGGER.warn("Error while downloading file - {} - {} - {}", queuedDownload.file, e, e.getStackTrace()); + LOGGER.warn("Error while downloading file - {} - {} - {}", queuedDownload.file, e, e.fillInStackTrace()); } finally { synchronized (downloadsInProgress) { downloadsInProgress.remove(hashPathPair); @@ -141,7 +141,7 @@ private synchronized void downloadNext() { } } - private void hostDownloadFile(FileInspection.HashPathPair hashPathPair, QueuedDownload queuedDownload) throws IOException, InterruptedException, ExecutionException { + private void hostDownloadFile(FileInspection.HashPathPair hashPathPair, QueuedDownload queuedDownload) throws IOException, InterruptedException { Path outFile = queuedDownload.file; if (Files.exists(outFile)) { @@ -152,15 +152,7 @@ private void hostDownloadFile(FileInspection.HashPathPair hashPathPair, QueuedDo } } - if (outFile.getParent() != null) { - Files.createDirectories(outFile.getParent()); - } - - if (!Files.exists(outFile)) { - // Windows? #302 - outFile.toFile().createNewFile(); -// Files.createFile(outFile); - } + CustomFileUtils.setupFilePaths(outFile); var future = downloadClient.downloadFile(hashPathPair.hash().getBytes(StandardCharsets.UTF_8), outFile, (bytes) -> { bytesDownloaded += bytes; @@ -181,15 +173,7 @@ private void httpDownloadFile(String url, FileInspection.HashPathPair hashPathPa } } - if (outFile.getParent() != null) { - Files.createDirectories(outFile.getParent()); - } - - if (!Files.exists(outFile)) { - // Windows? #302 - outFile.toFile().createNewFile(); -// Files.createFile(outFile); - } + CustomFileUtils.setupFilePaths(outFile); URLConnection connection = getHttpConnection(url); diff --git a/loader/loader-fabric-core.gradle.kts b/loader/loader-fabric-core.gradle.kts index 8343deea..25ef205c 100644 --- a/loader/loader-fabric-core.gradle.kts +++ b/loader/loader-fabric-core.gradle.kts @@ -25,7 +25,7 @@ dependencies { compileOnly("com.google.code.gson:gson:2.10.1") compileOnly("org.apache.logging.log4j:log4j-core:2.20.0") implementation("org.tomlj:tomlj:1.1.1") - implementation("org.bouncycastle:bcprov-jdk18on:1.80") +// implementation("org.bouncycastle:bcprov-jdk18on:1.80") implementation("org.bouncycastle:bcpkix-jdk18on:1.80") implementation("com.github.luben:zstd-jni:1.5.7-1") @@ -42,7 +42,6 @@ configurations { // TODO: make it less messy tasks.named("shadowJar") { archiveClassifier.set("") - mergeServiceFiles() from(project(":core").sourceSets.main.get().output) from(project(":loader-core").sourceSets.main.get().output) @@ -53,9 +52,12 @@ tasks.named("shadowJar") { // Include the tomlj dependency in the shadow jar configurations = listOf(project.configurations.getByName("shadowImplementation")) - relocate("org.antlr.v4", "reloc.org.antlr.v4") - relocate("org.tomlj", "reloc.org.tomlj") - relocate("org.checkerframework", "reloc.org.checkerframework") + val reloc = "am_libs" + relocate("org.antlr", "${reloc}.org.antlr") + relocate("org.tomlj", "${reloc}.org.tomlj") + relocate("org.checkerframework", "${reloc}.org.checkerframework") + relocate("com.github.luben", "${reloc}.com.github.luben") + relocate("org.bouncycastle", "${reloc}.org.bouncycastle") relocate("pl.skidam.automodpack_loader_core_fabric", "pl.skidam.automodpack_loader_core") relocate("pl.skidam.automodpack_loader_master_core_fabric", "pl.skidam.automodpack_loader_core") @@ -70,6 +72,8 @@ tasks.named("shadowJar") { manifest { attributes["AutoModpack-Version"] = version } + + mergeServiceFiles() } java { diff --git a/loader/loader-forge.gradle.kts b/loader/loader-forge.gradle.kts index 58615de2..7fa43208 100644 --- a/loader/loader-forge.gradle.kts +++ b/loader/loader-forge.gradle.kts @@ -37,7 +37,7 @@ dependencies { compileOnly("com.google.code.gson:gson:2.10.1") compileOnly("org.apache.logging.log4j:log4j-core:2.20.0") implementation("org.tomlj:tomlj:1.1.1") - implementation("org.bouncycastle:bcprov-jdk18on:1.80") +// implementation("org.bouncycastle:bcprov-jdk18on:1.80") implementation("org.bouncycastle:bcpkix-jdk18on:1.80") implementation("com.github.luben:zstd-jni:1.5.7-1") @@ -65,9 +65,12 @@ tasks.named("shadowJar") { // Include the tomlj dependency in the shadow jar configurations = listOf(project.configurations.getByName("shadowImplementation")) - relocate("org.antlr.v4", "reloc.org.antlr.v4") - relocate("org.tomlj", "reloc.org.tomlj") - relocate("org.checkerframework", "reloc.org.checkerframework") + val reloc = "am_libs" + relocate("org.antlr", "${reloc}.org.antlr") + relocate("org.tomlj", "${reloc}.org.tomlj") + relocate("org.checkerframework", "${reloc}.org.checkerframework") + relocate("com.github.luben", "${reloc}.com.github.luben") + relocate("org.bouncycastle", "${reloc}.org.bouncycastle") if (project.name.contains("neoforge")) { relocate("pl.skidam.automodpack_loader_core_neoforge", "pl.skidam.automodpack_loader_core") diff --git a/src/main/java/pl/skidam/automodpack/init/Common.java b/src/main/java/pl/skidam/automodpack/init/Common.java index d8147ce1..a973c2af 100644 --- a/src/main/java/pl/skidam/automodpack/init/Common.java +++ b/src/main/java/pl/skidam/automodpack/init/Common.java @@ -6,7 +6,7 @@ import pl.skidam.automodpack.networking.ModPackets; import pl.skidam.automodpack_core.modpack.Modpack; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.protocol.netty.NettyServer; +import pl.skidam.automodpack_core.protocol.netty.NettyServer; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/pl/skidam/automodpack/loader/GameCall.java b/src/main/java/pl/skidam/automodpack/loader/GameCall.java index 2bfc27d5..9bb5229e 100644 --- a/src/main/java/pl/skidam/automodpack/loader/GameCall.java +++ b/src/main/java/pl/skidam/automodpack/loader/GameCall.java @@ -14,7 +14,7 @@ public class GameCall implements GameCallService { @Override - public boolean canPlayerJoin(SocketAddress address, String id) { + public boolean isPlayerAuthorized(SocketAddress address, String id) { UUID uuid = UUID.fromString(id); String playerName = "Player"; // mock name, name matters less than UUID anyway GameProfile profile = new GameProfile(uuid, playerName); diff --git a/src/main/java/pl/skidam/automodpack/mixin/core/ServerNetworkIoMixin.java b/src/main/java/pl/skidam/automodpack/mixin/core/ServerNetworkIoMixin.java index a3103374..9e7a8670 100644 --- a/src/main/java/pl/skidam/automodpack/mixin/core/ServerNetworkIoMixin.java +++ b/src/main/java/pl/skidam/automodpack/mixin/core/ServerNetworkIoMixin.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import pl.skidam.automodpack_core.GlobalVariables; -import pl.skidam.protocol.netty.handler.ProtocolServerHandler; +import pl.skidam.automodpack_core.protocol.netty.handler.ProtocolServerHandler; import static pl.skidam.automodpack_core.GlobalVariables.MOD_ID; import static pl.skidam.automodpack_core.GlobalVariables.hostServer;