Added UDP Support, Refactored

This commit is contained in:
Finn
2026-01-18 21:00:56 +01:00
parent 7f0f82a1eb
commit ee790659b5
57 changed files with 3267 additions and 703 deletions

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View File

@@ -6,13 +6,13 @@
<groupId>dev.unlegitdqrk</groupId> <groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>unlegitlibrary</artifactId>
<version>1.6.9</version> <version>1.7.0</version>
<url>https://unlegitdqrk.dev/</url> <url>https://unlegitdqrk.dev/</url>
<description>Just a big library</description> <description>Just a big library</description>
<properties> <properties>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>

View File

@@ -25,14 +25,6 @@ public final class AddonLoader extends DefaultMethodsOverrider {
private final EventManager eventManager; private final EventManager eventManager;
private final Logger logger; private final Logger logger;
public final EventManager getEventManager() {
return eventManager;
}
public final Logger getLogger() {
return logger;
}
public AddonLoader(EventManager eventManager, Logger logger) { public AddonLoader(EventManager eventManager, Logger logger) {
this.addons = new ArrayList<>(); this.addons = new ArrayList<>();
this.loadedClasses = new HashMap<>(); this.loadedClasses = new HashMap<>();
@@ -40,6 +32,14 @@ public final class AddonLoader extends DefaultMethodsOverrider {
this.logger = logger; this.logger = logger;
} }
public EventManager getEventManager() {
return eventManager;
}
public Logger getLogger() {
return logger;
}
public void loadAddonsFromDirectory(File addonFolder) throws IOException { public void loadAddonsFromDirectory(File addonFolder) throws IOException {
if (!addonFolder.exists()) return; if (!addonFolder.exists()) return;
if (!addonFolder.isDirectory()) return; if (!addonFolder.isDirectory()) return;

View File

@@ -4,14 +4,12 @@ import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonDisabledEvent; import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonDisabledEvent;
import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonEnabledEvent; import dev.unlegitdqrk.unlegitlibrary.addon.events.AddonEnabledEvent;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener; import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
public abstract class Addon { public abstract class Addon {
private boolean isEnabled = false;
private final AddonLoader addonLoader; private final AddonLoader addonLoader;
private boolean isEnabled = false;
public Addon(AddonLoader addonLoader) { public Addon(AddonLoader addonLoader) {
this.addonLoader = addonLoader; this.addonLoader = addonLoader;

View File

@@ -1,13 +1,16 @@
package dev.unlegitdqrk.unlegitlibrary.addon.impl; package dev.unlegitdqrk.unlegitlibrary.addon.impl;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.*; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface AddonInfo { public @interface AddonInfo {
String name(); String name();
String version(); String version();
String author(); String author();
} }

View File

@@ -2,13 +2,13 @@
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
* *
* You are unauthorized to remove this copyright. * 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 * See LICENSE-File if exists
*/ */
package dev.unlegitdqrk.unlegitlibrary.bank; 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 VISA = new CardBrand(16, new String[]{"4"});
public static final CardBrand AMERICANEXPRESS = new CardBrand(15, new String[]{"34", "37"}); public static final CardBrand AMERICANEXPRESS = new CardBrand(15, new String[]{"34", "37"});
public static final CardBrand MASTERCARD = new CardBrand(16, new String[]{ public static final CardBrand MASTERCARD = new CardBrand(16, new String[]{
@@ -16,19 +16,4 @@ public class CardBrand {
"2221", "2222", "2223", "2720" "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;
}
} }

View File

@@ -2,7 +2,7 @@
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
* *
* You are unauthorized to remove this copyright. * 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 * See LICENSE-File if exists
*/ */
@@ -18,18 +18,6 @@ public class CreditCard {
private final CardBrand cardBrand; private final CardBrand cardBrand;
private final Random random; private final Random random;
public CardBrand getCardBrand() {
return cardBrand;
}
public Random getRandom() {
return random;
}
public String getCardNumber() {
return cardNumber;
}
public CreditCard(CardBrand cardBrand) { public CreditCard(CardBrand cardBrand) {
this.cardBrand = cardBrand; this.cardBrand = cardBrand;
this.random = new Random(); this.random = new Random();
@@ -42,14 +30,26 @@ public class CreditCard {
this.cardNumber = generateCardNumber(); this.cardNumber = generateCardNumber();
} }
public CardBrand getCardBrand() {
return cardBrand;
}
public Random getRandom() {
return random;
}
public String getCardNumber() {
return cardNumber;
}
private String generateCardNumber() { private String generateCardNumber() {
int totalLength = cardBrand.getLength(); int totalLength = cardBrand.length();
List<Integer> digits = new ArrayList<>(totalLength); List<Integer> digits = new ArrayList<>(totalLength);
String chosenPrefix = null; String chosenPrefix = null;
if (cardBrand.getPrefixes() != null && cardBrand.getPrefixes().length > 0) { if (cardBrand.prefixes() != null && cardBrand.prefixes().length > 0) {
String[] prefixes = cardBrand.getPrefixes(); String[] prefixes = cardBrand.prefixes();
chosenPrefix = prefixes[random.nextInt(prefixes.length)]; chosenPrefix = prefixes[random.nextInt(prefixes.length)];
for (char c : chosenPrefix.toCharArray()) { for (char c : chosenPrefix.toCharArray()) {
digits.add(c - '0'); digits.add(c - '0');

View File

@@ -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; package dev.unlegitdqrk.unlegitlibrary.network.system.client;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager; 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.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; 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.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.network.utils.PemUtils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider; import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger; import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
@@ -15,230 +36,419 @@ import java.net.ConnectException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Hybrid client supporting TCP (TLS) and UDP (DTLS).
*
* <p>Your chosen policy: BOTH transports must be connected simultaneously.</p>
*/
public final class NetworkClient { public final class NetworkClient {
private final String host; private final String host;
private final int port; private final int tcpPort;
private final int udpPort;
private final PacketHandler packetHandler; private final PacketHandler packetHandler;
private final EventManager eventManager; private final EventManager eventManager;
private final Logger logger; 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, private final int timeout;
EventManager eventManager, Logger logger, private final SSLSocketFactory tlsSocketFactory;
int timeout, SSLSocketFactory sslSocketFactory, private final SSLParameters tlsParameters;
SSLParameters sslParameters, Proxy proxy) { private final SSLContext dtlsContext;
this.host = host; private final Proxy proxy;
this.port = port; private final AtomicBoolean fullyConnectedEventFired = new AtomicBoolean(false);
this.packetHandler = packetHandler; private SSLSocket tcpSocket;
this.eventManager = eventManager; 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.logger = logger;
this.timeout = timeout; this.timeout = timeout;
this.sslSocketFactory = sslSocketFactory; this.tlsSocketFactory = tlsSocketFactory;
this.sslParameters = sslParameters; this.tlsParameters = tlsParameters;
this.dtlsContext = dtlsContext;
this.proxy = proxy;
this.packetHandler.setClientInstance(this); this.packetHandler.setClientInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket()); this.packetHandler.registerPacket(new ClientIDPacket());
this.proxy = proxy; this.packetHandler.registerPacket(new UdpBindPacket());
}
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;
} }
public EventManager getEventManager() { public EventManager getEventManager() {
return eventManager; return eventManager;
} }
public ObjectInputStream getInputStream() { public ClientID getClientId() {
return inputStream; return clientId;
} }
public SSLSocket getSocket() { public void setClientId(ClientID clientId) {
return socket; if (this.clientId == null) {
this.clientId = Objects.requireNonNull(clientId, "clientId");
}
} }
public ObjectOutputStream getOutputStream() { public boolean isTcpConnected() {
return outputStream; Thread t = tcpReceiveThread;
return tcpSocket != null && tcpSocket.isConnected() && !tcpSocket.isClosed()
&& t != null && t.isAlive() && !t.isInterrupted();
} }
public Logger getLogger() { public boolean isUdpConnected() {
return logger; return dtlsEndpoint != null && udpChannel != null && udpChannel.isOpen();
} }
public int getPort() { public boolean isFullyConnected() {
return port; return isTcpConnected() && isUdpConnected() && clientId != null;
}
public String getHost() {
return host;
}
public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed()
&& receiveThread.isAlive() && !receiveThread.isInterrupted();
} }
public synchronized boolean connect() throws ConnectException { 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 + "..."); fullyConnectedEventFired.set(false);
else System.out.println("Trying to connect to " + host + ":" + port + "...");
try { try {
if (sslSocketFactory == null) connectTcp();
throw new ConnectException("SSL socket factory not set. Client certificate required!"); eventManager.executeEvent(new ClientConnectedEvent(this, Transport.TCP));
if (proxy != null) { waitForClientId();
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);
if (sslParameters != null) socket.setSSLParameters(sslParameters); connectDtls();
else { // UDP is considered connected after DTLS handshake is complete (bind follows immediately)
SSLParameters defaultParams = socket.getSSLParameters(); eventManager.executeEvent(new ClientConnectedEvent(this, Transport.UDP));
defaultParams.setProtocols(new String[]{"TLSv1.3"});
socket.setSSLParameters(defaultParams);
}
socket.setTcpNoDelay(true); bindUdpToClientId();
socket.setSoTimeout(timeout);
try {
socket.startHandshake();
} catch (Exception handshakeEx) {
throw new ConnectException("Handshake failed: " + handshakeEx.getMessage());
}
outputStream = new ObjectOutputStream(socket.getOutputStream()); fireFullyConnectedIfReady();
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);
return true; return true;
} catch (ConnectException e) {
disconnect();
throw e;
} catch (Exception e) { } catch (Exception e) {
disconnect();
throw new ConnectException("Failed to connect: " + e.getMessage()); throw new ConnectException("Failed to connect: " + e.getMessage());
} }
} }
private void receive() { private void connectTcp() throws Exception {
try { if (tlsSocketFactory == null) throw new ConnectException("TLS socket factory not set.");
while (isConnected()) {
Object received = inputStream.readObject(); if (proxy != null) {
handleReceived(received); 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);
} }
} catch (Exception e) {
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 (udpChannel != null && udpChannel.isOpen() && !Thread.currentThread().isInterrupted()) {
DtlsEndpoint endpoint = dtlsEndpoint;
if (endpoint != null) endpoint.poll();
Thread.onSpinWait();
}
} catch (Exception ignored) {
disconnect(); 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 (received instanceof Integer id) {
if (packetHandler.isPacketIDRegistered(id)) { if (packetHandler.isPacketIDRegistered(id)) {
Packet packet = packetHandler.getPacketByID(id); Packet packet = packetHandler.getPacketByID(id);
boolean handled = packetHandler.handlePacket(id, packet, inputStream); boolean handled = packetHandler.handlePacket(id, packet, tcpIn);
if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet));
else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet)); if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet, Transport.TCP));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet, Transport.TCP));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); } 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() { public synchronized boolean disconnect() {
if (!isConnected()) return false; boolean wasTcpConnected = isTcpConnected();
boolean wasUdpConnected = isUdpConnected();
boolean wasFullyConnected = isFullyConnected();
if (!wasTcpConnected && !wasUdpConnected) {
cleanup();
return false;
}
try { try {
receiveThread.interrupt(); Thread t1 = tcpReceiveThread;
if (outputStream != null) outputStream.close(); Thread t2 = udpThread;
if (inputStream != null) inputStream.close(); if (t1 != null) t1.interrupt();
if (socket != null) socket.close(); if (t2 != null) t2.interrupt();
} catch (IOException e) {
if (logger != null) logger.exception("Error closing connection", e); if (tcpOut != null) tcpOut.close();
else System.err.println("Error closing connection: " + e.getMessage()); if (tcpIn != null) tcpIn.close();
} finally { if (tcpSocket != null) tcpSocket.close();
socket = null;
outputStream = null; if (udpChannel != null) udpChannel.close();
inputStream = null; } catch (IOException ignored) {
clientID = -1; }
eventManager.executeEvent(new ClientDisconnectedEvent(this));
// 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; return true;
} }
@Override private void cleanup() {
public boolean equals(Object obj) { tcpSocket = null;
if (!(obj instanceof NetworkClient target)) return false; tcpOut = null;
return target.getClientID() == clientID; 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 { private void logInfo(String msg) {
if (!isConnected()) return false; if (logger != null) logger.info(msg);
boolean sent = packetHandler.sendPacket(packet, outputStream); else System.out.println(msg);
if (sent) eventManager.executeEvent(new C_PacketSendEvent(this, packet));
else eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet));
return sent;
} }
// --- Builder --- /**
public static class ClientBuilder extends DefaultMethodsOverrider { * Builder for {@link NetworkClient}.
*/
public static final class ClientBuilder extends DefaultMethodsOverrider {
private String host; private String host;
private int port; private int tcpPort;
private int udpPort;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private EventManager eventManager; private EventManager eventManager;
private Logger logger; private Logger logger;
private int timeout = 5000; private int timeout = 5000;
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters; private SSLSocketFactory tlsSocketFactory;
private SSLParameters tlsParameters;
private SSLContext dtlsContext;
private File caFolder; private File caFolder;
private File clientFolder; private File clientCertFolder;
private File keyFolder; private File clientKeyFolder;
private Proxy proxy; 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()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null); trustStore.load(null, null);
int caIndex = 1; int caIndex = 1;
for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { File[] caFiles = caFolder.listFiles((f) -> f.isFile() && f.getName().endsWith(".pem"));
if (caFiles != null) {
for (File caFile : caFiles) {
try (FileInputStream fis = new FileInputStream(caFile)) { try (FileInputStream fis = new FileInputStream(caFile)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509"); CertificateFactory cf = CertificateFactory.getInstance("X.509");
java.security.cert.Certificate caCert = cf.generateCertificate(fis); java.security.cert.Certificate caCert = cf.generateCertificate(fis);
trustStore.setCertificateEntry("ca" + (caIndex++), caCert); trustStore.setCertificateEntry("ca" + (caIndex++), caCert);
} }
} }
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore); tmf.init(trustStore);
// Key store (client cert + key)
KeyStore keyStore = KeyStore.getInstance("PKCS12"); KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null); // Kein Passwort nötig keyStore.load(null, null);
int clientIndex = 1; int clientIndex = 1;
for (File certFile : clientCertFolder.listFiles((f) -> f.getName().endsWith(".crt"))) { File[] certFiles = clientCertFolder.listFiles((f) -> f.isFile() && (f.getName().endsWith(".crt") || f.getName().endsWith(".pem")));
String baseName = certFile.getName().replace(".crt", ""); if (certFiles != null) {
for (File certFile : certFiles) {
String baseName = certFile.getName()
.replace(".crt", "")
.replace(".pem", "");
File keyFile = new File(clientKeyFolder, baseName + ".key"); File keyFile = new File(clientKeyFolder, baseName + ".key");
if (!keyFile.exists()) continue; if (!keyFile.exists()) continue;
@@ -247,85 +457,228 @@ public final class NetworkClient {
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()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null); kmf.init(keyStore, null);
// SSLContext SSLContext tls = SSLContext.getInstance("TLSv1.3");
SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); tls.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return tls.getSocketFactory();
return sslContext.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) { public ClientBuilder setHost(String host) {
this.host = host; this.host = host;
return this; return this;
} }
public ClientBuilder setPort(int port) { /**
this.port = port; * Sets TCP port.
*/
public ClientBuilder setTcpPort(int tcpPort) {
this.tcpPort = tcpPort;
return this; return this;
} }
/**
* Sets UDP port.
*/
public ClientBuilder setUdpPort(int udpPort) {
this.udpPort = udpPort;
return this;
}
/**
* Sets packet handler.
*/
public ClientBuilder setPacketHandler(PacketHandler handler) { public ClientBuilder setPacketHandler(PacketHandler handler) {
this.packetHandler = handler; this.packetHandler = handler;
return this; return this;
} }
/**
* Sets event manager.
*/
public ClientBuilder setEventManager(EventManager manager) { public ClientBuilder setEventManager(EventManager manager) {
this.eventManager = manager; this.eventManager = manager;
return this; return this;
} }
/**
* Sets logger.
*/
public ClientBuilder setLogger(Logger logger) { public ClientBuilder setLogger(Logger logger) {
this.logger = logger; this.logger = logger;
return this; return this;
} }
public ClientBuilder setTimeout(int timeout) { /**
this.timeout = timeout; * Sets timeout in millis.
*/
public ClientBuilder setTimeout(int timeoutMillis) {
this.timeout = timeoutMillis;
return this; 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; return this;
} }
public ClientBuilder setSSLParameters(SSLParameters params) { /**
this.sslParameters = params; * Sets TLS parameters (optional).
*/
public ClientBuilder setTLSParameters(SSLParameters params) {
this.tlsParameters = params;
return this; 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) { public ClientBuilder setRootCAFolder(File folder) {
this.caFolder = folder; this.caFolder = folder;
return this; return this;
} }
public ClientBuilder setClientCertificatesFolder(File clientFolder, File keyFolder) { /**
this.clientFolder = clientFolder; * Sets client certificate + key folders for auto-creating TLS/DTLS.
this.keyFolder = keyFolder; */
public ClientBuilder setClientCertificatesFolder(File clientCertFolder, File clientKeyFolder) {
this.clientCertFolder = clientCertFolder;
this.clientKeyFolder = clientKeyFolder;
return this; return this;
} }
/**
* Sets optional proxy.
*/
public ClientBuilder setProxy(Proxy proxy) { public ClientBuilder setProxy(Proxy proxy) {
this.proxy = proxy; this.proxy = proxy;
return this; return this;
} }
/**
* Builds the client.
*
* @return client
*/
public NetworkClient build() { 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 { 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) { } 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
);
}
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>This event is transport-specific:
* <ul>
* <li>{@link Transport#TCP}: after TLS handshake + TCP streams are ready</li>
* <li>{@link Transport#UDP}: after DTLS handshake + UDP bind was initiated</li>
* </ul>
*/
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;
}
}

View File

@@ -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.
*
* <p>For your setup this means: TCP (TLS) connected, ClientID received and UDP (DTLS) bound.</p>
*/
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;
}
}

View File

@@ -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.
*
* <p>This event is transport-specific:
* <ul>
* <li>{@link Transport#TCP}: TCP/TLS socket closed or receive loop ended</li>
* <li>{@link Transport#UDP}: UDP channel/DTLS endpoint closed</li>
* </ul>
*/
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;
}
}

View File

@@ -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.
*
* <p>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).</p>
*/
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;
}
}

View File

@@ -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; package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientID;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.util.UUID;
/**
* Transfers the server-assigned {@link ClientID} to the client.
*/
public final class ClientIDPacket extends Packet { public final class ClientIDPacket extends Packet {
private int clientID;
private ClientID clientId;
public ClientIDPacket() { public ClientIDPacket() {
super(0); super(0);
} }
public ClientIDPacket(int clientID) { public ClientIDPacket(ClientID clientId) {
this(); this();
this.clientID = clientID; this.clientId = clientId;
}
public ClientID getClientId() {
return clientId;
} }
@Override @Override
public void write(PacketHandler packetHandler, ObjectOutputStream outputStream) throws IOException, ClassNotFoundException { public void write(PacketHandler packetHandler, ObjectOutputStream outputStream) throws IOException {
outputStream.writeInt(clientID); if (clientId == null) throw new IOException("ClientIDPacket.clientId is null");
UUID uuid = clientId.uuid();
outputStream.writeLong(uuid.getMostSignificantBits());
outputStream.writeLong(uuid.getLeastSignificantBits());
} }
@Override @Override
public void read(PacketHandler packetHandler, ObjectInputStream outputStream) throws IOException, ClassNotFoundException { public void read(PacketHandler packetHandler, ObjectInputStream inputStream) throws IOException {
packetHandler.getClientInstance().setClientID(clientID); 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);
}
} }
} }

View File

@@ -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}.
*
* <p>Flow: TCP connect -> receive ClientID -> DTLS handshake -> send UdpBindPacket(clientId)</p>
*/
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);
}
}
}

View File

@@ -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; package dev.unlegitdqrk.unlegitlibrary.network.system.server;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ClientIDPacket; 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 javax.net.ssl.SSLSocket;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.SocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Objects;
/**
* Handles one connected client socket and dispatches received packets.
*
* <p>Supports TCP (TLS) and UDP (DTLS). UDP is bound after TCP identity assignment.</p>
*/
public final class ConnectionHandler { public final class ConnectionHandler {
private final NetworkServer server; private final NetworkServer server;
private final ClientID clientId;
private final Thread receiveThread;
private SSLSocket socket; private SSLSocket socket;
private int clientID;
private ObjectOutputStream outputStream; private ObjectOutputStream outputStream;
private ObjectInputStream inputStream; 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; * Creates a new connection handler for an already accepted SSL socket.
this.socket = socket; *
this.clientID = clientID; * @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()); this.outputStream = new ObjectOutputStream(socket.getOutputStream());
inputStream = new ObjectInputStream(socket.getInputStream()); 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()); // Send identity via TCP first.
server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this)); 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 @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof ConnectionHandler target)) return false; if (this == obj) return true;
return target.getClientID() == clientID; if (!(obj instanceof ConnectionHandler other)) return false;
return clientId.equals(other.clientId);
}
@Override
public int hashCode() {
return clientId.hashCode();
} }
public SSLSocket getSocket() { public SSLSocket getSocket() {
@@ -53,70 +136,140 @@ public final class ConnectionHandler {
return server; return server;
} }
public int getClientID() { public ClientID getClientId() {
return clientID; return clientId;
} public final Thread receiveThread = new Thread(this::receive); }
public boolean isConnected() { 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() { public synchronized boolean disconnect() {
if (!isConnected()) return false; boolean wasTcpConnected = isConnected();
if (receiveThread.isAlive()) receiveThread.interrupt(); boolean wasUdpConnected = isUdpAttached();
boolean wasFullyConnected = isFullyConnected();
if (!wasTcpConnected && !wasUdpConnected) {
// still cleanup
cleanup();
return false;
}
try { try {
outputStream.close(); receiveThread.interrupt();
inputStream.close();
socket.close(); if (outputStream != null) outputStream.close();
if (inputStream != null) inputStream.close();
if (socket != null) socket.close();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
socket = null;
outputStream = null;
inputStream = null;
clientID = -1;
server.getConnectionHandlers().remove(this); // Emit transport-specific disconnect events
server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this)); 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; 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; if (!isConnected()) return false;
boolean sent = server.getPacketHandler().sendPacket(packet, outputStream); boolean sent = server.getPacketHandler().sendPacket(packet, outputStream);
if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this, Transport.TCP));
if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this)); else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this, Transport.TCP));
else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this));
return sent; 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() { private void receive() {
while (isConnected()) { while (isConnected()) {
try { try {
Object received = inputStream.readObject(); Object received = inputStream.readObject();
if (received instanceof Integer) {
int id = (Integer) received;
if (received instanceof Integer id) {
if (server.getPacketHandler().isPacketIDRegistered(id)) { if (server.getPacketHandler().isPacketIDRegistered(id)) {
Packet packet = server.getPacketHandler().getPacketByID(id); Packet packet = server.getPacketHandler().getPacketByID(id);
if (server.getPacketHandler().handlePacket(id, packet, inputStream)) boolean ok = server.getPacketHandler().handlePacket(id, packet, inputStream);
server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet)); if (ok)
else server.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet)); server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet, Transport.TCP));
} else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); else
} else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); 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) { } catch (SocketException se) {
disconnect(); disconnect();
} catch (Exception ex) { } catch (Exception ex) {
if (server.getLogger() != null) if (server.getLogger() != null) {
server.getLogger().exception("Receive thread exception for client " + clientID, ex); server.getLogger().exception("Receive thread exception for client " + clientId, ex);
else System.err.println("Receive thread exception for client " + clientID + ": " + ex.getMessage()); }
disconnect(); disconnect();
} }
} }
} }
} }

View File

@@ -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; package dev.unlegitdqrk.unlegitlibrary.network.system.server;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager; 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.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ClientIDPacket; 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.network.utils.PemUtils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider; import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger; import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.security.KeyStore; import java.security.KeyStore;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.concurrent.ConcurrentHashMap;
/**
* Hybrid server supporting TCP (TLS) and UDP (DTLS) transports.
*
* <p>Supports policy combinations:
* <ul>
* <li>TCP only</li>
* <li>UDP only</li>
* <li>both supported</li>
* <li>both required simultaneously</li>
* </ul>
*/
public final class NetworkServer { public final class NetworkServer {
private final int port;
private final int tcpPort;
private final int udpPort;
private final PacketHandler packetHandler; private final PacketHandler packetHandler;
private final EventManager eventManager; private final EventManager eventManager;
private final Logger logger; private final Logger logger;
private final int timeout;
private final SSLServerSocketFactory sslServerSocketFactory;
private final List<ConnectionHandler> 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, private final int timeout;
Logger logger, int timeout, SSLServerSocketFactory factory, boolean requireClientCert) { private final SSLServerSocketFactory tlsServerSocketFactory;
this.port = port; private final SSLContext dtlsContext;
this.packetHandler = packetHandler;
this.eventManager = eventManager; private final TransportPolicy transportPolicy;
private final boolean requireClientCert;
private final List<ConnectionHandler> connectionHandlers = Collections.synchronizedList(new ArrayList<>());
private final Map<ClientID, ConnectionHandler> 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.logger = logger;
this.timeout = timeout; 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.setServerInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket()); this.packetHandler.registerPacket(new ClientIDPacket());
this.requireClientCert = requireClientCert; this.packetHandler.registerPacket(new UdpBindPacket());
} }
public List<ConnectionHandler> getConnectionHandlers() { /**
return connectionHandlers; * Returns the configured transport policy.
*
* @return policy
*/
public TransportPolicy getTransportPolicy() {
return transportPolicy;
} }
public ConnectionHandler getConnectionHandlerByID(int clientID) { /**
for (ConnectionHandler connectionHandler : connectionHandlers) * Returns the TCP port (TLS).
if (connectionHandler.getClientID() == clientID) return connectionHandler; *
return null; * @return tcp port
*/
public int getTcpPort() {
return tcpPort;
} }
@Override /**
public boolean equals(Object obj) { * Returns the UDP port (DTLS).
if (!(obj instanceof NetworkServer target)) return false; *
return super.equals(obj); * @return udp port
} */
public int getUdpPort() {
public int getPort() { return udpPort;
return port;
} }
/**
* Returns packet handler.
*
* @return packet handler
*/
public PacketHandler getPacketHandler() { public PacketHandler getPacketHandler() {
return packetHandler; return packetHandler;
} }
public Logger getLogger() { /**
return logger; * Returns event manager.
} *
* @return event manager
public SSLServerSocket getServerSocket() { */
return serverSocket;
}
public EventManager getEventManager() { public EventManager getEventManager() {
return eventManager; 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<ConnectionHandler> 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 { try {
serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); if (transportPolicy.supported().contains(Transport.TCP)) {
serverSocket.setNeedClientAuth(requireClientCert); if (tlsServerSocketFactory == null) {
serverSocket.setSoTimeout(timeout); throw new IllegalStateException("TLS ServerSocketFactory missing (TCP supported).");
serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"}); }
incomingThread.start(); if (tcpPort <= 0) {
if (logger != null) logger.log("Server started on port " + port); throw new IllegalStateException("TCP port not set (TCP supported).");
else System.out.println("Server started on port " + port); }
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; return true;
} catch (Exception e) { } catch (Exception e) {
if (logger != null) logger.exception("Failed to start", e); logException("Failed to start", e);
else System.err.println("Failed to start: " + e.getMessage()); stop();
return false; return false;
} }
} }
public boolean stop() { /**
for (ConnectionHandler connectionHandler : new ArrayList<>(connectionHandlers)) connectionHandler.disconnect(); * Stops the server and disconnects clients.
incomingThread.interrupt(); *
* @return true if stopped successfully
*/
public synchronized boolean stop() {
for (ConnectionHandler h : new ArrayList<>(connectionHandlers)) {
try { try {
serverSocket.close(); h.disconnect();
serverSocket = null; } catch (Exception ignored) {
if (logger != null) logger.log("Server stopped"); }
else System.out.println("Server stopped"); }
try {
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; return true;
} catch (Exception e) { } catch (Exception e) {
if (logger != null) logger.exception("Failed to stop", e); logException("Failed to stop", e);
else System.err.println("Failed to stop: " + e.getMessage());
return false; return false;
} }
} }
private void incomingConnections() { private void acceptTcpLoop() {
try { try {
while (!serverSocket.isClosed()) { while (tcpServerSocket != null && !tcpServerSocket.isClosed() && !Thread.currentThread().isInterrupted()) {
Socket socket = serverSocket.accept(); Socket socket = tcpServerSocket.accept();
if (!(socket instanceof SSLSocket ssl)) { if (!(socket instanceof SSLSocket ssl)) {
socket.close(); socket.close();
continue; continue;
} }
ssl.setTcpNoDelay(true); ssl.setTcpNoDelay(true);
ssl.setSoTimeout(timeout); ssl.setSoTimeout(timeout);
try { try {
ssl.startHandshake(); ssl.startHandshake();
} catch (Exception handshakeEx) { } catch (Exception handshakeEx) {
if (logger != null) logger.exception("Handshake failed", handshakeEx); logException("TLS handshake failed", handshakeEx);
else System.err.println("Handshake failed: " + handshakeEx.getMessage()); try {
ssl.close(); ssl.close();
} catch (Exception ignored) {
}
continue; continue;
} }
IncomingConnectionEvent event = new IncomingConnectionEvent(this, ssl); TCPIncomingConnectionEvent event = new TCPIncomingConnectionEvent(this, ssl);
eventManager.executeEvent(event); eventManager.executeEvent(event);
if (event.isCancelled()) { if (event.isCancelled()) {
try {
ssl.close(); ssl.close();
} catch (Exception ignored) {
}
continue; continue;
} }
try { try {
ConnectionHandler connectionHandler = new ConnectionHandler(this, ssl, connectionHandlers.size() + 1); ClientID clientId = ClientID.random();
connectionHandlers.add(connectionHandler); ConnectionHandler handler = new ConnectionHandler(this, ssl, clientId);
} catch (Exception exception) { 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(); ssl.close();
continue; } catch (Exception ignored) {
}
} }
} }
} catch (Exception e) { } 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(); e.printStackTrace();
} }
} }
// --- Builder --- // --- Builder ---
public static class ServerBuilder extends DefaultMethodsOverrider { public static class ServerBuilder extends DefaultMethodsOverrider {
private int port;
private int tcpPort;
private int udpPort;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private EventManager eventManager; private EventManager eventManager;
private Logger logger; private Logger logger;
private int timeout = 5000; private int timeout = 5000;
private SSLServerSocketFactory factory;
private SSLServerSocketFactory tlsFactory;
private SSLContext dtlsContext;
private TransportPolicy transportPolicy = TransportPolicy.bothRequired();
private boolean requireClientCert; private boolean requireClientCert;
private File caFolder; private File caFolder;
private File serverCertFile; private File serverCertFile;
private File serverKeyFile; 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()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null); trustStore.load(null, null);
int caIndex = 1; int caIndex = 1;
for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { File[] caFiles = caFolder.listFiles((f) -> f.getName().endsWith(".pem"));
try (FileInputStream fis = new FileInputStream(caFile)) { if (caFiles != null) {
for (File caFile : caFiles) {
java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile); java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile);
trustStore.setCertificateEntry("ca" + (caIndex++), cert); trustStore.setCertificateEntry("ca" + (caIndex++), cert);
} }
@@ -181,6 +485,7 @@ public final class NetworkServer {
KeyStore keyStore = KeyStore.getInstance("PKCS12"); KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null); keyStore.load(null, null);
java.security.PrivateKey key = PemUtils.loadPrivateKey(serverKey); java.security.PrivateKey key = PemUtils.loadPrivateKey(serverKey);
java.security.cert.Certificate cert = PemUtils.loadCertificate(serverCert); java.security.cert.Certificate cert = PemUtils.loadCertificate(serverCert);
keyStore.setKeyEntry("server", key, null, new java.security.cert.Certificate[]{cert}); 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()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null); kmf.init(keyStore, null);
SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); SSLContext tls = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); tls.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getServerSocketFactory();
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; return this;
} }
@@ -213,13 +562,13 @@ public final class NetworkServer {
return this; return this;
} }
public ServerBuilder setTimeout(int timeout) { public ServerBuilder setTimeout(int timeoutMillis) {
this.timeout = timeout; this.timeout = timeoutMillis;
return this; return this;
} }
public ServerBuilder setSSLServerSocketFactory(SSLServerSocketFactory factory) { public ServerBuilder setTransportPolicy(TransportPolicy policy) {
this.factory = factory; this.transportPolicy = policy;
return this; return this;
} }
@@ -239,15 +588,53 @@ public final class NetworkServer {
return this; 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() { 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 { 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) { } 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
);
} }
} }
} }

View File

@@ -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.
*
* <p>Drop this into your NetworkServer class (replace your existing onDtlsApplicationData).</p>
*/
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
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>Transport-specific:
* <ul>
* <li>{@link Transport#TCP}: handler created after TLS handshake</li>
* <li>{@link Transport#UDP}: handler received valid UDP bind and attached DTLS session</li>
* </ul>
*/
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;
}
}

View File

@@ -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.
*
* <p>For your setup this means: TCP connected and UDP (DTLS) bound.</p>
*/
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>For your policy (TCP+UDP required) this means:
* previously: TCP connected and UDP attached,
* now: requirements are no longer satisfied.</p>
*/
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;
}
}

View File

@@ -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.
*
* <p>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.</p>
*/
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;
}
}

View File

@@ -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
* <b>before</b> it is bound to a {@link dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler}.
*
* <p>This event allows inspection or rejection of:
* <ul>
* <li>DTLS handshake traffic</li>
* <li>{@code UdpBindPacket}</li>
* <li>Any unbound UDP packet</li>
* </ul>
*
* <p>If cancelled, the datagram is silently dropped.</p>
*/
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.
*
* <p>The buffer is read-only and positioned at the start of the payload.</p>
*
* @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;
}
}

View File

@@ -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}.
*
* <p>This implementation is designed for "best effort" UDP and focuses on:
* <ul>
* <li>DTLS handshake via SSLEngine</li>
* <li>wrap/unwrap application data into datagrams</li>
* <li>per remote address sessions</li>
* </ul>
*/
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<SocketAddress, DtlsSession> 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.
*
* <p>Client: call this after creating a session and before sending app data.</p>
* <p>Server: call this once you detect a new remote (first datagrams arrive) to complete handshake.</p>
*
* @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.
*
* <p>Run this in a dedicated thread for server and client.</p>
*
* @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;
}
}
}

View File

@@ -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.
*
* <p>Format:
* <pre>
* [int packetId][ObjectOutputStream payload bytes...]
* </pre>
*/
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;
}
}
}

View File

@@ -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 + "}";
}
}

View File

@@ -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<Transport> both() {
return EnumSet.of(TCP, UDP);
}
}

View File

@@ -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<Transport> supported, EnumSet<Transport> 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<Transport> supported, EnumSet<Transport> 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<Transport> supported() {
return EnumSet.copyOf(supported);
}
/**
* Required transports.
*
* @return required set
*/
@Override
public EnumSet<Transport> required() {
return EnumSet.copyOf(required);
}
}

View File

@@ -10,6 +10,7 @@ import java.lang.reflect.ParameterizedType;
*/ */
public abstract class GenericReflectClass<T> { public abstract class GenericReflectClass<T> {
protected final Class<T> persistentClass; protected final Class<T> persistentClass;
public GenericReflectClass() { public GenericReflectClass() {
this.persistentClass = (Class<T>) this.persistentClass = (Class<T>)
((ParameterizedType) getClass().getGenericSuperclass()) ((ParameterizedType) getClass().getGenericSuperclass())

View File

@@ -2,7 +2,7 @@
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
* *
* You are unauthorized to remove this copyright. * 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 * See LICENSE-File if exists
*/ */

View File

@@ -13,17 +13,12 @@ import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;
public abstract class AnnotationProcessor<A extends Annotation> extends GenericReflectClass<A> { public abstract class AnnotationProcessor<A extends Annotation> extends GenericReflectClass<A> {
protected Set<Class<?>> projectClasses;
protected Set<Class<?>> annotatedTypes;
protected Set<Method> annotatedMethods;
protected Set<Field> annotatedFields;
protected Set<Constructor> annotatedConstructors;
private final Reflections reflections; private final Reflections reflections;
protected Set<Class<?>> projectClasses;
protected Set<Class<?>> annotatedTypes;
protected Set<Method> annotatedMethods;
protected Set<Field> annotatedFields;
protected Set<Constructor> annotatedConstructors;
public AnnotationProcessor(String packageName) { public AnnotationProcessor(String packageName) {
super(); super();
@@ -62,7 +57,10 @@ public abstract class AnnotationProcessor<A extends Annotation> extends GenericR
protected abstract void processType(Class<?> type); protected abstract void processType(Class<?> type);
protected abstract void processMethod(Method method); protected abstract void processMethod(Method method);
protected abstract void processField(Field field); protected abstract void processField(Field field);
protected abstract void processConstructor(Constructor constructor); protected abstract void processConstructor(Constructor constructor);
} }