diff --git a/.idea/misc.xml b/.idea/misc.xml index 001e756..0c04b52 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 767d389..6a99b27 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,13 @@ dev.unlegitdqrk unlegitlibrary - 1.6.9 + 1.7.0 https://unlegitdqrk.dev/ Just a big library - 1.8 - 1.8 + 25 + 25 UTF-8 diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/AddonLoader.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/AddonLoader.java index 2d9f524..39ba153 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/AddonLoader.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/AddonLoader.java @@ -25,14 +25,6 @@ public final class AddonLoader extends DefaultMethodsOverrider { private final EventManager eventManager; private final Logger logger; - public final EventManager getEventManager() { - return eventManager; - } - - public final Logger getLogger() { - return logger; - } - public AddonLoader(EventManager eventManager, Logger logger) { this.addons = new ArrayList<>(); this.loadedClasses = new HashMap<>(); @@ -40,6 +32,14 @@ public final class AddonLoader extends DefaultMethodsOverrider { this.logger = logger; } + public EventManager getEventManager() { + return eventManager; + } + + public Logger getLogger() { + return logger; + } + public void loadAddonsFromDirectory(File addonFolder) throws IOException { if (!addonFolder.exists()) return; if (!addonFolder.isDirectory()) return; diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/Addon.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/Addon.java index bcf0bb6..5d4011f 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/Addon.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/Addon.java @@ -4,14 +4,12 @@ import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader; import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonDisabledEvent; import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonEnabledEvent; import dev.unlegitdqrk.unlegitlibrary.event.EventListener; -import dev.unlegitdqrk.unlegitlibrary.event.EventManager; import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.utils.Logger; public abstract class Addon { - private boolean isEnabled = false; private final AddonLoader addonLoader; + private boolean isEnabled = false; public Addon(AddonLoader addonLoader) { this.addonLoader = addonLoader; diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/AddonInfo.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/AddonInfo.java index be708da..c89553b 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/AddonInfo.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/addon/impl/AddonInfo.java @@ -1,13 +1,16 @@ package dev.unlegitdqrk.unlegitlibrary.addon.impl; -import dev.unlegitdqrk.unlegitlibrary.event.EventManager; - -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface AddonInfo { String name(); + String version(); + String author(); } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CardBrand.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CardBrand.java index 33540c9..d65d861 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CardBrand.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CardBrand.java @@ -2,33 +2,18 @@ * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ * See LICENSE-File if exists */ package dev.unlegitdqrk.unlegitlibrary.bank; -public class CardBrand { +public record CardBrand(int length, String[] prefixes) { public static final CardBrand VISA = new CardBrand(16, new String[]{"4"}); - public static final CardBrand AMERICANEXPRESS = new CardBrand(15, new String[]{"34","37"}); - public static final CardBrand MASTERCARD = new CardBrand(16, new String[] { - "51","52","53","54","55", - "2221","2222","2223","2720" + public static final CardBrand AMERICANEXPRESS = new CardBrand(15, new String[]{"34", "37"}); + public static final CardBrand MASTERCARD = new CardBrand(16, new String[]{ + "51", "52", "53", "54", "55", + "2221", "2222", "2223", "2720" }); - private final int length; - private final String[] prefixes; - - public CardBrand(int length, String[] prefixes) { - this.length = length; - this.prefixes = prefixes; - } - - public int getLength() { - return length; - } - - public String[] getPrefixes() { - return prefixes; - } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CreditCard.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CreditCard.java index f06d01e..149ea47 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CreditCard.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/bank/CreditCard.java @@ -2,7 +2,7 @@ * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ * See LICENSE-File if exists */ @@ -18,18 +18,6 @@ public class CreditCard { private final CardBrand cardBrand; private final Random random; - public CardBrand getCardBrand() { - return cardBrand; - } - - public Random getRandom() { - return random; - } - - public String getCardNumber() { - return cardNumber; - } - public CreditCard(CardBrand cardBrand) { this.cardBrand = cardBrand; this.random = new Random(); @@ -42,14 +30,26 @@ public class CreditCard { this.cardNumber = generateCardNumber(); } + public CardBrand getCardBrand() { + return cardBrand; + } + + public Random getRandom() { + return random; + } + + public String getCardNumber() { + return cardNumber; + } + private String generateCardNumber() { - int totalLength = cardBrand.getLength(); + int totalLength = cardBrand.length(); List digits = new ArrayList<>(totalLength); String chosenPrefix = null; - if (cardBrand.getPrefixes() != null && cardBrand.getPrefixes().length > 0) { - String[] prefixes = cardBrand.getPrefixes(); + if (cardBrand.prefixes() != null && cardBrand.prefixes().length > 0) { + String[] prefixes = cardBrand.prefixes(); chosenPrefix = prefixes[random.nextInt(prefixes.length)]; for (char c : chosenPrefix.toCharArray()) { digits.add(c - '0'); diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java index 174195e..3d76fb0 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java @@ -1,10 +1,31 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + package dev.unlegitdqrk.unlegitlibrary.network.system.client; import dev.unlegitdqrk.unlegitlibrary.event.EventManager; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.*; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive.C_PacketReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive.C_PacketReceivedFailedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive.C_UnknownObjectReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send.C_PacketSendEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send.C_PacketSendFailedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect.ClientConnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect.ClientDisconnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect.ClientFullyConnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect.ClientFullyDisconnectedEvent; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ClientIDPacket; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindPacket; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.DtlsEndpoint; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.UdpPacketCodec; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils; import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider; import dev.unlegitdqrk.unlegitlibrary.utils.Logger; @@ -15,317 +36,649 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; import java.security.KeyStore; import java.security.cert.CertificateFactory; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +/** + * Hybrid client supporting TCP (TLS) and UDP (DTLS). + * + *

Your chosen policy: BOTH transports must be connected simultaneously.

+ */ public final class NetworkClient { + private final String host; - private final int port; + private final int tcpPort; + private final int udpPort; + private final PacketHandler packetHandler; private final EventManager eventManager; private final Logger logger; - private final int timeout; - private final SSLSocketFactory sslSocketFactory; - private final SSLParameters sslParameters; - private final Proxy proxy; - private SSLSocket socket; - private ObjectOutputStream outputStream; - private ObjectInputStream inputStream; - private int clientID = -1; - private NetworkClient(String host, int port, PacketHandler packetHandler, - EventManager eventManager, Logger logger, - int timeout, SSLSocketFactory sslSocketFactory, - SSLParameters sslParameters, Proxy proxy) { - this.host = host; - this.port = port; - this.packetHandler = packetHandler; - this.eventManager = eventManager; + private final int timeout; + private final SSLSocketFactory tlsSocketFactory; + private final SSLParameters tlsParameters; + private final SSLContext dtlsContext; + private final Proxy proxy; + private final AtomicBoolean fullyConnectedEventFired = new AtomicBoolean(false); + private SSLSocket tcpSocket; + private ObjectOutputStream tcpOut; + private ObjectInputStream tcpIn; + private DatagramChannel udpChannel; + private DtlsEndpoint dtlsEndpoint; + private InetSocketAddress udpRemote; + private volatile ClientID clientId; + private volatile Thread tcpReceiveThread; + private volatile Thread udpThread; + + public NetworkClient( + String host, + int tcpPort, + int udpPort, + PacketHandler packetHandler, + EventManager eventManager, + Logger logger, + int timeout, + SSLSocketFactory tlsSocketFactory, + SSLParameters tlsParameters, + SSLContext dtlsContext, + Proxy proxy + ) { + this.host = Objects.requireNonNull(host, "host"); + this.tcpPort = tcpPort; + this.udpPort = udpPort; + + this.packetHandler = Objects.requireNonNull(packetHandler, "packetHandler"); + this.eventManager = Objects.requireNonNull(eventManager, "eventManager"); this.logger = logger; + this.timeout = timeout; - this.sslSocketFactory = sslSocketFactory; - this.sslParameters = sslParameters; + this.tlsSocketFactory = tlsSocketFactory; + this.tlsParameters = tlsParameters; + this.dtlsContext = dtlsContext; + this.proxy = proxy; this.packetHandler.setClientInstance(this); this.packetHandler.registerPacket(new ClientIDPacket()); - this.proxy = proxy; - } - - public Proxy getProxy() { - return proxy; - } - - public int getClientID() { - return clientID; - } - - public void setClientID(int clientID) { - if (this.clientID == -1) this.clientID = clientID; - } private final Thread receiveThread = new Thread(this::receive); - - public PacketHandler getPacketHandler() { - return packetHandler; + this.packetHandler.registerPacket(new UdpBindPacket()); } public EventManager getEventManager() { return eventManager; } - public ObjectInputStream getInputStream() { - return inputStream; + public ClientID getClientId() { + return clientId; } - public SSLSocket getSocket() { - return socket; + public void setClientId(ClientID clientId) { + if (this.clientId == null) { + this.clientId = Objects.requireNonNull(clientId, "clientId"); + } } - public ObjectOutputStream getOutputStream() { - return outputStream; + public boolean isTcpConnected() { + Thread t = tcpReceiveThread; + return tcpSocket != null && tcpSocket.isConnected() && !tcpSocket.isClosed() + && t != null && t.isAlive() && !t.isInterrupted(); } - public Logger getLogger() { - return logger; + public boolean isUdpConnected() { + return dtlsEndpoint != null && udpChannel != null && udpChannel.isOpen(); } - public int getPort() { - return port; - } - - public String getHost() { - return host; - } - - public boolean isConnected() { - return socket != null && socket.isConnected() && !socket.isClosed() - && receiveThread.isAlive() && !receiveThread.isInterrupted(); + public boolean isFullyConnected() { + return isTcpConnected() && isUdpConnected() && clientId != null; } public synchronized boolean connect() throws ConnectException { - if (isConnected()) return false; + if (isFullyConnected()) return false; - if (logger != null) logger.info("Trying to connect to " + host + ":" + port + "..."); - else System.out.println("Trying to connect to " + host + ":" + port + "..."); + fullyConnectedEventFired.set(false); try { - if (sslSocketFactory == null) - throw new ConnectException("SSL socket factory not set. Client certificate required!"); + connectTcp(); + eventManager.executeEvent(new ClientConnectedEvent(this, Transport.TCP)); - if (proxy != null) { - Socket rawSocket = new Socket(proxy); - rawSocket.connect(new InetSocketAddress(host, port), timeout); - socket = (SSLSocket) sslSocketFactory.createSocket(rawSocket, host, port, true); - } else socket = (SSLSocket) sslSocketFactory.createSocket(host, port); + waitForClientId(); - if (sslParameters != null) socket.setSSLParameters(sslParameters); - else { - SSLParameters defaultParams = socket.getSSLParameters(); - defaultParams.setProtocols(new String[]{"TLSv1.3"}); - socket.setSSLParameters(defaultParams); - } + connectDtls(); + // UDP is considered connected after DTLS handshake is complete (bind follows immediately) + eventManager.executeEvent(new ClientConnectedEvent(this, Transport.UDP)); - socket.setTcpNoDelay(true); - socket.setSoTimeout(timeout); - try { - socket.startHandshake(); - } catch (Exception handshakeEx) { - throw new ConnectException("Handshake failed: " + handshakeEx.getMessage()); - } + bindUdpToClientId(); - outputStream = new ObjectOutputStream(socket.getOutputStream()); - inputStream = new ObjectInputStream(socket.getInputStream()); - - receiveThread.start(); - eventManager.executeEvent(new ClientConnectedEvent(this)); - if (logger != null) logger.info("Connected to " + host + ":" + port); - else System.out.println("Connected to " + host + ":" + port); + fireFullyConnectedIfReady(); return true; + } catch (ConnectException e) { + disconnect(); + throw e; } catch (Exception e) { + disconnect(); throw new ConnectException("Failed to connect: " + e.getMessage()); } } - private void receive() { + private void connectTcp() throws Exception { + if (tlsSocketFactory == null) throw new ConnectException("TLS socket factory not set."); + + if (proxy != null) { + Socket raw = new Socket(proxy); + raw.connect(new InetSocketAddress(host, tcpPort), timeout); + tcpSocket = (SSLSocket) tlsSocketFactory.createSocket(raw, host, tcpPort, true); + } else { + tcpSocket = (SSLSocket) tlsSocketFactory.createSocket(host, tcpPort); + } + + if (tlsParameters != null) { + tcpSocket.setSSLParameters(tlsParameters); + } else { + SSLParameters p = tcpSocket.getSSLParameters(); + p.setProtocols(new String[]{"TLSv1.3"}); + tcpSocket.setSSLParameters(p); + } + + tcpSocket.setTcpNoDelay(true); + tcpSocket.setSoTimeout(timeout); + tcpSocket.startHandshake(); + + tcpOut = new ObjectOutputStream(tcpSocket.getOutputStream()); + tcpIn = new ObjectInputStream(tcpSocket.getInputStream()); + + tcpReceiveThread = new Thread(this::tcpReceive, "NetworkClient-TCP-Receive"); + tcpReceiveThread.start(); + } + + private void waitForClientId() throws ConnectException { + long start = System.currentTimeMillis(); + while (clientId == null) { + if (!isTcpConnected()) throw new ConnectException("TCP disconnected before ClientID was assigned."); + if (System.currentTimeMillis() - start > timeout) + throw new ConnectException("Timed out waiting for ClientID over TCP."); + Thread.onSpinWait(); + } + } + + private void connectDtls() throws Exception { + if (dtlsContext == null) throw new ConnectException("DTLS context not set."); + + udpRemote = new InetSocketAddress(host, udpPort); + + udpChannel = DatagramChannel.open(); + udpChannel.configureBlocking(false); + udpChannel.connect(udpRemote); + + dtlsEndpoint = new DtlsEndpoint( + udpChannel, + dtlsContext, + true, + 1400, + timeout, + this::onDtlsApplicationData + ); + + dtlsEndpoint.handshake(udpRemote); + + udpThread = new Thread(this::udpLoop, "NetworkClient-UDP-DTLS"); + udpThread.start(); + } + + private void bindUdpToClientId() throws IOException, ClassNotFoundException { + sendPacket(new UdpBindPacket(clientId), Transport.UDP); + } + + public boolean sendPacket(Packet packet, Transport transport) throws IOException, ClassNotFoundException { + Objects.requireNonNull(packet, "packet"); + Objects.requireNonNull(transport, "transport"); + + return switch (transport) { + case TCP -> sendTcp(packet); + case UDP -> sendUdp(packet); + }; + } + + private boolean sendTcp(Packet packet) throws IOException, ClassNotFoundException { + if (!isTcpConnected()) return false; + + boolean sent = packetHandler.sendPacket(packet, tcpOut); + if (sent) eventManager.executeEvent(new C_PacketSendEvent(this, packet, Transport.TCP)); + else eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet, Transport.TCP)); + return sent; + } + + private boolean sendUdp(Packet packet) throws IOException, ClassNotFoundException { + if (dtlsEndpoint == null || udpRemote == null) return false; + + ByteBuffer encoded = UdpPacketCodec.encode(packetHandler, packet); + dtlsEndpoint.sendApplication(udpRemote, encoded); + + eventManager.executeEvent(new C_PacketSendEvent(this, packet, Transport.UDP)); + return true; + } + + private void udpLoop() { try { - while (isConnected()) { - Object received = inputStream.readObject(); - handleReceived(received); + while (udpChannel != null && udpChannel.isOpen() && !Thread.currentThread().isInterrupted()) { + DtlsEndpoint endpoint = dtlsEndpoint; + if (endpoint != null) endpoint.poll(); + Thread.onSpinWait(); } - } catch (Exception e) { + } catch (Exception ignored) { disconnect(); } } - private void handleReceived(Object received) throws IOException, ClassNotFoundException { + private void onDtlsApplicationData(java.net.SocketAddress remote, ByteBuffer data) { + try { + Packet decoded = UdpPacketCodec.decodeAndHandle(packetHandler, data); + if (decoded == null) { + eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, data, Transport.UDP)); + return; + } + eventManager.executeEvent(new C_PacketReceivedEvent(this, decoded, Transport.UDP)); + } catch (Exception ignored) { + } + } + + private void tcpReceive() { + try { + while (isTcpConnected()) { + Object received = tcpIn.readObject(); + handleTcpReceived(received); + } + } catch (Exception ignored) { + disconnect(); + } + } + + private void handleTcpReceived(Object received) throws IOException, ClassNotFoundException { if (received instanceof Integer id) { if (packetHandler.isPacketIDRegistered(id)) { Packet packet = packetHandler.getPacketByID(id); - boolean handled = packetHandler.handlePacket(id, packet, inputStream); - if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet)); - else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet)); - } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); - } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); + boolean handled = packetHandler.handlePacket(id, packet, tcpIn); + + if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet, Transport.TCP)); + else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet, Transport.TCP)); + } else { + eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received, Transport.TCP)); + } + } else { + eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received, Transport.TCP)); + } + + fireFullyConnectedIfReady(); + } + + private void fireFullyConnectedIfReady() { + if (isFullyConnected() && fullyConnectedEventFired.compareAndSet(false, true)) { + eventManager.executeEvent(new ClientFullyConnectedEvent(this, new Transport[]{Transport.TCP, Transport.UDP})); + } } public synchronized boolean disconnect() { - if (!isConnected()) return false; + boolean wasTcpConnected = isTcpConnected(); + boolean wasUdpConnected = isUdpConnected(); + boolean wasFullyConnected = isFullyConnected(); + + if (!wasTcpConnected && !wasUdpConnected) { + cleanup(); + return false; + } + try { - receiveThread.interrupt(); - if (outputStream != null) outputStream.close(); - if (inputStream != null) inputStream.close(); - if (socket != null) socket.close(); - } catch (IOException e) { - if (logger != null) logger.exception("Error closing connection", e); - else System.err.println("Error closing connection: " + e.getMessage()); - } finally { - socket = null; - outputStream = null; - inputStream = null; - clientID = -1; - eventManager.executeEvent(new ClientDisconnectedEvent(this)); + Thread t1 = tcpReceiveThread; + Thread t2 = udpThread; + if (t1 != null) t1.interrupt(); + if (t2 != null) t2.interrupt(); + + if (tcpOut != null) tcpOut.close(); + if (tcpIn != null) tcpIn.close(); + if (tcpSocket != null) tcpSocket.close(); + + if (udpChannel != null) udpChannel.close(); + } catch (IOException ignored) { + } + + // Transport-specific disconnect events + if (wasUdpConnected) { + eventManager.executeEvent(new ClientDisconnectedEvent(this, Transport.UDP)); + } + if (wasTcpConnected) { + eventManager.executeEvent(new ClientDisconnectedEvent(this, Transport.TCP)); + } + + cleanup(); + + if (wasFullyConnected) { + eventManager.executeEvent(new ClientFullyDisconnectedEvent(this, new Transport[]{Transport.TCP, Transport.UDP})); } return true; } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof NetworkClient target)) return false; - return target.getClientID() == clientID; + private void cleanup() { + tcpSocket = null; + tcpOut = null; + tcpIn = null; + + udpChannel = null; + dtlsEndpoint = null; + udpRemote = null; + + tcpReceiveThread = null; + udpThread = null; + + clientId = null; + fullyConnectedEventFired.set(false); } - public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { - if (!isConnected()) return false; - boolean sent = packetHandler.sendPacket(packet, outputStream); - if (sent) eventManager.executeEvent(new C_PacketSendEvent(this, packet)); - else eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet)); - return sent; + private void logInfo(String msg) { + if (logger != null) logger.info(msg); + else System.out.println(msg); } - // --- Builder --- - public static class ClientBuilder extends DefaultMethodsOverrider { + /** + * Builder for {@link NetworkClient}. + */ + public static final class ClientBuilder extends DefaultMethodsOverrider { + private String host; - private int port; + private int tcpPort; + private int udpPort; + private PacketHandler packetHandler; private EventManager eventManager; private Logger logger; + private int timeout = 5000; - private SSLSocketFactory sslSocketFactory; - private SSLParameters sslParameters; + + private SSLSocketFactory tlsSocketFactory; + private SSLParameters tlsParameters; + private SSLContext dtlsContext; + private File caFolder; - private File clientFolder; - private File keyFolder; + private File clientCertFolder; + private File clientKeyFolder; + private Proxy proxy; - public static SSLSocketFactory createSSLSocketFactory(File caFolder, File clientCertFolder, File clientKeyFolder) throws Exception { - // TrustStore (Root-CAs) + /** + * Creates a TLS socket factory for TCP using PEM certificates/keys. + * + * @param caFolder folder containing CA PEM files + * @param clientCertFolder folder containing client certificate files + * @param clientKeyFolder folder containing client key files + * @return TLS socket factory + * @throws Exception on any certificate/key errors + */ + public static SSLSocketFactory createTLSSocketFactory(File caFolder, File clientCertFolder, File clientKeyFolder) throws Exception { + Objects.requireNonNull(caFolder, "caFolder"); + Objects.requireNonNull(clientCertFolder, "clientCertFolder"); + Objects.requireNonNull(clientKeyFolder, "clientKeyFolder"); + + // Trust store (CAs) KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); int caIndex = 1; - for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { - try (FileInputStream fis = new FileInputStream(caFile)) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - java.security.cert.Certificate caCert = cf.generateCertificate(fis); - trustStore.setCertificateEntry("ca" + (caIndex++), caCert); + File[] caFiles = caFolder.listFiles((f) -> f.isFile() && f.getName().endsWith(".pem")); + if (caFiles != null) { + for (File caFile : caFiles) { + try (FileInputStream fis = new FileInputStream(caFile)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + java.security.cert.Certificate caCert = cf.generateCertificate(fis); + trustStore.setCertificateEntry("ca" + (caIndex++), caCert); + } } } TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); + // Key store (client cert + key) KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load(null, null); // Kein Passwort nötig + keyStore.load(null, null); int clientIndex = 1; - for (File certFile : clientCertFolder.listFiles((f) -> f.getName().endsWith(".crt"))) { - String baseName = certFile.getName().replace(".crt", ""); - File keyFile = new File(clientKeyFolder, baseName + ".key"); - if (!keyFile.exists()) continue; + File[] certFiles = clientCertFolder.listFiles((f) -> f.isFile() && (f.getName().endsWith(".crt") || f.getName().endsWith(".pem"))); + if (certFiles != null) { + for (File certFile : certFiles) { + String baseName = certFile.getName() + .replace(".crt", "") + .replace(".pem", ""); + File keyFile = new File(clientKeyFolder, baseName + ".key"); + if (!keyFile.exists()) continue; - java.security.PrivateKey key = PemUtils.loadPrivateKey(keyFile); - java.security.cert.Certificate cert = PemUtils.loadCertificate(certFile); + java.security.PrivateKey key = PemUtils.loadPrivateKey(keyFile); + java.security.cert.Certificate cert = PemUtils.loadCertificate(certFile); - keyStore.setKeyEntry("client" + (clientIndex++), key, null, new java.security.cert.Certificate[]{cert}); + keyStore.setKeyEntry("client" + (clientIndex++), key, null, new java.security.cert.Certificate[]{cert}); + } } KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, null); - // SSLContext - SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext.getSocketFactory(); + SSLContext tls = SSLContext.getInstance("TLSv1.3"); + tls.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return tls.getSocketFactory(); } + /** + * Creates a DTLS context using the same key/trust setup as TLS. + * + * @param caFolder folder containing CA PEM files + * @param clientCertFolder folder containing client certificate files + * @param clientKeyFolder folder containing client key files + * @return DTLS SSL context + * @throws Exception on any certificate/key errors + */ + public static SSLContext createDTLSContext(File caFolder, File clientCertFolder, File clientKeyFolder) throws Exception { + Objects.requireNonNull(caFolder, "caFolder"); + Objects.requireNonNull(clientCertFolder, "clientCertFolder"); + Objects.requireNonNull(clientKeyFolder, "clientKeyFolder"); + + // Trust store + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + + int caIndex = 1; + File[] caFiles = caFolder.listFiles((f) -> f.isFile() && f.getName().endsWith(".pem")); + if (caFiles != null) { + for (File caFile : caFiles) { + try (FileInputStream fis = new FileInputStream(caFile)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + java.security.cert.Certificate caCert = cf.generateCertificate(fis); + trustStore.setCertificateEntry("ca" + (caIndex++), caCert); + } + } + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + // Key store + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(null, null); + + int clientIndex = 1; + File[] certFiles = clientCertFolder.listFiles((f) -> f.isFile() && (f.getName().endsWith(".crt") || f.getName().endsWith(".pem"))); + if (certFiles != null) { + for (File certFile : certFiles) { + String baseName = certFile.getName() + .replace(".crt", "") + .replace(".pem", ""); + File keyFile = new File(clientKeyFolder, baseName + ".key"); + if (!keyFile.exists()) continue; + + java.security.PrivateKey key = PemUtils.loadPrivateKey(keyFile); + java.security.cert.Certificate cert = PemUtils.loadCertificate(certFile); + + keyStore.setKeyEntry("client" + (clientIndex++), key, null, new java.security.cert.Certificate[]{cert}); + } + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); + + SSLContext dtls = SSLContext.getInstance("DTLS"); + dtls.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return dtls; + } + + /** + * Sets server host. + */ public ClientBuilder setHost(String host) { this.host = host; return this; } - public ClientBuilder setPort(int port) { - this.port = port; + /** + * Sets TCP port. + */ + public ClientBuilder setTcpPort(int tcpPort) { + this.tcpPort = tcpPort; return this; } + /** + * Sets UDP port. + */ + public ClientBuilder setUdpPort(int udpPort) { + this.udpPort = udpPort; + return this; + } + + /** + * Sets packet handler. + */ public ClientBuilder setPacketHandler(PacketHandler handler) { this.packetHandler = handler; return this; } + /** + * Sets event manager. + */ public ClientBuilder setEventManager(EventManager manager) { this.eventManager = manager; return this; } + /** + * Sets logger. + */ public ClientBuilder setLogger(Logger logger) { this.logger = logger; return this; } - public ClientBuilder setTimeout(int timeout) { - this.timeout = timeout; + /** + * Sets timeout in millis. + */ + public ClientBuilder setTimeout(int timeoutMillis) { + this.timeout = timeoutMillis; return this; } - public ClientBuilder setSSLSocketFactory(SSLSocketFactory factory) { - this.sslSocketFactory = factory; + /** + * Sets TLS socket factory explicitly. + */ + public ClientBuilder setTLSSocketFactory(SSLSocketFactory factory) { + this.tlsSocketFactory = factory; return this; } - public ClientBuilder setSSLParameters(SSLParameters params) { - this.sslParameters = params; + /** + * Sets TLS parameters (optional). + */ + public ClientBuilder setTLSParameters(SSLParameters params) { + this.tlsParameters = params; return this; } + /** + * Sets DTLS context explicitly. + */ + public ClientBuilder setDTLSContext(SSLContext dtlsContext) { + this.dtlsContext = dtlsContext; + return this; + } + + /** + * Sets root CA folder for auto-creating TLS/DTLS when factories/contexts are not provided. + */ public ClientBuilder setRootCAFolder(File folder) { this.caFolder = folder; return this; } - public ClientBuilder setClientCertificatesFolder(File clientFolder, File keyFolder) { - this.clientFolder = clientFolder; - this.keyFolder = keyFolder; + /** + * Sets client certificate + key folders for auto-creating TLS/DTLS. + */ + public ClientBuilder setClientCertificatesFolder(File clientCertFolder, File clientKeyFolder) { + this.clientCertFolder = clientCertFolder; + this.clientKeyFolder = clientKeyFolder; return this; } + /** + * Sets optional proxy. + */ public ClientBuilder setProxy(Proxy proxy) { this.proxy = proxy; return this; } + /** + * Builds the client. + * + * @return client + */ public NetworkClient build() { - if (sslSocketFactory == null && caFolder != null) { + if (host == null) throw new IllegalStateException("Host not set"); + if (tcpPort <= 0) throw new IllegalStateException("TCP port not set"); + if (udpPort <= 0) throw new IllegalStateException("UDP port not set"); + if (packetHandler == null) throw new IllegalStateException("PacketHandler not set"); + if (eventManager == null) throw new IllegalStateException("EventManager not set"); + + // Auto-create TLS/DTLS from folders if not explicitly provided + if ((tlsSocketFactory == null || dtlsContext == null) && caFolder != null) { + if (clientCertFolder == null || clientKeyFolder == null) { + throw new IllegalStateException("Client cert/key folders must be set when using CA folder auto-config."); + } try { - sslSocketFactory = createSSLSocketFactory(caFolder, clientFolder, keyFolder); + if (tlsSocketFactory == null) { + tlsSocketFactory = createTLSSocketFactory(caFolder, clientCertFolder, clientKeyFolder); + } + if (dtlsContext == null) { + dtlsContext = createDTLSContext(caFolder, clientCertFolder, clientKeyFolder); + } } catch (Exception e) { - throw new RuntimeException("Failed to create SSLFactory", e); + throw new RuntimeException("Failed to create TLS/DTLS client configuration", e); } } - return new NetworkClient(host, port, packetHandler, eventManager, logger, - timeout, sslSocketFactory, sslParameters, proxy); + if (tlsSocketFactory == null) throw new IllegalStateException("TLS socket factory missing"); + if (dtlsContext == null) throw new IllegalStateException("DTLS context missing"); + + return new NetworkClient( + host, + tcpPort, + udpPort, + packetHandler, + eventManager, + logger, + timeout, + tlsSocketFactory, + tlsParameters, + dtlsContext, + proxy + ); } } - - } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedEvent.java deleted file mode 100644 index 02664f3..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; - -public final class C_PacketReceivedEvent extends Event { - private final NetworkClient networkClient; - private final Packet packet; - - public C_PacketReceivedEvent(NetworkClient networkClient, Packet packet) { - this.networkClient = networkClient; - this.packet = packet; - } - - public NetworkClient getNetworkClient() { - return networkClient; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedFailedEvent.java deleted file mode 100644 index a947bd9..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketReceivedFailedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; - -public final class C_PacketReceivedFailedEvent extends Event { - private final NetworkClient networkClient; - private final Packet packet; - - public C_PacketReceivedFailedEvent(NetworkClient networkClient, Packet packet) { - this.networkClient = networkClient; - this.packet = packet; - } - - public NetworkClient getNetworkClient() { - return networkClient; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendEvent.java deleted file mode 100644 index 55b3e50..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; - -public final class C_PacketSendEvent extends Event { - private final NetworkClient networkClient; - private final Packet packet; - - public C_PacketSendEvent(NetworkClient networkClient, Packet packet) { - this.networkClient = networkClient; - this.packet = packet; - } - - public NetworkClient getNetworkClient() { - return networkClient; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendFailedEvent.java deleted file mode 100644 index 53a269a..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_PacketSendFailedEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; - -public final class C_PacketSendFailedEvent extends Event { - private final NetworkClient networkClient; - private final Packet packet; - - - public C_PacketSendFailedEvent(NetworkClient networkClient, Packet packet) { - this.networkClient = networkClient; - this.packet = packet; - } - - public NetworkClient getNetworkClient() { - return networkClient; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_UnknownObjectReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_UnknownObjectReceivedEvent.java deleted file mode 100644 index a98262e..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/C_UnknownObjectReceivedEvent.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; - -public final class C_UnknownObjectReceivedEvent extends Event { - private final NetworkClient networkClient; - private final Object received; - - public C_UnknownObjectReceivedEvent(NetworkClient networkClient, Object received) { - this.networkClient = networkClient; - this.received = received; - } - - public NetworkClient getNetworkClient() { - return networkClient; - } - - public Object getReceived() { - return received; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientConnectedEvent.java deleted file mode 100644 index 8ea1100..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientConnectedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; - -public final class ClientConnectedEvent extends Event { - private final NetworkClient client; - - - public ClientConnectedEvent(NetworkClient client) { - this.client = client; - } - - public NetworkClient getClient() { - return client; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientDisconnectedEvent.java deleted file mode 100644 index be23ef9..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/ClientDisconnectedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.client.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; - -public final class ClientDisconnectedEvent extends Event { - private final NetworkClient client; - - public ClientDisconnectedEvent(NetworkClient client) { - this.client = client; - } - - public NetworkClient getClient() { - return client; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedEvent.java new file mode 100644 index 0000000..6e24468 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet was received by the client on a specific transport. + */ +public final class C_PacketReceivedEvent extends Event { + + private final NetworkClient networkClient; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet received event. + * + * @param networkClient client instance + * @param packet received packet + * @param transport transport the packet was received on + */ + public C_PacketReceivedEvent(NetworkClient networkClient, Packet packet, Transport transport) { + this.networkClient = networkClient; + this.packet = packet; + this.transport = transport; + } + + public NetworkClient getNetworkClient() { + return networkClient; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedFailedEvent.java new file mode 100644 index 0000000..4a3326c --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_PacketReceivedFailedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet receive failed on the client on a specific transport. + */ +public final class C_PacketReceivedFailedEvent extends Event { + + private final NetworkClient networkClient; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet receive failed event. + * + * @param networkClient client instance + * @param packet packet that failed to be read/handled + * @param transport transport the packet was received on + */ + public C_PacketReceivedFailedEvent(NetworkClient networkClient, Packet packet, Transport transport) { + this.networkClient = networkClient; + this.packet = packet; + this.transport = transport; + } + + public NetworkClient getNetworkClient() { + return networkClient; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_UnknownObjectReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_UnknownObjectReceivedEvent.java new file mode 100644 index 0000000..f748218 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/receive/C_UnknownObjectReceivedEvent.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when an unknown object was received on a specific transport. + */ +public final class C_UnknownObjectReceivedEvent extends Event { + + private final NetworkClient networkClient; + private final Object received; + private final Transport transport; + + /** + * Creates a new unknown object received event. + * + * @param networkClient client instance + * @param received received object + * @param transport transport the object was received on + */ + public C_UnknownObjectReceivedEvent(NetworkClient networkClient, Object received, Transport transport) { + this.networkClient = networkClient; + this.received = received; + this.transport = transport; + } + + public NetworkClient getNetworkClient() { + return networkClient; + } + + public Object getReceived() { + return received; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java new file mode 100644 index 0000000..a433072 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet was sent by the client on a specific transport. + */ +public final class C_PacketSendEvent extends Event { + + private final NetworkClient networkClient; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet send event. + * + * @param networkClient client instance + * @param packet sent packet + * @param transport used transport + */ + public C_PacketSendEvent(NetworkClient networkClient, Packet packet, Transport transport) { + this.networkClient = networkClient; + this.packet = packet; + this.transport = transport; + } + + public NetworkClient getNetworkClient() { + return networkClient; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendFailedEvent.java new file mode 100644 index 0000000..ee63090 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendFailedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet send failed on the client on a specific transport. + */ +public final class C_PacketSendFailedEvent extends Event { + + private final NetworkClient networkClient; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet send failed event. + * + * @param networkClient client instance + * @param packet packet that failed to be sent + * @param transport intended transport + */ + public C_PacketSendFailedEvent(NetworkClient networkClient, Packet packet, Transport transport) { + this.networkClient = networkClient; + this.packet = packet; + this.transport = transport; + } + + public NetworkClient getNetworkClient() { + return networkClient; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientConnectedEvent.java new file mode 100644 index 0000000..d0d0643 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientConnectedEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when the client established a specific transport connection. + * + *

This event is transport-specific: + *

+ */ +public final class ClientConnectedEvent extends Event { + + private final NetworkClient client; + private final Transport transport; + + /** + * Creates a new client connected event. + * + * @param client client instance + * @param transport connected transport + */ + public ClientConnectedEvent(NetworkClient client, Transport transport) { + this.client = Objects.requireNonNull(client, "client"); + this.transport = Objects.requireNonNull(transport, "transport"); + } + + /** + * Returns the client. + * + * @return client + */ + public NetworkClient getClient() { + return client; + } + + /** + * Returns the transport that was connected. + * + * @return transport + */ + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientFullyConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientFullyConnectedEvent.java new file mode 100644 index 0000000..d836019 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/connect/ClientFullyConnectedEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when the client has fully established the configured transport policy. + * + *

For your setup this means: TCP (TLS) connected, ClientID received and UDP (DTLS) bound.

+ */ +public final class ClientFullyConnectedEvent extends Event { + + private final NetworkClient client; + private final Transport[] requiredTransports; + + /** + * Creates a new event. + * + * @param client client instance + * @param requiredTransports transports that are required and now satisfied + */ + public ClientFullyConnectedEvent(NetworkClient client, Transport[] requiredTransports) { + this.client = client; + this.requiredTransports = requiredTransports; + } + + /** + * Returns the client. + * + * @return client + */ + public NetworkClient getClient() { + return client; + } + + /** + * Returns the required transports that are now established. + * + * @return required transports + */ + public Transport[] getRequiredTransports() { + return requiredTransports; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientDisconnectedEvent.java new file mode 100644 index 0000000..cac7034 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientDisconnectedEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when the client lost/closed a specific transport connection. + * + *

This event is transport-specific: + *

+ */ +public final class ClientDisconnectedEvent extends Event { + + private final NetworkClient client; + private final Transport transport; + + /** + * Creates a new client disconnected event. + * + * @param client client instance + * @param transport disconnected transport + */ + public ClientDisconnectedEvent(NetworkClient client, Transport transport) { + this.client = Objects.requireNonNull(client, "client"); + this.transport = Objects.requireNonNull(transport, "transport"); + } + + /** + * Returns the client. + * + * @return client + */ + public NetworkClient getClient() { + return client; + } + + /** + * Returns the transport that was disconnected. + * + * @return transport + */ + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientFullyDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientFullyDisconnectedEvent.java new file mode 100644 index 0000000..278530a --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/state/disconnect/ClientFullyDisconnectedEvent.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when the client was fully connected and then became fully disconnected. + * + *

For your policy (TCP+UDP required) this means: + * previously: TCP+UDP connected and ClientID present, + * now: not fully connected anymore (both transports are not satisfying the requirement).

+ */ +public final class ClientFullyDisconnectedEvent extends Event { + + private final NetworkClient client; + private final Transport[] requiredTransports; + + /** + * Creates a new fully disconnected event. + * + * @param client client instance + * @param requiredTransports required transports according to policy + */ + public ClientFullyDisconnectedEvent(NetworkClient client, Transport[] requiredTransports) { + this.client = Objects.requireNonNull(client, "client"); + this.requiredTransports = Objects.requireNonNull(requiredTransports, "requiredTransports"); + } + + /** + * Returns the client. + * + * @return client + */ + public NetworkClient getClient() { + return client; + } + + /** + * Returns transports that were required for full connectivity. + * + * @return required transports + */ + public Transport[] getRequiredTransports() { + return requiredTransports; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ClientIDPacket.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ClientIDPacket.java index dbd9580..07eb3cc 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ClientIDPacket.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ClientIDPacket.java @@ -1,31 +1,58 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.UUID; +/** + * Transfers the server-assigned {@link ClientID} to the client. + */ public final class ClientIDPacket extends Packet { - private int clientID; + + private ClientID clientId; public ClientIDPacket() { super(0); } - public ClientIDPacket(int clientID) { + public ClientIDPacket(ClientID clientId) { this(); - this.clientID = clientID; + this.clientId = clientId; + } + + public ClientID getClientId() { + return clientId; } @Override - public void write(PacketHandler packetHandler, ObjectOutputStream outputStream) throws IOException, ClassNotFoundException { - outputStream.writeInt(clientID); + public void write(PacketHandler packetHandler, ObjectOutputStream outputStream) throws IOException { + if (clientId == null) throw new IOException("ClientIDPacket.clientId is null"); + UUID uuid = clientId.uuid(); + outputStream.writeLong(uuid.getMostSignificantBits()); + outputStream.writeLong(uuid.getLeastSignificantBits()); } @Override - public void read(PacketHandler packetHandler, ObjectInputStream outputStream) throws IOException, ClassNotFoundException { - packetHandler.getClientInstance().setClientID(clientID); + public void read(PacketHandler packetHandler, ObjectInputStream inputStream) throws IOException { + long msb = inputStream.readLong(); + long lsb = inputStream.readLong(); + this.clientId = new ClientID(new UUID(msb, lsb)); + + if (packetHandler.getClientInstance() != null) { + packetHandler.getClientInstance().setClientId(this.clientId); + } } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java new file mode 100644 index 0000000..9bb5113 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl; + +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.UUID; + +/** + * Binds a DTLS/UDP endpoint to the already assigned {@link ClientID}. + * + *

Flow: TCP connect -> receive ClientID -> DTLS handshake -> send UdpBindPacket(clientId)

+ */ +public final class UdpBindPacket extends Packet { + + public static final int PACKET_ID = 1; + + private ClientID clientId; + + public UdpBindPacket() { + super(PACKET_ID); + } + + public UdpBindPacket(ClientID clientId) { + this(); + this.clientId = clientId; + } + + public ClientID getClientId() { + return clientId; + } + + @Override + public void write(PacketHandler packetHandler, ObjectOutputStream outputStream) throws IOException { + if (clientId == null) throw new IOException("UdpBindPacket.clientId is null"); + UUID uuid = clientId.uuid(); + outputStream.writeLong(uuid.getMostSignificantBits()); + outputStream.writeLong(uuid.getLeastSignificantBits()); + } + + @Override + public void read(PacketHandler packetHandler, ObjectInputStream inputStream) throws IOException { + long msb = inputStream.readLong(); + long lsb = inputStream.readLong(); + this.clientId = new ClientID(new UUID(msb, lsb)); + // Server-side binding is handled by UDP receiver, because it knows the remote address. + if (packetHandler.getClientInstance() != null) { + packetHandler.getClientInstance().setClientId(this.clientId); + } + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectionHandler.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectionHandler.java index c434e2f..0e4f296 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectionHandler.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectionHandler.java @@ -1,40 +1,123 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + package dev.unlegitdqrk.unlegitlibrary.network.system.server; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ClientIDPacket; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.*; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedFailedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_UnknownObjectReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send.S_PacketSendEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send.S_PacketSendFailedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect.ConnectionHandlerConnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect.ConnectionHandlerDisconnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect.ConnectionHandlerFullyDisconnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.DtlsEndpoint; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.UdpPacketCodec; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.net.SocketAddress; import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Objects; +/** + * Handles one connected client socket and dispatches received packets. + * + *

Supports TCP (TLS) and UDP (DTLS). UDP is bound after TCP identity assignment.

+ */ public final class ConnectionHandler { + private final NetworkServer server; + private final ClientID clientId; + private final Thread receiveThread; private SSLSocket socket; - private int clientID; private ObjectOutputStream outputStream; private ObjectInputStream inputStream; + private volatile SocketAddress udpRemoteAddress; + private volatile DtlsEndpoint.DtlsSession udpSession; + private volatile DtlsEndpoint dtlsEndpoint; - public ConnectionHandler(NetworkServer server, SSLSocket socket, int clientID) throws IOException, ClassNotFoundException { - this.server = server; - this.socket = socket; - this.clientID = clientID; + /** + * Creates a new connection handler for an already accepted SSL socket. + * + * @param server owning server + * @param socket ssl socket + * @param clientId server-assigned client identity + * @throws IOException if stream creation fails + * @throws ClassNotFoundException if packet serialization fails + */ + public ConnectionHandler(NetworkServer server, SSLSocket socket, ClientID clientId) throws IOException, ClassNotFoundException { + this.server = Objects.requireNonNull(server, "server"); + this.socket = Objects.requireNonNull(socket, "socket"); + this.clientId = Objects.requireNonNull(clientId, "clientId"); - outputStream = new ObjectOutputStream(socket.getOutputStream()); - inputStream = new ObjectInputStream(socket.getInputStream()); + this.outputStream = new ObjectOutputStream(socket.getOutputStream()); + this.inputStream = new ObjectInputStream(socket.getInputStream()); - receiveThread.start(); + this.receiveThread = new Thread(this::receive, "ConnectionHandler-TCP-Receive-" + clientId.uuid()); + this.receiveThread.start(); - sendPacket(new ClientIDPacket()); - server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this)); + // Send identity via TCP first. + sendPacket(new ClientIDPacket(this.clientId), Transport.TCP); + + // Transport-specific connect event (TCP) + server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this, Transport.TCP)); + } + + /** + * Attaches a DTLS/UDP remote endpoint to this already TCP-authenticated connection. + * + * @param remote remote UDP address + * @param session DTLS session for the remote + * @param endpoint DTLS endpoint + */ + public void attachUdp(SocketAddress remote, DtlsEndpoint.DtlsSession session, DtlsEndpoint endpoint) { + this.udpRemoteAddress = Objects.requireNonNull(remote, "remote"); + this.udpSession = Objects.requireNonNull(session, "session"); + this.dtlsEndpoint = Objects.requireNonNull(endpoint, "endpoint"); + + // Transport-specific connect event (UDP) + server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this, Transport.UDP)); + } + + public SocketAddress getUdpRemoteAddress() { + return udpRemoteAddress; + } + + public boolean isUdpAttached() { + return udpRemoteAddress != null && udpSession != null && udpSession.isHandshakeComplete(); + } + + public boolean isFullyConnected() { + boolean tcpOk = isConnected(); + boolean udpOk = isUdpAttached(); + + if (server.getTransportPolicy().required().contains(Transport.TCP) && !tcpOk) return false; + return !server.getTransportPolicy().required().contains(Transport.UDP) || udpOk; } @Override public boolean equals(Object obj) { - if (!(obj instanceof ConnectionHandler target)) return false; - return target.getClientID() == clientID; + if (this == obj) return true; + if (!(obj instanceof ConnectionHandler other)) return false; + return clientId.equals(other.clientId); + } + + @Override + public int hashCode() { + return clientId.hashCode(); } public SSLSocket getSocket() { @@ -53,70 +136,140 @@ public final class ConnectionHandler { return server; } - public int getClientID() { - return clientID; - } public final Thread receiveThread = new Thread(this::receive); + public ClientID getClientId() { + return clientId; + } public boolean isConnected() { - return socket != null && socket.isConnected() && !socket.isClosed() && receiveThread.isAlive(); + return socket != null && socket.isConnected() && !socket.isClosed() + && receiveThread.isAlive() && !receiveThread.isInterrupted(); } public synchronized boolean disconnect() { - if (!isConnected()) return false; - if (receiveThread.isAlive()) receiveThread.interrupt(); + boolean wasTcpConnected = isConnected(); + boolean wasUdpConnected = isUdpAttached(); + boolean wasFullyConnected = isFullyConnected(); + + if (!wasTcpConnected && !wasUdpConnected) { + // still cleanup + cleanup(); + return false; + } try { - outputStream.close(); - inputStream.close(); - socket.close(); + receiveThread.interrupt(); + + if (outputStream != null) outputStream.close(); + if (inputStream != null) inputStream.close(); + if (socket != null) socket.close(); } catch (IOException ignored) { } - socket = null; - outputStream = null; - inputStream = null; - clientID = -1; - server.getConnectionHandlers().remove(this); - server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this)); + // Emit transport-specific disconnect events + if (wasUdpConnected) { + server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this, Transport.UDP)); + } + if (wasTcpConnected) { + server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this, Transport.TCP)); + } + + cleanup(); + + if (wasFullyConnected) { + server.getEventManager().executeEvent(new ConnectionHandlerFullyDisconnectedEvent( + this, + server.getTransportPolicy().required().toArray(new Transport[0]) + )); + } return true; } - public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { + private void cleanup() { + socket = null; + outputStream = null; + inputStream = null; + + udpRemoteAddress = null; + udpSession = null; + dtlsEndpoint = null; + + server.getConnectionHandlers().remove(this); + } + + /** + * Sends a packet via the selected transport. + * + * @param packet packet to send + * @param transport target transport + * @return true if sent, false if not possible + * @throws IOException on I/O errors + * @throws ClassNotFoundException on serialization errors + */ + public boolean sendPacket(Packet packet, Transport transport) throws IOException, ClassNotFoundException { + Objects.requireNonNull(packet, "packet"); + Objects.requireNonNull(transport, "transport"); + + if (!server.getTransportPolicy().supported().contains(transport)) { + return false; + } + + return switch (transport) { + case TCP -> sendTcp(packet); + case UDP -> sendUdp(packet); + }; + } + + private boolean sendTcp(Packet packet) throws IOException, ClassNotFoundException { if (!isConnected()) return false; + boolean sent = server.getPacketHandler().sendPacket(packet, outputStream); - - if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this)); - else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this)); - + if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this, Transport.TCP)); + else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this, Transport.TCP)); return sent; } + private boolean sendUdp(Packet packet) throws IOException, ClassNotFoundException { + if (!isUdpAttached()) return false; + + DtlsEndpoint endpoint = dtlsEndpoint; + if (endpoint == null) return false; + + ByteBuffer encoded = UdpPacketCodec.encode(server.getPacketHandler(), packet); + endpoint.sendApplication(udpRemoteAddress, encoded); + + server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this, Transport.UDP)); + return true; + } + private void receive() { while (isConnected()) { try { Object received = inputStream.readObject(); - if (received instanceof Integer) { - int id = (Integer) received; + if (received instanceof Integer id) { if (server.getPacketHandler().isPacketIDRegistered(id)) { Packet packet = server.getPacketHandler().getPacketByID(id); - if (server.getPacketHandler().handlePacket(id, packet, inputStream)) - server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet)); - else server.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet)); - } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); - } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); + boolean ok = server.getPacketHandler().handlePacket(id, packet, inputStream); + if (ok) + server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet, Transport.TCP)); + else + server.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet, Transport.TCP)); + } else { + server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this, Transport.TCP)); + } + } else { + server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this, Transport.TCP)); + } } catch (SocketException se) { disconnect(); } catch (Exception ex) { - if (server.getLogger() != null) - server.getLogger().exception("Receive thread exception for client " + clientID, ex); - else System.err.println("Receive thread exception for client " + clientID + ": " + ex.getMessage()); + if (server.getLogger() != null) { + server.getLogger().exception("Receive thread exception for client " + clientId, ex); + } disconnect(); } } } - - -} \ No newline at end of file +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java index ca25387..1b323be 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java @@ -1,176 +1,480 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + package dev.unlegitdqrk.unlegitlibrary.network.system.server; import dev.unlegitdqrk.unlegitlibrary.event.EventManager; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ClientIDPacket; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.IncomingConnectionEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindPacket; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_UnknownObjectReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect.ConnectionHandlerFullyConnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming.TCPIncomingConnectionEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming.UDPIncomingConnectionEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.DtlsEndpoint; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.UdpPacketCodec; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportPolicy; import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils; import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider; import dev.unlegitdqrk.unlegitlibrary.utils.Logger; import javax.net.ssl.*; import java.io.File; -import java.io.FileInputStream; +import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; import java.security.KeyStore; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +/** + * Hybrid server supporting TCP (TLS) and UDP (DTLS) transports. + * + *

Supports policy combinations: + *

+ */ public final class NetworkServer { - private final int port; + + private final int tcpPort; + private final int udpPort; + private final PacketHandler packetHandler; private final EventManager eventManager; private final Logger logger; - private final int timeout; - private final SSLServerSocketFactory sslServerSocketFactory; - private final List connectionHandlers = new ArrayList<>(); - private final boolean requireClientCert; - private SSLServerSocket serverSocket; - private final Thread incomingThread = new Thread(this::incomingConnections); - private NetworkServer(int port, PacketHandler packetHandler, EventManager eventManager, - Logger logger, int timeout, SSLServerSocketFactory factory, boolean requireClientCert) { - this.port = port; - this.packetHandler = packetHandler; - this.eventManager = eventManager; + private final int timeout; + private final SSLServerSocketFactory tlsServerSocketFactory; + private final SSLContext dtlsContext; + + private final TransportPolicy transportPolicy; + private final boolean requireClientCert; + private final List connectionHandlers = Collections.synchronizedList(new ArrayList<>()); + private final Map handlerByClientId = new ConcurrentHashMap<>(); + private SSLServerSocket tcpServerSocket; + private DatagramChannel udpChannel; + private DtlsEndpoint dtlsEndpoint; + private volatile Thread tcpAcceptThread; + private volatile Thread udpThread; + + private NetworkServer( + int tcpPort, + int udpPort, + PacketHandler packetHandler, + EventManager eventManager, + Logger logger, + int timeout, + SSLServerSocketFactory tlsFactory, + SSLContext dtlsContext, + TransportPolicy transportPolicy, + boolean requireClientCert + ) { + this.tcpPort = tcpPort; + this.udpPort = udpPort; + + this.packetHandler = Objects.requireNonNull(packetHandler, "packetHandler"); + this.eventManager = Objects.requireNonNull(eventManager, "eventManager"); this.logger = logger; + this.timeout = timeout; - this.sslServerSocketFactory = factory; + this.tlsServerSocketFactory = tlsFactory; + this.dtlsContext = dtlsContext; + + this.transportPolicy = Objects.requireNonNull(transportPolicy, "transportPolicy"); + this.requireClientCert = requireClientCert; this.packetHandler.setServerInstance(this); this.packetHandler.registerPacket(new ClientIDPacket()); - this.requireClientCert = requireClientCert; + this.packetHandler.registerPacket(new UdpBindPacket()); } - public List getConnectionHandlers() { - return connectionHandlers; + /** + * Returns the configured transport policy. + * + * @return policy + */ + public TransportPolicy getTransportPolicy() { + return transportPolicy; } - public ConnectionHandler getConnectionHandlerByID(int clientID) { - for (ConnectionHandler connectionHandler : connectionHandlers) - if (connectionHandler.getClientID() == clientID) return connectionHandler; - return null; + /** + * Returns the TCP port (TLS). + * + * @return tcp port + */ + public int getTcpPort() { + return tcpPort; } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof NetworkServer target)) return false; - return super.equals(obj); - } - - public int getPort() { - return port; + /** + * Returns the UDP port (DTLS). + * + * @return udp port + */ + public int getUdpPort() { + return udpPort; } + /** + * Returns packet handler. + * + * @return packet handler + */ public PacketHandler getPacketHandler() { return packetHandler; } - public Logger getLogger() { - return logger; - } - - public SSLServerSocket getServerSocket() { - return serverSocket; - } - + /** + * Returns event manager. + * + * @return event manager + */ public EventManager getEventManager() { return eventManager; } - public boolean start() { + /** + * Returns logger. + * + * @return logger (may be null) + */ + public Logger getLogger() { + return logger; + } + + /** + * Returns current DTLS endpoint instance (may be null if UDP not supported or not started). + * + * @return dtls endpoint + */ + public DtlsEndpoint getDtlsEndpoint() { + return dtlsEndpoint; + } + + /** + * Returns live connection handlers. + * + * @return list of handlers + */ + public List getConnectionHandlers() { + return connectionHandlers; + } + + /** + * Looks up a handler by client id. + * + * @param clientId client id + * @return handler or null + */ + public ConnectionHandler getConnectionHandlerByClientId(ClientID clientId) { + return handlerByClientId.get(clientId); + } + + /** + * Starts the server according to the configured transport policy. + * + * @return true if started successfully + */ + public synchronized boolean start() { try { - serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); - serverSocket.setNeedClientAuth(requireClientCert); - serverSocket.setSoTimeout(timeout); - serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"}); - incomingThread.start(); - if (logger != null) logger.log("Server started on port " + port); - else System.out.println("Server started on port " + port); + if (transportPolicy.supported().contains(Transport.TCP)) { + if (tlsServerSocketFactory == null) { + throw new IllegalStateException("TLS ServerSocketFactory missing (TCP supported)."); + } + if (tcpPort <= 0) { + throw new IllegalStateException("TCP port not set (TCP supported)."); + } + + tcpServerSocket = (SSLServerSocket) tlsServerSocketFactory.createServerSocket(tcpPort); + tcpServerSocket.setNeedClientAuth(requireClientCert); + tcpServerSocket.setSoTimeout(timeout); + tcpServerSocket.setEnabledProtocols(new String[]{"TLSv1.3"}); + + tcpAcceptThread = new Thread(this::acceptTcpLoop, "NetworkServer-TCP-Acceptor"); + tcpAcceptThread.start(); + } + + if (transportPolicy.supported().contains(Transport.UDP)) { + if (dtlsContext == null) { + throw new IllegalStateException("DTLS SSLContext missing (UDP supported)."); + } + if (udpPort <= 0) { + throw new IllegalStateException("UDP port not set (UDP supported)."); + } + + udpChannel = DatagramChannel.open(); + udpChannel.bind(new InetSocketAddress(udpPort)); + udpChannel.configureBlocking(false); + + dtlsEndpoint = new DtlsEndpoint( + udpChannel, + dtlsContext, + false, + 1400, + timeout, + this::onDtlsApplicationData + ); + + udpThread = new Thread(this::udpLoop, "NetworkServer-UDP-DTLS"); + udpThread.start(); + } + + logInfo("Server started. tcp=" + tcpPort + ", udp=" + udpPort + + ", supported=" + transportPolicy.supported() + + ", required=" + transportPolicy.required()); return true; } catch (Exception e) { - if (logger != null) logger.exception("Failed to start", e); - else System.err.println("Failed to start: " + e.getMessage()); + logException("Failed to start", e); + stop(); return false; } } - public boolean stop() { - for (ConnectionHandler connectionHandler : new ArrayList<>(connectionHandlers)) connectionHandler.disconnect(); - incomingThread.interrupt(); + /** + * Stops the server and disconnects clients. + * + * @return true if stopped successfully + */ + public synchronized boolean stop() { + for (ConnectionHandler h : new ArrayList<>(connectionHandlers)) { + try { + h.disconnect(); + } catch (Exception ignored) { + } + } + try { - serverSocket.close(); - serverSocket = null; - if (logger != null) logger.log("Server stopped"); - else System.out.println("Server stopped"); + if (tcpAcceptThread != null) tcpAcceptThread.interrupt(); + if (udpThread != null) udpThread.interrupt(); + + if (tcpServerSocket != null) tcpServerSocket.close(); + tcpServerSocket = null; + + if (udpChannel != null) udpChannel.close(); + udpChannel = null; + + dtlsEndpoint = null; + + handlerByClientId.clear(); + connectionHandlers.clear(); + + logInfo("Server stopped"); return true; } catch (Exception e) { - if (logger != null) logger.exception("Failed to stop", e); - else System.err.println("Failed to stop: " + e.getMessage()); + logException("Failed to stop", e); return false; } } - private void incomingConnections() { + private void acceptTcpLoop() { try { - while (!serverSocket.isClosed()) { - Socket socket = serverSocket.accept(); + while (tcpServerSocket != null && !tcpServerSocket.isClosed() && !Thread.currentThread().isInterrupted()) { + Socket socket = tcpServerSocket.accept(); if (!(socket instanceof SSLSocket ssl)) { socket.close(); continue; } + ssl.setTcpNoDelay(true); ssl.setSoTimeout(timeout); + try { ssl.startHandshake(); } catch (Exception handshakeEx) { - if (logger != null) logger.exception("Handshake failed", handshakeEx); - else System.err.println("Handshake failed: " + handshakeEx.getMessage()); - ssl.close(); + logException("TLS handshake failed", handshakeEx); + try { + ssl.close(); + } catch (Exception ignored) { + } continue; } - IncomingConnectionEvent event = new IncomingConnectionEvent(this, ssl); + TCPIncomingConnectionEvent event = new TCPIncomingConnectionEvent(this, ssl); eventManager.executeEvent(event); if (event.isCancelled()) { - ssl.close(); + try { + ssl.close(); + } catch (Exception ignored) { + } continue; } try { - ConnectionHandler connectionHandler = new ConnectionHandler(this, ssl, connectionHandlers.size() + 1); - connectionHandlers.add(connectionHandler); - } catch (Exception exception) { - ssl.close(); - continue; + ClientID clientId = ClientID.random(); + ConnectionHandler handler = new ConnectionHandler(this, ssl, clientId); + connectionHandlers.add(handler); + handlerByClientId.put(clientId, handler); + + // NOTE: + // TCP connect event is fired inside ConnectionHandler constructor (Transport.TCP). + // FullyConnected will be fired later after UDP bind (if policy requires UDP). + } catch (Exception ex) { + try { + ssl.close(); + } catch (Exception ignored) { + } } } } catch (Exception e) { + if (!Thread.currentThread().isInterrupted()) { + logException("TCP accept loop failed", e); + } + } + } + + private void udpLoop() { + try { + while (udpChannel != null && udpChannel.isOpen() && !Thread.currentThread().isInterrupted()) { + DtlsEndpoint endpoint = dtlsEndpoint; + if (endpoint != null) { + endpoint.poll(); + } + Thread.onSpinWait(); + } + } catch (Exception e) { + if (!Thread.currentThread().isInterrupted()) { + logException("UDP loop failed", e); + } + } + } + + private void onDtlsApplicationData(SocketAddress remote, ByteBuffer data) { + try { + UDPIncomingConnectionEvent event = + new UDPIncomingConnectionEvent(this, remote, data); + eventManager.executeEvent(event); + if (event.isCancelled()) { + return; + } + + Packet decoded = UdpPacketCodec.decodeAndHandle(packetHandler, data); + if (decoded == null) { + // No handler associated here; keep null for handler + eventManager.executeEvent(new S_UnknownObjectReceivedEvent(data, null, Transport.UDP)); + return; + } + + // Bind flow: DTLS is up, now bind remote <-> clientId using UdpBindPacket(ClientID) + if (decoded instanceof UdpBindPacket bind) { + ConnectionHandler handler = handlerByClientId.get(bind.getClientId()); + if (handler == null) { + // Unknown/expired client id - ignore + return; + } + + boolean wasFullyConnected = handler.isFullyConnected(); + + // attachUdp fires ConnectionHandlerConnectedEvent(Transport.UDP) internally + DtlsEndpoint endpoint = dtlsEndpoint; + if (endpoint == null) { + return; + } + handler.attachUdp(remote, endpoint.session(remote), endpoint); + + boolean nowFullyConnected = handler.isFullyConnected(); + if (!wasFullyConnected && nowFullyConnected) { + eventManager.executeEvent(new ConnectionHandlerFullyConnectedEvent( + handler, + transportPolicy.required().toArray(new Transport[0]) + )); + } + return; + } + + // Route other UDP packets by already bound remote address + ConnectionHandler bound = findHandlerByUdpRemote(remote); + if (bound != null) { + eventManager.executeEvent(new S_PacketReceivedEvent(bound, decoded, Transport.UDP)); + } + } catch (Exception ignored) { + // best effort: drop + } + } + + /** + * Finds a handler by its bound UDP remote address. + * + * @param remote remote address + * @return handler or null + */ + public ConnectionHandler findHandlerByUdpRemote(SocketAddress remote) { + synchronized (connectionHandlers) { + for (ConnectionHandler h : connectionHandlers) { + if (remote != null && remote.equals(h.getUdpRemoteAddress())) { + return h; + } + } + } + return null; + } + + private void logInfo(String msg) { + if (logger != null) logger.log(msg); + else System.out.println(msg); + } + + private void logException(String msg, Exception e) { + if (logger != null) logger.exception(msg, e); + else { + System.err.println(msg + ": " + e.getMessage()); e.printStackTrace(); } } // --- Builder --- public static class ServerBuilder extends DefaultMethodsOverrider { - private int port; + + private int tcpPort; + private int udpPort; + private PacketHandler packetHandler; private EventManager eventManager; private Logger logger; + private int timeout = 5000; - private SSLServerSocketFactory factory; + + private SSLServerSocketFactory tlsFactory; + private SSLContext dtlsContext; + + private TransportPolicy transportPolicy = TransportPolicy.bothRequired(); private boolean requireClientCert; + private File caFolder; private File serverCertFile; private File serverKeyFile; - public static SSLServerSocketFactory createSSLServerSocketFactory(File caFolder, File serverCert, File serverKey) throws Exception { - // TrustStore (Root-CAs) + /** + * Creates a TLS (TCP) server socket factory using your PEM setup. + * + * @param caFolder root CA folder + * @param serverCert server certificate (PEM) + * @param serverKey server key (PEM) + * @return TLS server socket factory + * @throws Exception if configuration fails + */ + public static SSLServerSocketFactory createTLSServerSocketFactory(File caFolder, File serverCert, File serverKey) throws Exception { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); int caIndex = 1; - for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { - try (FileInputStream fis = new FileInputStream(caFile)) { + File[] caFiles = caFolder.listFiles((f) -> f.getName().endsWith(".pem")); + if (caFiles != null) { + for (File caFile : caFiles) { java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile); trustStore.setCertificateEntry("ca" + (caIndex++), cert); } @@ -181,6 +485,7 @@ public final class NetworkServer { KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(null, null); + java.security.PrivateKey key = PemUtils.loadPrivateKey(serverKey); java.security.cert.Certificate cert = PemUtils.loadCertificate(serverCert); keyStore.setKeyEntry("server", key, null, new java.security.cert.Certificate[]{cert}); @@ -188,13 +493,57 @@ public final class NetworkServer { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, null); - SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext.getServerSocketFactory(); + SSLContext tls = SSLContext.getInstance("TLSv1.3"); + tls.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return tls.getServerSocketFactory(); } - public ServerBuilder setPort(int port) { - this.port = port; + /** + * Creates a DTLS SSLContext using the same key/trust setup. + * + * @param caFolder root CA folder + * @param serverCert server certificate (PEM) + * @param serverKey server key (PEM) + * @return DTLS context + * @throws Exception if configuration fails + */ + public static SSLContext createDTLSContext(File caFolder, File serverCert, File serverKey) throws Exception { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + + int caIndex = 1; + File[] caFiles = caFolder.listFiles((f) -> f.getName().endsWith(".pem")); + if (caFiles != null) { + for (File caFile : caFiles) { + java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile); + trustStore.setCertificateEntry("ca" + (caIndex++), cert); + } + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(null, null); + + java.security.PrivateKey key = PemUtils.loadPrivateKey(serverKey); + java.security.cert.Certificate cert = PemUtils.loadCertificate(serverCert); + keyStore.setKeyEntry("server", key, null, new java.security.cert.Certificate[]{cert}); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); + + return DtlsEndpoint.createDtlsContext(kmf.getKeyManagers(), tmf.getTrustManagers()); + } + + public ServerBuilder setTcpPort(int port) { + this.tcpPort = port; + return this; + } + + public ServerBuilder setUdpPort(int port) { + this.udpPort = port; return this; } @@ -213,13 +562,13 @@ public final class NetworkServer { return this; } - public ServerBuilder setTimeout(int timeout) { - this.timeout = timeout; + public ServerBuilder setTimeout(int timeoutMillis) { + this.timeout = timeoutMillis; return this; } - public ServerBuilder setSSLServerSocketFactory(SSLServerSocketFactory factory) { - this.factory = factory; + public ServerBuilder setTransportPolicy(TransportPolicy policy) { + this.transportPolicy = policy; return this; } @@ -239,15 +588,53 @@ public final class NetworkServer { return this; } + public ServerBuilder setTLSServerSocketFactory(SSLServerSocketFactory factory) { + this.tlsFactory = factory; + return this; + } + + public ServerBuilder setDTLSContext(SSLContext dtlsContext) { + this.dtlsContext = dtlsContext; + return this; + } + public NetworkServer build() { - if (factory == null && caFolder != null && serverCertFile != null && serverKeyFile != null) { + if (packetHandler == null) throw new IllegalStateException("PacketHandler not set"); + if (eventManager == null) throw new IllegalStateException("EventManager not set"); + if (transportPolicy == null) throw new IllegalStateException("TransportPolicy not set"); + + if (transportPolicy.supported().contains(Transport.TCP) && tcpPort <= 0) { + throw new IllegalStateException("TCP port not set"); + } + if (transportPolicy.supported().contains(Transport.UDP) && udpPort <= 0) { + throw new IllegalStateException("UDP port not set"); + } + + if ((tlsFactory == null || dtlsContext == null) && caFolder != null && serverCertFile != null && serverKeyFile != null) { try { - factory = createSSLServerSocketFactory(caFolder, serverCertFile, serverKeyFile); + if (tlsFactory == null && transportPolicy.supported().contains(Transport.TCP)) { + tlsFactory = createTLSServerSocketFactory(caFolder, serverCertFile, serverKeyFile); + } + if (dtlsContext == null && transportPolicy.supported().contains(Transport.UDP)) { + dtlsContext = createDTLSContext(caFolder, serverCertFile, serverKeyFile); + } } catch (Exception e) { - throw new RuntimeException("Failed to create SSLServerSocketFactory", e); + throw new RuntimeException("Failed to create TLS/DTLS configuration", e); } } - return new NetworkServer(port, packetHandler, eventManager, logger, timeout, factory, requireClientCert); + + return new NetworkServer( + tcpPort, + udpPort, + packetHandler, + eventManager, + logger, + timeout, + tlsFactory, + dtlsContext, + transportPolicy, + requireClientCert + ); } } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServerUdpHooks.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServerUdpHooks.java new file mode 100644 index 0000000..05e889c --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServerUdpHooks.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server; + +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindPacket; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect.ConnectionHandlerFullyConnectedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.DtlsEndpoint; +import dev.unlegitdqrk.unlegitlibrary.network.system.udp.UdpPacketCodec; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * Helper methods for server-side DTLS/UDP handling. + * + *

Drop this into your NetworkServer class (replace your existing onDtlsApplicationData).

+ */ +final class NetworkServerUdpHooks { + + private NetworkServerUdpHooks() { + } + + /** + * Processes decrypted DTLS application data. + * + * @param server server + * @param remote remote address + * @param data decrypted data + */ + static void onDtlsApplicationData(NetworkServer server, SocketAddress remote, ByteBuffer data) { + Objects.requireNonNull(server, "server"); + Objects.requireNonNull(remote, "remote"); + Objects.requireNonNull(data, "data"); + + try { + Packet decoded = UdpPacketCodec.decodeAndHandle(server.getPacketHandler(), data); + if (decoded == null) { + return; + } + + if (decoded instanceof UdpBindPacket bind) { + // Bind remote to existing TCP handler + ConnectionHandler handler = server.getConnectionHandlerByClientId(bind.getClientId()); + if (handler == null) { + return; + } + if (!handler.isConnected()) { + return; + } + + DtlsEndpoint endpoint = server.getDtlsEndpoint(); + if (endpoint == null) { + return; + } + + boolean wasFullyConnected = handler.isFullyConnected(); + handler.attachUdp(remote, endpoint.session(remote), endpoint); + boolean nowFullyConnected = handler.isFullyConnected(); + + if (!wasFullyConnected && nowFullyConnected) { + server.getEventManager().executeEvent( + new ConnectionHandlerFullyConnectedEvent(handler, server.getTransportPolicy().required().toArray(new Transport[0])) + ); + } + + return; + } + + // For normal UDP packets: route to the bound handler (if any) + ConnectionHandler bound = server.findHandlerByUdpRemote(remote); + if (bound != null) { + server.getEventManager().executeEvent(new S_PacketReceivedEvent(bound, decoded, Transport.UDP)); + } + } catch (Exception ignored) { + // best effort: drop + } + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerConnectedEvent.java deleted file mode 100644 index c666bf2..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerConnectedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class ConnectionHandlerConnectedEvent extends Event { - private final ConnectionHandler connectionHandler; - - public ConnectionHandlerConnectedEvent(ConnectionHandler connectionHandler) { - this.connectionHandler = connectionHandler; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerDisconnectedEvent.java deleted file mode 100644 index 714fe4c..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/ConnectionHandlerDisconnectedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class ConnectionHandlerDisconnectedEvent extends Event { - private final ConnectionHandler connectionHandler; - - - public ConnectionHandlerDisconnectedEvent(ConnectionHandler connectionHandler) { - this.connectionHandler = connectionHandler; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java deleted file mode 100644 index bab9f27..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.CancellableEvent; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer; - -import javax.net.ssl.SSLSocket; - -public final class IncomingConnectionEvent extends CancellableEvent { - private final NetworkServer server; - private final SSLSocket socket; - - public IncomingConnectionEvent(NetworkServer server, SSLSocket socket) { - this.server = server; - this.socket = socket; - } - - public SSLSocket getSocket() { - return socket; - } - - public NetworkServer getServer() { - return server; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedEvent.java deleted file mode 100644 index 40e304f..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class S_PacketReceivedEvent extends Event { - private final ConnectionHandler connectionHandler; - private final Packet packet; - - public S_PacketReceivedEvent(ConnectionHandler connectionHandler, Packet packet) { - this.connectionHandler = connectionHandler; - this.packet = packet; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedFailedEvent.java deleted file mode 100644 index 0676327..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketReceivedFailedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class S_PacketReceivedFailedEvent extends Event { - private final ConnectionHandler connectionHandler; - private final Packet packet; - - public S_PacketReceivedFailedEvent(ConnectionHandler connectionHandler, Packet packet) { - this.connectionHandler = connectionHandler; - this.packet = packet; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendEvent.java deleted file mode 100644 index 697c51f..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class S_PacketSendEvent extends Event { - private final Packet packet; - private final ConnectionHandler connectionHandler; - - public S_PacketSendEvent(Packet packet, ConnectionHandler connectionHandler) { - this.packet = packet; - this.connectionHandler = connectionHandler; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendFailedEvent.java deleted file mode 100644 index ce87b1a..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_PacketSendFailedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class S_PacketSendFailedEvent extends Event { - private final Packet packet; - private final ConnectionHandler connectionHandler; - - public S_PacketSendFailedEvent(Packet packet, ConnectionHandler connectionHandler) { - this.packet = packet; - this.connectionHandler = connectionHandler; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } - - public Packet getPacket() { - return packet; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_UnknownObjectReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_UnknownObjectReceivedEvent.java deleted file mode 100644 index b59d7f2..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/S_UnknownObjectReceivedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.unlegitdqrk.unlegitlibrary.network.system.server.events; - -import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; -import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; - -public final class S_UnknownObjectReceivedEvent extends Event { - - private final Object received; - private final ConnectionHandler connectionHandler; - - public S_UnknownObjectReceivedEvent(Object received, ConnectionHandler connectionHandler) { - this.received = received; - this.connectionHandler = connectionHandler; - } - - public ConnectionHandler getConnectionHandler() { - return connectionHandler; - } - - public Object getReceived() { - return received; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedEvent.java new file mode 100644 index 0000000..f633eef --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet was received on the server on a specific transport. + */ +public final class S_PacketReceivedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet received event. + * + * @param connectionHandler handler + * @param packet packet + * @param transport transport used + */ + public S_PacketReceivedEvent(ConnectionHandler connectionHandler, Packet packet, Transport transport) { + this.connectionHandler = connectionHandler; + this.packet = packet; + this.transport = transport; + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedFailedEvent.java new file mode 100644 index 0000000..1e0d292 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_PacketReceivedFailedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet receive failed on the server on a specific transport. + */ +public final class S_PacketReceivedFailedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Packet packet; + private final Transport transport; + + /** + * Creates a new packet receive failed event. + * + * @param connectionHandler handler + * @param packet packet + * @param transport transport used + */ + public S_PacketReceivedFailedEvent(ConnectionHandler connectionHandler, Packet packet, Transport transport) { + this.connectionHandler = connectionHandler; + this.packet = packet; + this.transport = transport; + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_UnknownObjectReceivedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_UnknownObjectReceivedEvent.java new file mode 100644 index 0000000..2d3f4e3 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/receive/S_UnknownObjectReceivedEvent.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when an unknown object was received on a specific transport. + */ +public final class S_UnknownObjectReceivedEvent extends Event { + + private final Object received; + private final ConnectionHandler connectionHandler; + private final Transport transport; + + /** + * Creates a new event. + * + * @param received received object + * @param connectionHandler handler + * @param transport transport + */ + public S_UnknownObjectReceivedEvent(Object received, ConnectionHandler connectionHandler, Transport transport) { + this.received = received; + this.connectionHandler = connectionHandler; + this.transport = transport; + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Object getReceived() { + return received; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java new file mode 100644 index 0000000..206bb6d --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet was sent by the server on a specific transport. + */ +public final class S_PacketSendEvent extends Event { + + private final Packet packet; + private final ConnectionHandler connectionHandler; + private final Transport transport; + + /** + * Creates a new packet send event. + * + * @param packet packet + * @param connectionHandler handler + * @param transport transport used + */ + public S_PacketSendEvent(Packet packet, ConnectionHandler connectionHandler, Transport transport) { + this.packet = packet; + this.connectionHandler = connectionHandler; + this.transport = transport; + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java new file mode 100644 index 0000000..c239a92 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a packet send failed on the server on a specific transport. + */ +public final class S_PacketSendFailedEvent extends Event { + + private final Packet packet; + private final ConnectionHandler connectionHandler; + private final Transport transport; + + /** + * Creates a new packet send failed event. + * + * @param packet packet + * @param connectionHandler handler + * @param transport intended transport + */ + public S_PacketSendFailedEvent(Packet packet, ConnectionHandler connectionHandler, Transport transport) { + this.packet = packet; + this.connectionHandler = connectionHandler; + this.transport = transport; + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Packet getPacket() { + return packet; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerConnectedEvent.java new file mode 100644 index 0000000..928c278 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerConnectedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when a specific transport becomes connected for a handler. + * + *

Transport-specific: + *

    + *
  • {@link Transport#TCP}: handler created after TLS handshake
  • + *
  • {@link Transport#UDP}: handler received valid UDP bind and attached DTLS session
  • + *
+ */ +public final class ConnectionHandlerConnectedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Transport transport; + + /** + * Creates a new handler connected event. + * + * @param connectionHandler handler + * @param transport connected transport + */ + public ConnectionHandlerConnectedEvent(ConnectionHandler connectionHandler, Transport transport) { + this.connectionHandler = Objects.requireNonNull(connectionHandler, "connectionHandler"); + this.transport = Objects.requireNonNull(transport, "transport"); + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerFullyConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerFullyConnectedEvent.java new file mode 100644 index 0000000..7e43e98 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ConnectionHandlerFullyConnectedEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +/** + * Fired when a connection handler satisfies the server transport policy. + * + *

For your setup this means: TCP connected and UDP (DTLS) bound.

+ */ +public final class ConnectionHandlerFullyConnectedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Transport[] requiredTransports; + + /** + * Creates a new event. + * + * @param connectionHandler handler + * @param requiredTransports required transports now established + */ + public ConnectionHandlerFullyConnectedEvent(ConnectionHandler connectionHandler, Transport[] requiredTransports) { + this.connectionHandler = connectionHandler; + this.requiredTransports = requiredTransports; + } + + /** + * Returns the handler. + * + * @return handler + */ + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + /** + * Returns the required transports that are now established. + * + * @return transports + */ + public Transport[] getRequiredTransports() { + return requiredTransports; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerDisconnectedEvent.java new file mode 100644 index 0000000..c7d2286 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerDisconnectedEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when a specific transport becomes disconnected for a handler. + */ +public final class ConnectionHandlerDisconnectedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Transport transport; + + /** + * Creates a new handler disconnected event. + * + * @param connectionHandler handler + * @param transport disconnected transport + */ + public ConnectionHandlerDisconnectedEvent(ConnectionHandler connectionHandler, Transport transport) { + this.connectionHandler = Objects.requireNonNull(connectionHandler, "connectionHandler"); + this.transport = Objects.requireNonNull(transport, "transport"); + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerFullyDisconnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerFullyDisconnectedEvent.java new file mode 100644 index 0000000..1790c71 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/disconnect/ConnectionHandlerFullyDisconnectedEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.util.Objects; + +/** + * Fired when a handler was fully connected and then became fully disconnected. + * + *

For your policy (TCP+UDP required) this means: + * previously: TCP connected and UDP attached, + * now: requirements are no longer satisfied.

+ */ +public final class ConnectionHandlerFullyDisconnectedEvent extends Event { + + private final ConnectionHandler connectionHandler; + private final Transport[] requiredTransports; + + /** + * Creates a new fully disconnected event. + * + * @param connectionHandler handler + * @param requiredTransports required transports according to policy + */ + public ConnectionHandlerFullyDisconnectedEvent(ConnectionHandler connectionHandler, Transport[] requiredTransports) { + this.connectionHandler = Objects.requireNonNull(connectionHandler, "connectionHandler"); + this.requiredTransports = Objects.requireNonNull(requiredTransports, "requiredTransports"); + } + + public ConnectionHandler getConnectionHandler() { + return connectionHandler; + } + + public Transport[] getRequiredTransports() { + return requiredTransports; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/TCPIncomingConnectionEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/TCPIncomingConnectionEvent.java new file mode 100644 index 0000000..03d16e8 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/TCPIncomingConnectionEvent.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.CancellableEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import javax.net.ssl.SSLSocket; +import java.util.Objects; + +/** + * Fired when an incoming connection attempt reaches the server. + * + *

Currently this event is emitted for TCP/TLS accept only (because UDP/DTLS is bound later via {@code UdpBindPacket}). + * The {@link #getTransport()} field exists to keep the event model transport-aware and future-proof.

+ */ +public final class TCPIncomingConnectionEvent extends CancellableEvent { + + private final NetworkServer server; + private final SSLSocket socket; + private final Transport transport = Transport.TCP; + + /** + * Creates a new incoming connection event. + * + * @param server server instance + * @param socket accepted SSL socket + */ + public TCPIncomingConnectionEvent(NetworkServer server, SSLSocket socket) { + this.server = Objects.requireNonNull(server, "server"); + this.socket = Objects.requireNonNull(socket, "socket"); + } + + /** + * Returns the accepted socket. + * + * @return ssl socket + */ + public SSLSocket getSocket() { + return socket; + } + + /** + * Returns the server instance. + * + * @return server + */ + public NetworkServer getServer() { + return server; + } + + /** + * Returns the transport associated with this incoming connection. + * + * @return transport + */ + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java new file mode 100644 index 0000000..a9c8744 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming; + +import dev.unlegitdqrk.unlegitlibrary.event.impl.CancellableEvent; +import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Transport; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * Fired when an incoming UDP/DTLS datagram is received by the server + * before it is bound to a {@link dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler}. + * + *

This event allows inspection or rejection of: + *

    + *
  • DTLS handshake traffic
  • + *
  • {@code UdpBindPacket}
  • + *
  • Any unbound UDP packet
  • + *
+ * + *

If cancelled, the datagram is silently dropped.

+ */ +public final class UDPIncomingConnectionEvent extends CancellableEvent { + + private final NetworkServer server; + private final SocketAddress remoteAddress; + private final ByteBuffer rawData; + private final Transport transport = Transport.UDP; + + /** + * Creates a new incoming UDP connection/datagram event. + * + * @param server server instance + * @param remoteAddress remote UDP address + * @param rawData raw received datagram (read-only duplicate recommended) + */ + public UDPIncomingConnectionEvent( + NetworkServer server, + SocketAddress remoteAddress, + ByteBuffer rawData + ) { + this.server = Objects.requireNonNull(server, "server"); + this.remoteAddress = Objects.requireNonNull(remoteAddress, "remoteAddress"); + this.rawData = Objects.requireNonNull(rawData, "rawData").asReadOnlyBuffer(); + } + + /** + * Returns the server instance. + * + * @return server + */ + public NetworkServer getServer() { + return server; + } + + /** + * Returns the remote UDP address. + * + * @return remote address + */ + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + /** + * Returns the raw UDP datagram payload. + * + *

The buffer is read-only and positioned at the start of the payload.

+ * + * @return raw datagram data + */ + public ByteBuffer getRawData() { + return rawData; + } + + /** + * Returns the transport type of this incoming connection. + * + * @return {@link Transport#UDP} + */ + public Transport getTransport() { + return transport; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/DtlsEndpoint.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/DtlsEndpoint.java new file mode 100644 index 0000000..241269d --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/DtlsEndpoint.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.udp; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Minimal DTLS endpoint using {@link SSLEngine} over {@link DatagramChannel}. + * + *

This implementation is designed for "best effort" UDP and focuses on: + *

    + *
  • DTLS handshake via SSLEngine
  • + *
  • wrap/unwrap application data into datagrams
  • + *
  • per remote address sessions
  • + *
+ */ +public final class DtlsEndpoint { + + private final DatagramChannel channel; + private final SSLContext sslContext; + private final boolean clientMode; + private final int mtu; + private final int timeoutMillis; + private final ApplicationDataHandler appHandler; + private final Map sessions = new ConcurrentHashMap<>(); + + /** + * Creates a DTLS endpoint. + * + * @param channel underlying datagram channel (bound for server, unbound or bound for client) + * @param sslContext DTLS SSL context (created with "DTLS") + * @param clientMode true for client sessions, false for server sessions + * @param mtu maximum datagram size + * @param timeoutMillis handshake/read timeout for polling + * @param appHandler application data handler + */ + public DtlsEndpoint( + DatagramChannel channel, + SSLContext sslContext, + boolean clientMode, + int mtu, + int timeoutMillis, + ApplicationDataHandler appHandler + ) { + this.channel = Objects.requireNonNull(channel, "channel"); + this.sslContext = Objects.requireNonNull(sslContext, "sslContext"); + this.clientMode = clientMode; + this.mtu = mtu; + this.timeoutMillis = timeoutMillis; + this.appHandler = Objects.requireNonNull(appHandler, "appHandler"); + } + + /** + * Creates a DTLS SSLContext from an existing key+trust configuration. + * + * @param keyManagers key managers + * @param trustManagers trust managers + * @return DTLS SSL context + * @throws Exception on errors + */ + public static SSLContext createDtlsContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception { + SSLContext ctx = SSLContext.getInstance("DTLS"); + ctx.init(keyManagers, trustManagers, new SecureRandom()); + return ctx; + } + + /** + * Gets or creates a DTLS session for a remote address. + * + * @param remote remote address + * @return session + * @throws SSLException if engine creation fails + */ + public DtlsSession session(SocketAddress remote) throws SSLException { + return sessions.computeIfAbsent(remote, r -> { + try { + return new DtlsSession(createEngine(r), r, mtu); + } catch (SSLException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Performs a DTLS handshake for a remote address. + * + *

Client: call this after creating a session and before sending app data.

+ *

Server: call this once you detect a new remote (first datagrams arrive) to complete handshake.

+ * + * @param remote remote address + * @throws IOException on I/O error + * @throws SSLException on TLS error + */ + public void handshake(SocketAddress remote) throws IOException, SSLException { + DtlsSession s = session(remote); + if (s.isHandshakeComplete()) return; + + s.engine().beginHandshake(); + SSLEngineResult.HandshakeStatus hs = s.engine().getHandshakeStatus(); + + ByteBuffer netIn = ByteBuffer.allocate(mtu); + ByteBuffer netOut = ByteBuffer.allocate(mtu); + ByteBuffer app = ByteBuffer.allocate(s.engine().getSession().getApplicationBufferSize()); + + long start = System.currentTimeMillis(); + + while (!s.isHandshakeComplete()) { + if (System.currentTimeMillis() - start > timeoutMillis) { + throw new SSLException("DTLS handshake timed out"); + } + + switch (hs) { + case NEED_WRAP -> { + netOut.clear(); + SSLEngineResult r = s.engine().wrap(ByteBuffer.allocate(0), netOut); + hs = r.getHandshakeStatus(); + + netOut.flip(); + if (netOut.hasRemaining()) { + channel.send(netOut, remote); + } + } + case NEED_UNWRAP -> { + netIn.clear(); + SocketAddress from = channel.receive(netIn); + if (from == null) { + // best effort: keep looping until timeout + continue; + } + if (!from.equals(remote)) { + // ignore other peers here; their sessions will be handled elsewhere + continue; + } + + netIn.flip(); + SSLEngineResult r = s.engine().unwrap(netIn, app); + hs = r.getHandshakeStatus(); + + if (r.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { + // wait for more datagrams + continue; + } + if (r.getStatus() == SSLEngineResult.Status.CLOSED) { + throw new SSLException("DTLS engine closed during handshake"); + } + } + case NEED_TASK -> { + Runnable task; + while ((task = s.engine().getDelegatedTask()) != null) { + task.run(); + } + hs = s.engine().getHandshakeStatus(); + } + case FINISHED, NOT_HANDSHAKING -> s.setHandshakeComplete(true); + } + } + } + + /** + * Sends application bytes through DTLS to a remote peer. + * + * @param remote remote address + * @param applicationData plaintext app data + * @throws IOException on I/O errors + * @throws SSLException on TLS errors + */ + public void sendApplication(SocketAddress remote, ByteBuffer applicationData) throws IOException, SSLException { + DtlsSession s = session(remote); + if (!s.isHandshakeComplete()) { + handshake(remote); + } + + ByteBuffer netOut = ByteBuffer.allocate(mtu); + + while (applicationData.hasRemaining()) { + netOut.clear(); + SSLEngineResult r = s.engine().wrap(applicationData, netOut); + if (r.getStatus() == SSLEngineResult.Status.CLOSED) { + throw new SSLException("DTLS engine closed"); + } + netOut.flip(); + if (netOut.hasRemaining()) { + channel.send(netOut, remote); + } + } + } + + /** + * Polls UDP, unwraps DTLS records and dispatches decrypted application data. + * + *

Run this in a dedicated thread for server and client.

+ * + * @throws IOException on I/O errors + */ + public void poll() throws IOException { + ByteBuffer netIn = ByteBuffer.allocate(mtu); + + SocketAddress from; + while ((from = channel.receive(netIn)) != null) { + netIn.flip(); + + DtlsSession s; + try { + s = session(from); + } catch (RuntimeException re) { + netIn.clear(); + continue; + } + + ByteBuffer app = ByteBuffer.allocate(s.engine().getSession().getApplicationBufferSize()); + + try { + SSLEngineResult r = s.engine().unwrap(netIn, app); + + if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) { + Runnable task; + while ((task = s.engine().getDelegatedTask()) != null) task.run(); + } + + if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) { + // Respond to DTLS handshake flights if needed + ByteBuffer netOut = ByteBuffer.allocate(mtu); + netOut.clear(); + SSLEngineResult wr = s.engine().wrap(ByteBuffer.allocate(0), netOut); + if (wr.getStatus() != SSLEngineResult.Status.CLOSED) { + netOut.flip(); + if (netOut.hasRemaining()) channel.send(netOut, from); + } + } + + if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED + || r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + s.setHandshakeComplete(true); + } + + if (r.getStatus() == SSLEngineResult.Status.CLOSED) { + sessions.remove(from); + netIn.clear(); + continue; + } + + app.flip(); + if (app.hasRemaining() && s.isHandshakeComplete()) { + appHandler.onApplicationData(from, app); + } + } catch (SSLException ignored) { + // best effort: invalid record / handshake mismatch -> drop + } finally { + netIn.clear(); + } + } + } + + private SSLEngine createEngine(SocketAddress remote) throws SSLException { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(clientMode); + + SSLParameters p = engine.getSSLParameters(); + // Prefer DTLSv1.2 (widest support in JSSE DTLS). If your environment supports DTLSv1.3, you can extend this. + p.setProtocols(new String[]{"DTLSv1.2"}); + engine.setSSLParameters(p); + + // For DTLS it's recommended to set a secure random + engine.getSSLParameters(); + return engine; + } + + /** + * Callback for decrypted application data. + */ + public interface ApplicationDataHandler { + /** + * Called when a DTLS session produced decrypted application bytes. + * + * @param remote remote socket address + * @param data decrypted data buffer (position=0, limit=length) + */ + void onApplicationData(SocketAddress remote, ByteBuffer data); + } + + /** + * Holds a per-remote DTLS state. + */ + public static final class DtlsSession { + private final SSLEngine engine; + private final SocketAddress remote; + private final int mtu; + private volatile boolean handshakeComplete; + + private DtlsSession(SSLEngine engine, SocketAddress remote, int mtu) { + this.engine = engine; + this.remote = remote; + this.mtu = mtu; + } + + public SSLEngine engine() { + return engine; + } + + public SocketAddress remote() { + return remote; + } + + public int mtu() { + return mtu; + } + + public boolean isHandshakeComplete() { + return handshakeComplete; + } + + public void setHandshakeComplete(boolean complete) { + this.handshakeComplete = complete; + } + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/UdpPacketCodec.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/UdpPacketCodec.java new file mode 100644 index 0000000..143b720 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/udp/UdpPacketCodec.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.udp; + +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; +import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; + +import java.io.*; +import java.nio.ByteBuffer; + +/** + * Encodes/decodes packets for UDP transport. + * + *

Format: + *

+ * [int packetId][ObjectOutputStream payload bytes...]
+ * 
+ */ +public final class UdpPacketCodec { + + private UdpPacketCodec() { + } + + /** + * Encodes a packet into a byte buffer ready for sending. + * + * @param handler packet handler + * @param packet packet + * @return encoded buffer (position=0, limit=length) + * @throws IOException on I/O errors + * @throws ClassNotFoundException on serialization errors + */ + public static ByteBuffer encode(PacketHandler handler, Packet packet) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + + try (DataOutputStream dos = new DataOutputStream(baos)) { + dos.writeInt(packet.getPacketID()); + + // Keep your existing Packet API (ObjectOutputStream) + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + packet.write(handler, oos); + oos.flush(); + } + } + + byte[] bytes = baos.toByteArray(); + return ByteBuffer.wrap(bytes); + } + + /** + * Decodes a packet id and lets the handler read the payload into a packet instance. + * + * @param handler packet handler + * @param datagram datagram buffer (position=0, limit=length) + * @return decoded packet instance (already read/filled), or {@code null} if unknown id + * @throws IOException on errors + * @throws ClassNotFoundException on errors + */ + public static Packet decodeAndHandle(PacketHandler handler, ByteBuffer datagram) throws IOException, ClassNotFoundException { + ByteArrayInputStream bais = new ByteArrayInputStream(datagram.array(), datagram.position(), datagram.remaining()); + + int id; + try (DataInputStream dis = new DataInputStream(bais)) { + id = dis.readInt(); + } + + if (!handler.isPacketIDRegistered(id)) { + return null; + } + + Packet packet = handler.getPacketByID(id); + + // Now decode remaining bytes with ObjectInputStream + int payloadOffset = 4; + ByteArrayInputStream payload = new ByteArrayInputStream(datagram.array(), datagram.position() + payloadOffset, datagram.remaining() - payloadOffset); + + try (ObjectInputStream ois = new ObjectInputStream(payload)) { + boolean ok = handler.handlePacket(id, packet, ois); + return packet; + } + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/ClientID.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/ClientID.java new file mode 100644 index 0000000..64628d8 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/ClientID.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.utils; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; +import java.util.UUID; + +/** + * Immutable identifier for a client (similar to a Minecraft player's UUID). + */ +public record ClientID(UUID uuid) implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Creates a new {@link ClientID}. + * + * @param uuid backing UUID (must not be {@code null}) + */ + public ClientID(UUID uuid) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + } + + /** + * Generates a random {@link ClientID}. + * + * @return random client id + */ + public static ClientID random() { + return new ClientID(UUID.randomUUID()); + } + + /** + * Returns backing UUID. + * + * @return UUID + */ + @Override + public UUID uuid() { + return uuid; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ClientID other)) return false; + return uuid.equals(other.uuid); + } + + @Override + public String toString() { + return "ClientID{uuid=" + uuid + "}"; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/Transport.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/Transport.java new file mode 100644 index 0000000..e344e58 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/Transport.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.utils; + +import java.util.EnumSet; + +/** + * Supported transport mechanisms for packet sending. + */ +public enum Transport { + TCP, + UDP; + + /** + * Returns a default transport set for a typical hybrid server/client. + * + * @return TCP + UDP set + */ + public static EnumSet both() { + return EnumSet.of(TCP, UDP); + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/TransportPolicy.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/TransportPolicy.java new file mode 100644 index 0000000..152bc4e --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/TransportPolicy.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2026 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ + * See LICENSE-File if exists + */ + +package dev.unlegitdqrk.unlegitlibrary.network.system.utils; + +import java.util.EnumSet; + +/** + * Defines which transports a server supports and which are mandatory to be connected. + */ +public record TransportPolicy(EnumSet supported, EnumSet required) { + + /** + * Creates a new transport policy. + * + * @param supported transports supported by the server + * @param required transports required for a connection to be considered "fully connected" + */ + public TransportPolicy(EnumSet supported, EnumSet required) { + this.supported = EnumSet.copyOf(supported); + this.required = EnumSet.copyOf(required); + + if (!this.supported.containsAll(this.required)) { + throw new IllegalArgumentException("Required transports must be a subset of supported transports."); + } + if (this.supported.isEmpty()) { + throw new IllegalArgumentException("Supported transports cannot be empty."); + } + } + + /** + * Convenience: TCP only. + */ + public static TransportPolicy tcpOnly() { + return new TransportPolicy(EnumSet.of(Transport.TCP), EnumSet.of(Transport.TCP)); + } + + /** + * Convenience: UDP only. + */ + public static TransportPolicy udpOnly() { + return new TransportPolicy(EnumSet.of(Transport.UDP), EnumSet.of(Transport.UDP)); + } + + /** + * Convenience: supports TCP+UDP and requires BOTH simultaneously (your chosen setting). + */ + public static TransportPolicy bothRequired() { + return new TransportPolicy(EnumSet.of(Transport.TCP, Transport.UDP), EnumSet.of(Transport.TCP, Transport.UDP)); + } + + /** + * Supported transports. + * + * @return supported set + */ + @Override + public EnumSet supported() { + return EnumSet.copyOf(supported); + } + + /** + * Required transports. + * + * @return required set + */ + @Override + public EnumSet required() { + return EnumSet.copyOf(required); + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/GenericReflectClass.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/GenericReflectClass.java index a9f3cb8..964adba 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/GenericReflectClass.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/GenericReflectClass.java @@ -10,9 +10,10 @@ import java.lang.reflect.ParameterizedType; */ public abstract class GenericReflectClass { protected final Class persistentClass; + public GenericReflectClass() { this.persistentClass = (Class) - ((ParameterizedType)getClass().getGenericSuperclass()) + ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/ReflectUtils.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/ReflectUtils.java index e1ff52f..4f5cd73 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/ReflectUtils.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/ReflectUtils.java @@ -2,7 +2,7 @@ * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk + * You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/ * See LICENSE-File if exists */ diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/annotation/processing/AnnotationProcessor.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/annotation/processing/AnnotationProcessor.java index f30d86e..455af67 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/annotation/processing/AnnotationProcessor.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/reflections/annotation/processing/AnnotationProcessor.java @@ -13,17 +13,12 @@ import java.lang.reflect.Method; import java.util.Set; public abstract class AnnotationProcessor extends GenericReflectClass { - protected Set> projectClasses; - - protected Set> annotatedTypes; - - protected Set annotatedMethods; - - protected Set annotatedFields; - - protected Set annotatedConstructors; - private final Reflections reflections; + protected Set> projectClasses; + protected Set> annotatedTypes; + protected Set annotatedMethods; + protected Set annotatedFields; + protected Set annotatedConstructors; public AnnotationProcessor(String packageName) { super(); @@ -47,22 +42,25 @@ public abstract class AnnotationProcessor extends GenericR } public void process() { - for(Class type : this.annotatedTypes) + for (Class type : this.annotatedTypes) processType(type); - for(Method method : this.annotatedMethods) + for (Method method : this.annotatedMethods) processMethod(method); - for(Field field : this.annotatedFields) + for (Field field : this.annotatedFields) processField(field); - for(Constructor constructor : this.annotatedConstructors) + for (Constructor constructor : this.annotatedConstructors) processConstructor(constructor); } protected abstract void processType(Class type); + protected abstract void processMethod(Method method); + protected abstract void processField(Field field); + protected abstract void processConstructor(Constructor constructor); } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/string/SimpleEncoderDecoder.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/string/SimpleEncoderDecoder.java index 7863636..a23799e 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/string/SimpleEncoderDecoder.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/string/SimpleEncoderDecoder.java @@ -44,9 +44,9 @@ public class SimpleEncoderDecoder extends DefaultMethodsOverrider { } public record EncoderDecoderKeys(String key) { - public static final EncoderDecoderKeys BIT_KEY_128 = new EncoderDecoderKeys("Bar12345Bar12345"); - public static final EncoderDecoderKeys DEC1632DDCL542 = new EncoderDecoderKeys("Dec1632DDCL542"); - public static final EncoderDecoderKeys SSSHHHHHHHHHHH = new EncoderDecoderKeys("ssshhhhhhhhhhh!!!!"); + public static final EncoderDecoderKeys BIT_KEY_128 = new EncoderDecoderKeys("Bar12345Bar12345"); + public static final EncoderDecoderKeys DEC1632DDCL542 = new EncoderDecoderKeys("Dec1632DDCL542"); + public static final EncoderDecoderKeys SSSHHHHHHHHHHH = new EncoderDecoderKeys("ssshhhhhhhhhhh!!!!"); } } \ No newline at end of file