Added SSL Support

This commit is contained in:
Finn
2026-02-01 15:33:58 +01:00
parent fc18794e75
commit a5dc89bcac
25 changed files with 1643 additions and 560 deletions

View File

@@ -1,15 +1,27 @@
# UnlegitLibrary
## Overview
UnlegitLibrary is a general-purpose Java utility library that bundles a modular
event system, command framework, addon loader, networking (TCP/UDP with optional TLS),
and a wide set of math/number/string/file/reflection helpers.
## Modules
- Addon system: loader + lifecycle events
- Event system: listeners, priorities, cancellable events
- Command system: command manager, permissions, execution events
- Network system: TCP/UDP transport, packet handling, optional TLS, UDP encryption
- Utilities: math/number helpers, strings, colors, files, reflection, logging
## License Information
GNU General Public License v3.0 (GPLv3)<br />
The default license. Applies to all users, projects, and distributions unless explicitly stated otherwise.<br />
https://repo.unlegitdqrk.dev/UnlegitDqrk/UnlegitLibrary/src/LICENSE
-> https://repo.unlegitdqrk.dev/UnlegitDqrk/UnlegitLibrary/src/LICENSE
Open Autonomous Public License (OAPL)<br />
A special exception applies exclusively to the project Open Autonomous Connection (OAC).<br />
Within OAC, the UnlegitLibrary is also licensed under the OAPL.<br />
In this context, OAPL terms take precedence.<br />
https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL
-> https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL
## Include in own projects
````
@@ -31,6 +43,56 @@ In this context, OAPL terms take precedence.<br />
</dependencies>
````
## NetworkSystem (TCP/UDP + TLS)
- TCP is the control channel (handshake, packet routing).
- UDP is optional and encrypted with a symmetric key negotiated over TCP.
- TLS can be enabled or disabled. For TLS, configure KeyStore/TrustStore explicitly.
- mTLS is supported: set client auth mode to REQUIRED and provide a TrustStore on the server.
### Basic usage
```java
PacketHandler packetHandler = new PacketHandler();
packetHandler.registerPacket(() -> new TestTextPacket(""));
NetworkServer server = new NetworkServer(packetHandler);
server.configureSSL(false, ClientAuthMode.NONE);
server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler);
client.configureSSL(false);
client.connect("127.0.0.1", 25565);
```
### TLS with TrustStore (server validation)
```java
KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray());
KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray());
NetworkServer server = new NetworkServer(packetHandler);
server.configureSSL(true, ClientAuthMode.NONE, serverKeyStore, "changeit".toCharArray(), null);
server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler);
client.configureSSL(true, null, null, clientTrustStore);
client.connect("127.0.0.1", 25565);
```
### TLS with Client Certificate (mTLS)
```java
KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray());
KeyStore serverTrustStore = loadStore("certs/server-trust.p12", "changeit".toCharArray());
KeyStore clientKeyStore = loadStore("certs/client.p12", "changeit".toCharArray());
KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray());
NetworkServer server = new NetworkServer(packetHandler);
server.configureSSL(true, ClientAuthMode.REQUIRED, serverKeyStore, "changeit".toCharArray(), serverTrustStore);
server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler);
client.configureSSL(true, clientKeyStore, "changeit".toCharArray(), clientTrustStore);
client.connect("127.0.0.1", 25565);
```
## Certificate generation for NetworkSystem
### Creating Root-CA:
````
@@ -66,4 +128,15 @@ client.crt = Client-Certificate signed by Root-CA
5. Put the Server-Certificate-Key in "certificates/key"-Folder
6. Put the Server-Certificate in "certificates/server"-Folder
7. Optional: Put the Client-Certificate-Key in "certificates/key"-Folder
8. Optional: Put the Client-Certificate in "certificates/client"-Folder
8. Optional: Put the Client-Certificate in "certificates/client"-Folder
### Helper: load PKCS12 stores in Java
```java
private static KeyStore loadStore(String path, char[] password) throws Exception {
KeyStore store = KeyStore.getInstance("PKCS12");
try (FileInputStream in = new FileInputStream(path)) {
store.load(in, password);
}
return store;
}
```

View File

@@ -0,0 +1,377 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketFailedReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketSendEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.crypto.SecretKey;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.security.KeyStore;
import java.util.UUID;
/**
* TCP/UDP client with optional TLS and UDP encryption.
*/
public class NetworkClient {
private Socket tcpSocket;
private DatagramChannel udpChannel;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private SecretKey udpKey;
private final PacketHandler packetHandler;
private final EventManager eventManager;
private final Thread tcpReceiveThread;
private final Thread udpReceiveThread;
private boolean sslEnabled = true;
private volatile UUID uniqueID;
private String host;
private int tcpPort = -1;
private int udpPort = -1;
private KeyStore keyStore;
private char[] keyPassword;
private KeyStore trustStore;
private long handshakeTimeoutMillis = 5000;
/**
* Creates a client with a default {@link EventManager}.
*
* @param packetHandler packet handler
*/
public NetworkClient(PacketHandler packetHandler) {
this(packetHandler, new EventManager());
}
/**
* Creates a client with a custom {@link EventManager}.
*
* @param packetHandler packet handler
* @param eventManager event manager
*/
public NetworkClient(PacketHandler packetHandler, EventManager eventManager) {
this.packetHandler = packetHandler;
this.eventManager = eventManager;
this.tcpReceiveThread = new Thread(this::tcpReceive);
this.udpReceiveThread = new Thread(this::udpReceive);
}
/**
* Enables or disables TLS.
*
* @param sslEnabled enable TLS
*/
public void configureSSL(boolean sslEnabled) {
this.sslEnabled = sslEnabled;
}
/**
* Enables or disables TLS with explicit key and trust stores.
*
* @param sslEnabled enable TLS
* @param keyStore client key store (optional)
* @param keyPassword key store password
* @param trustStore trust store (required when TLS is enabled)
*/
public void configureSSL(boolean sslEnabled,
KeyStore keyStore,
char[] keyPassword,
KeyStore trustStore) {
this.sslEnabled = sslEnabled;
this.keyStore = keyStore;
this.keyPassword = keyPassword;
this.trustStore = trustStore;
}
/**
* Sets the TCP handshake timeout in milliseconds.
*
* @param handshakeTimeoutMillis timeout in ms
*/
public void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) {
this.handshakeTimeoutMillis = handshakeTimeoutMillis;
}
/**
* Connects to a server.
*
* @param host server host
* @param tcpPort server TCP port
*/
public void connect(String host, int tcpPort) throws Exception {
this.host = host;
this.tcpPort = tcpPort;
if (sslEnabled) {
if (trustStore == null) {
throw new IllegalStateException("SSL enabled but no TrustStore configured");
}
SSLContext ctx = SSLContexts.create(keyStore, keyPassword, trustStore);
SSLSocket sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(host, tcpPort);
sslSocket.startHandshake();
tcpSocket = sslSocket;
} else {
tcpSocket = new Socket(host, tcpPort);
}
inputStream = new DataInputStream(tcpSocket.getInputStream());
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
tcpReceiveThread.start();
long deadline = System.currentTimeMillis() + handshakeTimeoutMillis;
while (uniqueID == null) {
if (System.currentTimeMillis() >= deadline) {
disconnect();
throw new IOException("TCP handshake timeout (no server response)");
}
Thread.sleep(5);
}
Thread.sleep(10);
eventManager.executeEvent(new ClientConnectedEvent(this));
}
private void tcpReceive() {
try {
while (true) {
UUID uuid = UUID.fromString(inputStream.readUTF());
int packetId = inputStream.readInt();
if (packetId == -1) {
uniqueID = uuid;
udpPort = inputStream.readInt();
int keyLen = inputStream.readInt();
byte[] keyBytes = new byte[keyLen];
inputStream.readFully(keyBytes);
udpKey = UdpCrypto.fromBytes(keyBytes);
connectUDP();
} else {
Packet packet = null;
if ((packet = packetHandler.readPacket(inputStream, uuid, packetId)) != null)
eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.TCP));
}
}
} catch (Exception e) {
disconnect();
}
}
private void connectUDP() throws Exception {
udpChannel = DatagramChannel.open();
udpChannel.connect(new InetSocketAddress(host, udpPort));
udpReceiveThread.start();
// initial handshake
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(uniqueID.toString());
dos.writeInt(0);
dos.flush();
ByteBuffer buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
udpChannel.write(buffer);
}
private void udpReceive() {
ByteBuffer buffer = ByteBuffer.allocate(65536);
int packetId = 0;
Packet packet = null;
while (udpChannel.isConnected()) {
try {
buffer.clear();
udpChannel.receive(buffer);
buffer.flip();
byte[] plaintext = UdpCrypto.decrypt(udpKey, buffer, null);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(plaintext));
UUID uuid = UUID.fromString(dis.readUTF());
packetId = dis.readInt();
if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) {
eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.UDP));
} else eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
} catch (Exception ignored) {
eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
}
}
}
/**
* Sends a packet via TCP or UDP.
*
* @param packet packet to send
* @param protocol transport protocol
*/
public void sendPacket(Packet packet, TransportProtocol protocol) throws Exception {
if (protocol == TransportProtocol.TCP) {
packetHandler.sendPacket(outputStream, packet, uniqueID);
eventManager.executeEvent(new C_PacketSendEvent(this, packet, TransportProtocol.TCP));
} else if (protocol == TransportProtocol.UDP && udpChannel != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
packetHandler.sendPacket(dos, packet, uniqueID);
dos.flush();
ByteBuffer buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
udpChannel.write(buffer);
eventManager.executeEvent(new C_PacketSendEvent(this, packet, TransportProtocol.UDP));
}
}
/**
* Disconnects the client and releases resources.
*/
public void disconnect() {
// Stop threads first
tcpReceiveThread.interrupt();
udpReceiveThread.interrupt();
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
inputStream = null;
outputStream = null;
tcpSocket = null;
udpChannel = null;
eventManager.executeEvent(new ClientDisconnectedEvent(this));
uniqueID = null;
udpKey = null;
udpPort = -1;
}
/**
* Builder for {@link NetworkClient}.
*/
public static class Builder {
private PacketHandler packetHandler;
private EventManager eventManager;
private boolean sslEnabled = true;
private KeyStore keyStore;
private char[] keyPassword;
private KeyStore trustStore;
private long handshakeTimeoutMillis = 5000;
/**
* Sets the packet handler (required).
*
* @param packetHandler packet handler
* @return builder
*/
public Builder packetHandler(PacketHandler packetHandler) {
this.packetHandler = packetHandler;
return this;
}
/**
* Sets a custom event manager.
*
* @param eventManager event manager
* @return builder
*/
public Builder eventManager(EventManager eventManager) {
this.eventManager = eventManager;
return this;
}
/**
* Enables or disables TLS.
*
* @param sslEnabled enable TLS
* @return builder
*/
public Builder sslEnabled(boolean sslEnabled) {
this.sslEnabled = sslEnabled;
return this;
}
/**
* Sets the key store and its password.
*
* @param keyStore key store
* @param keyPassword key store password
* @return builder
*/
public Builder keyStore(KeyStore keyStore, char[] keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
return this;
}
/**
* Sets the trust store (root CAs).
*
* @param trustStore trust store
* @return builder
*/
public Builder trustStore(KeyStore trustStore) {
this.trustStore = trustStore;
return this;
}
/**
* Sets the handshake timeout in milliseconds.
*
* @param handshakeTimeoutMillis timeout in ms
* @return builder
*/
public Builder handshakeTimeoutMillis(long handshakeTimeoutMillis) {
this.handshakeTimeoutMillis = handshakeTimeoutMillis;
return this;
}
/**
* Builds the client instance.
*
* @return network client
*/
public NetworkClient build() {
if (packetHandler == null) {
throw new IllegalStateException("PacketHandler is required");
}
NetworkClient client = (eventManager == null)
? new NetworkClient(packetHandler)
: new NetworkClient(packetHandler, eventManager);
if (keyStore != null || trustStore != null) {
client.configureSSL(sslEnabled, keyStore, keyPassword, trustStore);
} else {
client.configureSSL(sslEnabled);
}
client.setHandshakeTimeoutMillis(handshakeTimeoutMillis);
return client;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
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.TransportProtocol;
public class C_PacketFailedReadEvent extends Event {
private final NetworkClient client;
private final Packet packet;
private final int packetId;
private final TransportProtocol protocol;
public C_PacketFailedReadEvent(NetworkClient client, Packet packet, int packetId, TransportProtocol protocol) {
this.client = client;
this.packet = packet;
this.packetId = packetId;
this.protocol = protocol;
}
public Packet getPacket() {
return packet;
}
public int getPacketId() {
return packetId;
}
public NetworkClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
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.TransportProtocol;
public class C_PacketReadEvent extends Event {
private final NetworkClient client;
private final Packet packet;
private final TransportProtocol protocol;
public C_PacketReadEvent(NetworkClient client, Packet packet, TransportProtocol protocol) {
this.client = client;
this.packet = packet;
this.protocol = protocol;
}
public Packet getPacket() {
return packet;
}
public NetworkClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
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.TransportProtocol;
public class C_PacketSendEvent extends Event {
private final NetworkClient client;
private final Packet packet;
private final TransportProtocol protocol;
public C_PacketSendEvent(NetworkClient client, Packet packet, TransportProtocol protocol) {
this.client = client;
this.packet = packet;
this.protocol = protocol;
}
public Packet getPacket() {
return packet;
}
public NetworkClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
public class ClientConnectedEvent extends Event {
private final NetworkClient client;
public ClientConnectedEvent(NetworkClient client) {
this.client = client;
}
public NetworkClient getClient() {
return client;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
public class ClientDisconnectedEvent extends Event {
private final NetworkClient client;
public ClientDisconnectedEvent(NetworkClient client) {
this.client = client;
}
public NetworkClient getClient() {
return client;
}
}

View File

@@ -33,13 +33,13 @@ public class PacketHandler {
* @param input DataInputStream to read from
* @return Packet instance or null if failed
*/
public boolean readPacket(DataInputStream input, UUID clientID, int id) throws IOException {
public Packet readPacket(DataInputStream input, UUID clientID, int id) throws IOException {
Supplier<? extends Packet> factory = factories.get(id);
if (factory == null) return false;
if (factory == null) return null;
Packet packet = factory.get();
packet.read(input, clientID);
return true;
return packet;
}
/**

View File

@@ -6,21 +6,24 @@
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
package dev.unlegitdqrk.unlegitlibrary.network.system.server;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketFailedReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketSendEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.crypto.SecretKey;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.net.SocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.NotYetConnectedException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.UUID;
public class ConnectedClient {
@@ -30,33 +33,36 @@ public class ConnectedClient {
private DataInputStream inputStream;
private DataOutputStream outputStream;
private DatagramChannel udpChannel; // für serverseitiges Senden
private DatagramChannel udpChannel;
private final Thread tcpReceiveThread;
private final NetworkServer server;
private int udpPort = -1;
public Socket getTcpSocket() {
return tcpSocket;
}
private SecretKey udpKey;
public ConnectedClient(NetworkServer server, Socket tcpSocket, UUID uniqueID, int udpPort) throws IOException {
this.tcpSocket = tcpSocket;
this.uniqueID = uniqueID;
this.udpPort = udpPort;
this.server = server;
this.tcpReceiveThread = new Thread(this::tcpReceive);
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
inputStream = new DataInputStream(tcpSocket.getInputStream());
this.udpKey = UdpCrypto.generateKey();
tcpReceiveThread = new Thread(this::tcpReceive);
tcpReceiveThread.start();
server.getConnectedClients().add(this);
// Sende UUID & UDP-Port an Client
outputStream.writeUTF(uniqueID.toString());
outputStream.writeInt(-1);
outputStream.writeInt(-1); // Handshake-Packet
outputStream.writeInt(udpPort);
byte[] keyBytes = udpKey.getEncoded();
outputStream.writeInt(keyBytes.length);
outputStream.write(keyBytes);
outputStream.flush();
}
@@ -65,20 +71,17 @@ public class ConnectedClient {
}
public void sendPacket(Packet packet, TransportProtocol protocol) throws IOException {
if (protocol == TransportProtocol.UDP) {
sendPacketUDP(packet);
return;
}
if (protocol == TransportProtocol.TCP) {
sendPacketTCP(packet);
return;
} else if (protocol == TransportProtocol.UDP) {
sendPacketUDP(packet);
}
}
private void sendPacketTCP(Packet packet) throws IOException {
if (!isTCPConnected()) return;
server.getPacketHandler().sendPacket(outputStream, packet, uniqueID);
server.getEventManager().executeEvent(new S_PacketSendEvent(server, this, packet, TransportProtocol.TCP));
}
private void sendPacketUDP(Packet packet) throws IOException {
@@ -89,28 +92,30 @@ public class ConnectedClient {
server.getPacketHandler().sendPacket(dos, packet, uniqueID);
dos.flush();
ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
// Hole die gespeicherte UDP-Adresse des Clients
SocketAddress clientAddress = server.getClientUdpAddress(uniqueID);
if (clientAddress == null) {
return;
ByteBuffer buffer;
try {
buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
} catch (Exception e) {
throw new IOException("Failed to encrypt UDP packet", e);
}
udpChannel.send(buffer, clientAddress); // korrekt an Client senden
SocketAddress clientAddress = server.getClientUdpAddress(uniqueID);
if (clientAddress != null && udpChannel != null && udpChannel.isOpen()) {
udpChannel.send(buffer, clientAddress);
server.getEventManager().executeEvent(new S_PacketSendEvent(server, this, packet, TransportProtocol.UDP));
}
}
private void tcpReceive() {
try {
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && tcpSocket.isBound()) {
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && !tcpSocket.isClosed()) {
UUID uuid = UUID.fromString(inputStream.readUTF());
int packetId = inputStream.readInt();
if (!server.getPacketHandler().readPacket(inputStream, uuid, packetId)) {
// TODO: UnknownPacketReceivedEvent
}
Packet packet = null;
if ((packet = server.getPacketHandler().readPacket(inputStream, uuid, packetId)) != null)
server.getEventManager().executeEvent(new S_PacketReadEvent(server, this, packet, TransportProtocol.TCP));
else
server.getEventManager().executeEvent(new S_PacketFailedReadEvent(server, this, packet, packetId, TransportProtocol.TCP));
}
} catch (IOException e) {
disconnect();
@@ -123,11 +128,15 @@ public class ConnectedClient {
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
tcpSocket = null;
udpChannel = null;
udpPort = -1;
inputStream = null;
outputStream = null;
udpPort = -1;
server.getEventManager().executeEvent(new S_ClientDisconnectedEvent(server, this));
uniqueID = null;
server.getConnectedClients().remove(this);
@@ -152,4 +161,12 @@ public class ConnectedClient {
public int getUdpPort() {
return udpPort;
}
public Socket getTcpSocket() {
return tcpSocket;
}
public SecretKey getUdpKey() {
return udpKey;
}
}

View File

@@ -0,0 +1,432 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketFailedReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStartedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.security.KeyStore;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* TCP/UDP server with optional TLS and UDP encryption.
*/
public class NetworkServer {
private ServerSocket tcpSocket;
private Thread tcpThread;
private DatagramChannel udpChannel;
private Thread udpThread;
private final Map<SocketAddress, UUID> clientUdpAddresses = new ConcurrentHashMap<>();
private final PacketHandler packetHandler;
private final EventManager eventManager;
private final List<ConnectedClient> connectedClients;
private int udpPort = -1;
/* === TLS CONFIG === */
private boolean sslEnabled = true;
private ClientAuthMode clientAuthMode = ClientAuthMode.NONE;
private KeyStore keyStore;
private char[] keyPassword;
private KeyStore trustStore;
/**
* Creates a server with a custom {@link EventManager}.
*
* @param packetHandler packet handler
* @param eventManager event manager
*/
private NetworkServer(PacketHandler packetHandler, EventManager eventManager) {
this.packetHandler = packetHandler;
this.eventManager = eventManager;
this.connectedClients = Collections.synchronizedList(new ArrayList<>());
}
/**
* Enables or disables TLS and sets the client authentication mode.
*
* @param sslEnabled enable TLS
* @param clientAuthMode client authentication mode
*/
public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode) {
this.sslEnabled = sslEnabled;
this.clientAuthMode = clientAuthMode;
}
/**
* Enables or disables TLS with explicit key and trust stores.
*
* @param sslEnabled enable TLS
* @param clientAuthMode client authentication mode
* @param keyStore server key store (required when TLS is enabled)
* @param keyPassword key store password
* @param trustStore trust store (required for client auth)
*/
public void configureSSL(boolean sslEnabled,
ClientAuthMode clientAuthMode,
KeyStore keyStore,
char[] keyPassword,
KeyStore trustStore) {
this.sslEnabled = sslEnabled;
this.clientAuthMode = clientAuthMode;
this.keyStore = keyStore;
this.keyPassword = keyPassword;
this.trustStore = trustStore;
}
/**
* Starts the server.
*
* @param tcpPort TCP port
* @param udpPort UDP port (-1 to disable UDP)
*/
public void start(int tcpPort, int udpPort) throws IOException, InterruptedException {
this.udpPort = udpPort;
if (sslEnabled) {
if (keyStore == null) {
throw new IllegalStateException("SSL enabled but no server KeyStore configured");
}
if (clientAuthMode != ClientAuthMode.NONE && trustStore == null) {
throw new IllegalStateException("Client auth enabled but no TrustStore configured");
}
SSLContext sslContext = SSLContexts.create(keyStore, keyPassword, trustStore);
SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
tcpSocket = factory.createServerSocket(tcpPort);
SSLServerSocket sslServerSocket = (SSLServerSocket) tcpSocket;
switch (clientAuthMode) {
case REQUIRED -> sslServerSocket.setNeedClientAuth(true);
case OPTIONAL -> sslServerSocket.setWantClientAuth(true);
case NONE -> {
sslServerSocket.setNeedClientAuth(false);
sslServerSocket.setWantClientAuth(false);
}
}
} else {
tcpSocket = new ServerSocket(tcpPort);
}
tcpThread = new Thread(this::tcpAcceptLoop);
tcpThread.start();
if (isUDPEnabled()) {
udpChannel = DatagramChannel.open();
udpChannel.bind(new InetSocketAddress(udpPort));
udpThread = new Thread(this::udpReceiveLoop);
udpThread.start();
}
Thread.sleep(10);
eventManager.executeEvent(new ServerStartedEvent(this));
}
/**
* Stops the server and disconnects all clients.
*/
public void stop() {
if (tcpThread != null) tcpThread.interrupt();
if (udpThread != null) udpThread.interrupt();
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
List<ConnectedClient> snapshot;
synchronized (connectedClients) {
snapshot = new ArrayList<>(connectedClients);
}
for (ConnectedClient client : snapshot) {
client.disconnect();
}
tcpSocket = null;
udpChannel = null;
tcpThread = null;
udpThread = null;
udpPort = -1;
eventManager.executeEvent(new ServerStoppedEvent(this));
}
private void tcpAcceptLoop() {
while (!Thread.currentThread().isInterrupted()) {
Socket socket = null;
try {
socket = tcpSocket.accept();
if (socket instanceof SSLSocket sslSocket) {
sslSocket.startHandshake();
}
ConnectedClient client = new ConnectedClient(this, socket, UUID.randomUUID(), udpPort);
if (isUDPEnabled()) client.setUdpChannel(udpChannel);
Thread.sleep(100);
eventManager.executeEvent(new S_ClientConnectedEvent(this, client));
} catch (IOException | InterruptedException e) {
break;
}
}
}
private void udpReceiveLoop() {
ByteBuffer buffer = ByteBuffer.allocate(65536);
while (!Thread.currentThread().isInterrupted()) {
try {
buffer.clear();
SocketAddress sender = udpChannel.receive(buffer);
if (sender == null) continue;
buffer.flip();
if (buffer.remaining() < 13) continue; // 12-byte IV + at least 1 byte payload
UUID mappedUuid = clientUdpAddresses.get(sender);
if (mappedUuid != null) {
ConnectedClient mappedClient = getClientByUuid(mappedUuid);
if (mappedClient != null) {
if (tryHandleUdpPacket(mappedClient, sender, buffer.asReadOnlyBuffer())) {
continue;
}
}
}
// Fallback: try all clients if mapping missing or decrypt failed (e.g., NAT rebinding)
List<ConnectedClient> snapshot;
synchronized (connectedClients) {
snapshot = new ArrayList<>(connectedClients);
}
for (ConnectedClient client : snapshot) {
if (tryHandleUdpPacket(client, sender, buffer.asReadOnlyBuffer())) {
break;
}
}
} catch (IOException e) {
if (tcpSocket != null) {
try { tcpSocket.close(); } catch (IOException ignored) {}
}
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
}
/**
* @return true if UDP is enabled
*/
public boolean isUDPEnabled() {
return udpPort != -1;
}
/**
* @return packet handler
*/
public PacketHandler getPacketHandler() {
return packetHandler;
}
/**
* @return connected clients list (synchronized)
*/
public List<ConnectedClient> getConnectedClients() {
return connectedClients;
}
/**
* Returns the UDP socket address for a client UUID.
*
* @param uuid client UUID
* @return socket address or null
*/
public SocketAddress getClientUdpAddress(UUID uuid) {
AtomicReference<SocketAddress> socket = new AtomicReference<>();
clientUdpAddresses.forEach((socketAddress, client) -> {
if (client.equals(uuid)) socket.set(socketAddress);
});
return socket.get();
}
private ConnectedClient getClientByUuid(UUID uuid) {
synchronized (connectedClients) {
for (ConnectedClient client : connectedClients) {
if (uuid.equals(client.getUniqueID())) return client;
}
}
return null;
}
private boolean tryHandleUdpPacket(ConnectedClient client, SocketAddress sender, ByteBuffer rawBuffer) {
int packetId = 0;
Packet packet = null;
try {
rawBuffer.rewind();
byte[] plaintext = UdpCrypto.decrypt(client.getUdpKey(), rawBuffer, null);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(plaintext));
UUID uuid = UUID.fromString(dis.readUTF());
packetId = dis.readInt();
if (!uuid.equals(client.getUniqueID())) return false;
if (packetId == 0) {
clientUdpAddresses.put(sender, uuid);
return true;
}
clientUdpAddresses.put(sender, uuid);
if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) {
eventManager.executeEvent(new S_PacketReadEvent(this, client, packet, TransportProtocol.UDP));
return true;
}
} catch (Exception ignored) {}
eventManager.executeEvent(new S_PacketFailedReadEvent(this, client, packet, packetId, TransportProtocol.UDP));
return false;
}
/**
* @return event manager
*/
public EventManager getEventManager() {
return eventManager;
}
/**
* Builder for {@link NetworkServer}.
*/
public static class Builder {
private PacketHandler packetHandler;
private EventManager eventManager;
private boolean sslEnabled = true;
private ClientAuthMode clientAuthMode = ClientAuthMode.NONE;
private KeyStore keyStore;
private char[] keyPassword;
private KeyStore trustStore;
/**
* Sets the packet handler (required).
*
* @param packetHandler packet handler
* @return builder
*/
public Builder packetHandler(PacketHandler packetHandler) {
this.packetHandler = packetHandler;
return this;
}
/**
* Sets a custom event manager.
*
* @param eventManager event manager
* @return builder
*/
public Builder eventManager(EventManager eventManager) {
this.eventManager = eventManager;
return this;
}
/**
* Enables or disables TLS.
*
* @param sslEnabled enable TLS
* @return builder
*/
public Builder sslEnabled(boolean sslEnabled) {
this.sslEnabled = sslEnabled;
return this;
}
/**
* Sets the client authentication mode.
*
* @param clientAuthMode client authentication mode
* @return builder
*/
public Builder clientAuthMode(ClientAuthMode clientAuthMode) {
this.clientAuthMode = clientAuthMode;
return this;
}
/**
* Sets the key store and its password.
*
* @param keyStore key store
* @param keyPassword key store password
* @return builder
*/
public Builder keyStore(KeyStore keyStore, char[] keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
return this;
}
/**
* Sets the trust store (root CAs).
*
* @param trustStore trust store
* @return builder
*/
public Builder trustStore(KeyStore trustStore) {
this.trustStore = trustStore;
return this;
}
/**
* Builds the server instance.
*
* @return network server
*/
public NetworkServer build() {
if (packetHandler == null) {
throw new IllegalStateException("PacketHandler is required");
}
if (eventManager == null) {
throw new IllegalStateException("EventManager is required");
}
NetworkServer server = new NetworkServer(packetHandler, eventManager);
if (keyStore != null || trustStore != null || clientAuthMode != ClientAuthMode.NONE) {
server.configureSSL(sslEnabled, clientAuthMode, keyStore, keyPassword, trustStore);
} else {
server.configureSSL(sslEnabled, clientAuthMode);
}
return server;
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
public class S_ClientConnectedEvent extends Event {
private final NetworkServer server;
private final ConnectedClient client;
public S_ClientConnectedEvent(NetworkServer server, ConnectedClient client) {
this.server = server;
this.client = client;
}
public ConnectedClient getClient() {
return client;
}
public NetworkServer getServer() {
return server;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
public class S_ClientDisconnectedEvent extends Event {
private final NetworkServer server;
private final ConnectedClient client;
public S_ClientDisconnectedEvent(NetworkServer server, ConnectedClient client) {
this.server = server;
this.client = client;
}
public ConnectedClient getClient() {
return client;
}
public NetworkServer getServer() {
return server;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
public class S_PacketFailedReadEvent extends Event {
private final NetworkServer server;
private final ConnectedClient client;
private final Packet packet;
private final int packetId;
private final TransportProtocol protocol;
public S_PacketFailedReadEvent(NetworkServer server, ConnectedClient client, Packet packet, int packetId, TransportProtocol protocol) {
this.server = server;
this.client = client;
this.packet = packet;
this.packetId = packetId;
this.protocol = protocol;
}
public NetworkServer getServer() {
return server;
}
public Packet getPacket() {
return packet;
}
public int getPacketId() {
return packetId;
}
public ConnectedClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
public class S_PacketReadEvent extends Event {
private final NetworkServer server;
private final ConnectedClient client;
private final Packet packet;
private final TransportProtocol protocol;
public S_PacketReadEvent(NetworkServer server, ConnectedClient client, Packet packet, TransportProtocol protocol) {
this.server = server;
this.client = client;
this.packet = packet;
this.protocol = protocol;
}
public NetworkServer getServer() {
return server;
}
public Packet getPacket() {
return packet;
}
public ConnectedClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
public class S_PacketSendEvent extends Event {
private final NetworkServer server;
private final ConnectedClient client;
private final Packet packet;
private final TransportProtocol protocol;
public S_PacketSendEvent(NetworkServer server, ConnectedClient client, Packet packet, TransportProtocol protocol) {
this.server = server;
this.client = client;
this.packet = packet;
this.protocol = protocol;
}
public NetworkServer getServer() {
return server;
}
public Packet getPacket() {
return packet;
}
public ConnectedClient getClient() {
return client;
}
public TransportProtocol getProtocol() {
return protocol;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
public class ServerStartedEvent extends Event {
private final NetworkServer server;
public ServerStartedEvent(NetworkServer server) {
this.server = server;
}
public NetworkServer getServer() {
return server;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
public class ServerStoppedEvent extends Event {
private final NetworkServer server;
public ServerStoppedEvent(NetworkServer server) {
this.server = server;
}
public NetworkServer getServer() {
return server;
}
}

View File

@@ -1,206 +0,0 @@
/*
* 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://github.com/UnlegitDqrk
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.PortUnreachableException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.DatagramChannel;
import java.util.UUID;
public class NetworkClient {
private Socket tcpSocket;
private final Thread tcpReceiveThread;
private DatagramChannel udpChannel;
private final Thread udpReceiveThread;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private volatile UUID uniqueID;
private final PacketHandler packetHandler;
private int tcpPort = -1;
private String host;
private int udpPort = -1;
public NetworkClient(PacketHandler packetHandler) {
this.packetHandler = packetHandler;
this.tcpReceiveThread = new Thread(this::tcpReceive);
this.udpReceiveThread = new Thread(this::udpReceive);
}
public void sendPacket(Packet packet, TransportProtocol protocol) throws IOException {
if (protocol == TransportProtocol.TCP) {
if (!isTCPConnected()) return;
packetHandler.sendPacket(outputStream, packet, uniqueID);
}
if (protocol == TransportProtocol.UDP) {
if (!isUDPConnected()) return;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream oos = new DataOutputStream(baos)) {
packetHandler.sendPacket(oos, packet, uniqueID);
ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
udpChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Connects the client to a server with optional UDP.
*
* @param host Hostname or IP (IPv4/IPv6)
* @param tcpPort TCP port
* @throws IOException
* @throws InterruptedException
*/
public void connect(String host, int tcpPort) throws IOException, InterruptedException {
this.host = host;
this.tcpPort = tcpPort;
tcpSocket = new Socket(host, tcpPort);
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
inputStream = new DataInputStream(tcpSocket.getInputStream());
tcpReceiveThread.start();
while (uniqueID == null) Thread.sleep(10);
Thread.sleep(10);
}
private void tcpReceive() {
try {
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && tcpSocket.isBound()) {
UUID uuid = UUID.fromString(inputStream.readUTF());
int packetId = inputStream.readInt();
if (!packetHandler.readPacket(inputStream, uuid, packetId)) {
if (packetId == -1) {
uniqueID = uuid;
this.udpPort = inputStream.readInt();
if (isUDPEnabled()) {
connectUDP();
}
}
}
}
} catch (IOException | InterruptedException e) {
disconnect();
}
}
private void udpReceive() {
if (!isUDPConnected()) return;
ByteBuffer buffer = ByteBuffer.allocate(65536);
while (!Thread.currentThread().isInterrupted() && isUDPConnected()) {
try {
buffer.clear();
SocketAddress sender = udpChannel.receive(buffer);
if (sender == null) continue;
buffer.flip();
handleUdpPacket(buffer);
} catch (PortUnreachableException | AsynchronousCloseException ignored) {}
catch (IOException e) {
e.printStackTrace();
break;
}
}
}
private void handleUdpPacket(ByteBuffer buffer) {
try (DataInputStream ois = new DataInputStream(
new ByteArrayInputStream(buffer.array(), 0, buffer.limit()))) {
UUID uuid = UUID.fromString(ois.readUTF());
int packetId = ois.readInt();
if (!packetHandler.readPacket(ois, uuid, packetId)) {
// TODO: UnknownPacketReceivedEvent
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void connectUDP() throws IOException, InterruptedException {
if (!isUDPEnabled()) return;
if (udpReceiveThread.isAlive()) return;
udpChannel = DatagramChannel.open();
udpChannel.socket().setReuseAddress(true);
udpChannel.configureBlocking(true);
udpChannel.connect(new InetSocketAddress(host, udpPort));
udpReceiveThread.start();
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{0});
udpChannel.write(buffer);
}
public void disconnect() {
tcpReceiveThread.interrupt();
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
tcpSocket = null;
udpChannel = null;
inputStream = null;
outputStream = null;
host = null;
tcpPort = -1;
udpPort = -1;
uniqueID = null;
}
public boolean isTCPConnected() {
return uniqueID != null && tcpSocket != null && tcpSocket.isConnected() && !tcpSocket.isClosed();
}
public boolean isUDPEnabled() {
return udpPort != -1;
}
public boolean isUDPConnected() {
return isUDPEnabled() && isTCPConnected() && udpChannel != null && udpChannel.isConnected();
}
public UUID getUniqueID() {
return uniqueID;
}
}

View File

@@ -1,177 +0,0 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class NetworkServer {
// Events-Handler (optional)
public abstract static class EventsServer {
public abstract void onConnect(ConnectedClient client);
}
public EventsServer events;
private ServerSocket tcpSocket;
private Thread tcpThread;
private DatagramChannel udpChannel;
private Thread udpThread;
private final Map<UUID, SocketAddress> clientUdpAddresses = new ConcurrentHashMap<>();
private final PacketHandler packetHandler;
private final List<ConnectedClient> connectedClients;
private int udpPort = -1;
public NetworkServer(PacketHandler packetHandler) {
this.packetHandler = packetHandler;
this.connectedClients = Collections.synchronizedList(new ArrayList<>());
}
public void start(int tcpPort, int udpPort) throws IOException {
this.udpPort = udpPort;
// TCP starten
tcpSocket = new ServerSocket();
tcpSocket.bind(new InetSocketAddress(tcpPort));
tcpThread = new Thread(this::tcpAcceptLoop);
tcpThread.start();
// UDP starten, falls aktiviert
if (isUDPEnabled()) {
udpChannel = DatagramChannel.open();
udpChannel.socket().setReuseAddress(true);
udpChannel.configureBlocking(true);
udpChannel.bind(new InetSocketAddress(udpPort));
udpThread = new Thread(this::udpReceiveLoop);
udpThread.start();
}
}
private void tcpAcceptLoop() {
while (!Thread.currentThread().isInterrupted() && tcpSocket.isBound()) {
try {
Socket clientSocket = tcpSocket.accept();
ConnectedClient client = new ConnectedClient(this, clientSocket, UUID.randomUUID(), udpPort);
if (isUDPEnabled()) {
client.setUdpChannel(udpChannel);
}
Thread.sleep(100);
if (events != null) events.onConnect(client);
} catch (IOException | InterruptedException e) {
break;
}
}
}
private void udpReceiveLoop() {
ByteBuffer buffer = ByteBuffer.allocate(65536);
while (!Thread.currentThread().isInterrupted() && isUDPEnabled()) {
try {
buffer.clear();
SocketAddress sender = udpChannel.receive(buffer);
if (sender == null) continue;
buffer.flip();
if (buffer.remaining() == 1 && buffer.get(0) == 0) {
// Handshake: wir kennen die UUID über TCP
// Suche den ConnectedClient mit passender TCP-Adresse
Optional<ConnectedClient> clientOpt = connectedClients.stream()
.filter(c -> c.getTcpSocket().getInetAddress().equals(((InetSocketAddress) sender).getAddress()))
.findFirst();
if (clientOpt.isPresent()) {
ConnectedClient client = clientOpt.get();
clientUdpAddresses.put(client.getUniqueID(), sender);
}
continue; // kein normales Packet parsen
}
DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining())
);
UUID uuid = UUID.fromString(dis.readUTF());
int packetId = dis.readInt();
// Speichere die UDP-Adresse des Clients
clientUdpAddresses.put(uuid, sender);
for (ConnectedClient connectedClient : connectedClients) {
if (connectedClient.getUniqueID().equals(uuid)) {
packetHandler.readPacket(dis, uuid, packetId);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stop() {
if (tcpThread != null) tcpThread.interrupt();
if (udpThread != null) udpThread.interrupt();
new ArrayList<>(connectedClients).forEach(ConnectedClient::disconnect);
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
tcpSocket = null;
udpChannel = null;
udpPort = -1;
}
public boolean isTCPOnline() {
return tcpSocket != null && tcpSocket.isBound();
}
public boolean isUDPEnabled() {
return udpPort != -1;
}
public boolean isUDPConnected() {
return isUDPEnabled() && isTCPOnline() && udpChannel != null && udpChannel.isOpen();
}
public PacketHandler getPacketHandler() {
return packetHandler;
}
public List<ConnectedClient> getConnectedClients() {
return connectedClients;
}
public SocketAddress getClientUdpAddress(UUID uuid) {
return clientUdpAddresses.get(uuid);
}
}

View File

@@ -0,0 +1,10 @@
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
/**
* Defines client certificate requirements for TLS.
*/
public enum ClientAuthMode {
NONE,
OPTIONAL,
REQUIRED
}

View File

@@ -0,0 +1,86 @@
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
/**
* Utility class for creating SSLContexts.
*/
public final class SSLContexts {
private SSLContexts() {}
/**
* Creates an SSLContext.
*
* @param trustAll If true, disables certificate validation (SSL still active)
* @param keyStore Optional keystore for client authentication
* @param keyPassword Keystore password
*/
public static SSLContext create(boolean trustAll, KeyStore keyStore, char[] keyPassword) {
try {
TrustManager[] trustManagers = trustAll
? new TrustManager[]{new TrustAllX509()}
: null;
KeyManager[] keyManagers = null;
if (keyStore != null) {
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPassword);
keyManagers = kmf.getKeyManagers();
}
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(keyManagers, trustManagers, new SecureRandom());
return ctx;
} catch (Exception e) {
throw new IllegalStateException("Failed to create SSLContext", e);
}
}
/**
* Creates an SSLContext using explicit key and trust stores.
*
* @param keyStore Optional keystore for client/server authentication
* @param keyPassword Keystore password
* @param trustStore Optional truststore for certificate validation (root CAs)
*/
public static SSLContext create(KeyStore keyStore, char[] keyPassword, KeyStore trustStore) {
try {
KeyManager[] keyManagers = null;
if (keyStore != null) {
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPassword);
keyManagers = kmf.getKeyManagers();
}
TrustManager[] trustManagers = null;
if (trustStore != null) {
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
trustManagers = tmf.getTrustManagers();
}
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(keyManagers, trustManagers, new SecureRandom());
return ctx;
} catch (Exception e) {
throw new IllegalStateException("Failed to create SSLContext", e);
}
}
/**
* TrustManager that accepts all certificates.
*/
private static final class TrustAllX509 implements X509TrustManager {
@Override public void checkClientTrusted(X509Certificate[] c, String a) {}
@Override public void checkServerTrusted(X509Certificate[] c, String a) {}
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
}

View File

@@ -0,0 +1,80 @@
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
/**
* Helper class for encrypting/decrypting UDP packets.
*/
public final class UdpCrypto {
private static final SecureRandom RNG = new SecureRandom();
private static final int GCM_TAG_BITS = 128;
private UdpCrypto() {}
/**
* Generates a random AES-256 key for UDP.
*/
public static SecretKey generateKey() {
byte[] key = new byte[32];
RNG.nextBytes(key);
return new SecretKeySpec(key, "AES");
}
/**
* Creates a key from raw bytes.
*/
public static SecretKey fromBytes(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, "AES");
}
/**
* Encrypts a UDP payload with AES-GCM.
*
* @param key AES key
* @param plaintext payload
* @param aad additional authenticated data (e.g., UUID + packetId)
* @return ByteBuffer ready to send
*/
public static ByteBuffer encrypt(SecretKey key, byte[] plaintext, byte[] aad) throws Exception {
byte[] iv = new byte[12];
RNG.nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_BITS, iv));
if (aad != null) cipher.updateAAD(aad);
byte[] ciphertext = cipher.doFinal(plaintext);
ByteBuffer buffer = ByteBuffer.allocate(12 + ciphertext.length);
buffer.put(iv).put(ciphertext);
buffer.flip();
return buffer;
}
/**
* Decrypts a UDP payload with AES-GCM.
*
* @param key AES key
* @param in ByteBuffer received
* @param aad additional authenticated data (must match encryption)
* @return plaintext
*/
public static byte[] decrypt(SecretKey key, ByteBuffer in, byte[] aad) throws Exception {
byte[] iv = new byte[12];
in.get(iv);
byte[] ct = new byte[in.remaining()];
in.get(ct);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_BITS, iv));
if (aad != null) cipher.updateAAD(aad);
return cipher.doFinal(ct);
}
}

View File

@@ -1,28 +0,0 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.*;
import dev.unlegitdqrk.unlegitlibrary.network.system.tcp.*;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
public class Client extends EventListener {
static EventManager eventManager = new EventManager();
static NetworkClient client;
public static void main(String[] args) throws Exception {
PacketHandler packetHandler = new PacketHandler();
packetHandler.registerPacket(() -> new TestTextPacket(""));
client = new NetworkClient(packetHandler);
client.connect("127.0.0.1", 25565);
client.sendPacket(new TestTextPacket("client: tcp"), TransportProtocol.TCP);
client.sendPacket(new TestTextPacket("client: udp"), TransportProtocol.UDP);
}
}

View File

@@ -1,42 +0,0 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.*;
import dev.unlegitdqrk.unlegitlibrary.network.system.tcp.*;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import java.io.IOException;
public class TestServerMain extends EventListener {
static NetworkServer server;
static EventManager eventManager = new EventManager();
public static void main(String[] args) throws Exception {
PacketHandler packetHandler = new PacketHandler();
packetHandler.registerPacket(() -> new TestTextPacket(""));
eventManager.registerListener(new TestServerMain());
server = new NetworkServer(packetHandler);
server.start(25565, 25566);
server.events = new NetworkServer.EventsServer() {
@Override
public void onConnect(ConnectedClient client) {
try {
client.sendPacket(new TestTextPacket("server: tcp"), TransportProtocol.TCP);
client.sendPacket(new TestTextPacket("server: udp"), TransportProtocol.UDP);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
}
}

View File

@@ -1,65 +0,0 @@
/*
* 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://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import java.io.*;
import java.util.UUID;
/**
* Simple text packet used for round-trip transport tests over TCP and UDP.
*/
public final class TestTextPacket extends Packet {
private volatile String message;
@Override
public int getPacketID() {
return 100;
}
/**
* Creates a packet with a message.
*
* @param message message
*/
public TestTextPacket(String message) {
this.message = message;
}
public TestTextPacket() {
}
/**
* @return message
*/
public String getMessage() {
return message;
}
@Override
public void write(DataOutputStream stream) {
try {
stream.writeUTF(message == null ? "" : message);
System.out.println("[SEND] " + message);
} catch (IOException e) {
throw new IllegalStateException("Failed to write TestTextPacket: " + e.getMessage(), e);
}
}
@Override
public void read(DataInputStream stream, UUID clientID) {
try {
this.message = stream.readUTF();
System.out.println("[RECEIVE] " + this.message);
} catch (IOException e) {
throw new IllegalStateException("Failed to read TestTextPacket: " + e.getMessage(), e);
}
}
}