- Very big NetworkSystem updates

This commit is contained in:
2025-09-20 12:38:58 +02:00
parent d33b556578
commit b768fdfc40
7 changed files with 419 additions and 446 deletions

View File

@@ -10,3 +10,60 @@ A special exception applies exclusively to the project Open Autonomous Connectio
Within OAC, the UnlegitLibrary is also licensed under the OAPL.<br /> Within OAC, the UnlegitLibrary is also licensed under the OAPL.<br />
In this context, OAPL terms take precedence.<br /> In this context, OAPL terms take precedence.<br />
→ https://github.com/Open-Autonomous-Connection/OAPL → https://github.com/Open-Autonomous-Connection/OAPL
## Include in own projects
````
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/unlegitdqrk/unlegitlibrary</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>me.finn.unlegitlibrary</groupId>
<artifactId>unlegitlibrary</artifactId>
<version>1.5.15</version>
</dependency>
</dependencies>
````
## Certificate generation for NetworkSystem
### Creating Root-CA:
````
openssl genrsa -out myCA.key 4096
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 3650 -out myCA.pem
myCA.key = private Key for CA (keep secret)
myCA.pem = public Root-Certificate for signing server and client certificates
````
### Creating Server Certificate based on Root-CA:
````
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 825 -sha256
server.key = private Key for Server
server.crt = Server-Certificate signed by Root-CA
````
### Optional: Creating Client Certificate based on Root-CA:
````
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 825 -sha256
client.key = private Key for Client
client.crt = Client-Certificate signed by Root-CA
````
1. Generate a Root-CA. Every client and server NEED this Root-CA *.pem-File. Keep the *.key file private<br />
2. Generate a Server-Certificate
3. Optional: Generate a Client-Certificate
4. Put the Root-CA on your server and client in "certificates/ca"-Folder
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

View File

@@ -6,7 +6,7 @@
<groupId>me.finn.unlegitlibrary</groupId> <groupId>me.finn.unlegitlibrary</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>unlegitlibrary</artifactId>
<version>1.5.15</version> <version>1.6.0</version>
<url>https://unlegitdqrk.dev/UnlegitLibrary/</url> <url>https://unlegitdqrk.dev/UnlegitLibrary/</url>
<description>Just a big library</description> <description>Just a big library</description>
@@ -66,8 +66,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
<configuration> <configuration>
<source>1.8</source> <source>16</source>
<target>1.8</target> <target>16</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -1,11 +1,3 @@
/*
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package me.finn.unlegitlibrary.network.system.client; package me.finn.unlegitlibrary.network.system.client;
import me.finn.unlegitlibrary.event.EventManager; import me.finn.unlegitlibrary.event.EventManager;
@@ -13,15 +5,16 @@ import me.finn.unlegitlibrary.network.system.client.events.*;
import me.finn.unlegitlibrary.network.system.packets.Packet; import me.finn.unlegitlibrary.network.system.packets.Packet;
import me.finn.unlegitlibrary.network.system.packets.PacketHandler; import me.finn.unlegitlibrary.network.system.packets.PacketHandler;
import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket;
import me.finn.unlegitlibrary.network.utils.PemUtils;
import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider; import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider;
import me.finn.unlegitlibrary.utils.Logger; import me.finn.unlegitlibrary.utils.Logger;
import java.io.IOException; import javax.net.ssl.*;
import java.io.ObjectInputStream; import java.io.*;
import java.io.ObjectOutputStream;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.Socket; import java.net.Proxy;
import java.net.SocketException; import java.security.KeyStore;
import java.security.cert.CertificateFactory;
public final class NetworkClient { public final class NetworkClient {
private final String host; private final String host;
@@ -29,50 +22,33 @@ public final class NetworkClient {
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 maxReconnectAttempts;
private final int reconnectDelay;
private Socket socket;
private final int timeout; private final int timeout;
private SSLSocket socket;
private ObjectOutputStream outputStream; private ObjectOutputStream outputStream;
private ObjectInputStream inputStream; private ObjectInputStream inputStream;
private int clientID; private final SSLSocketFactory sslSocketFactory;
private int currentAttempts; private final SSLParameters sslParameters;
private NetworkClient(String host, int port, PacketHandler packetHandler, EventManager eventManager, Logger logger, int reconnectAttempts, int reconnectDelay, int timeout) {
this.host = host;
this.port = port;
this.clientID = -1;
this.timeout = timeout;
this.packetHandler = packetHandler; private final Thread receiveThread = new Thread(this::receive);
this.eventManager = eventManager;
this.logger = logger;
this.maxReconnectAttempts = reconnectAttempts; public PacketHandler getPacketHandler() {
this.reconnectDelay = reconnectDelay; return packetHandler;
this.currentAttempts = 0;
this.packetHandler.setClientInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket());
}
public int getClientID() {
return clientID;
} private final Thread receiveThread = new Thread(this::receive);
public void setClientID(int clientID) {
if (this.clientID == -1) this.clientID = clientID;
}
public Socket getSocket() {
return socket;
} }
public EventManager getEventManager() { public EventManager getEventManager() {
return eventManager; return eventManager;
} }
public PacketHandler getPacketHandler() { public ObjectInputStream getInputStream() {
return packetHandler; return inputStream;
}
public SSLSocket getSocket() {
return socket;
}
public ObjectOutputStream getOutputStream() {
return outputStream;
} }
public Logger getLogger() { public Logger getLogger() {
@@ -87,200 +63,188 @@ public final class NetworkClient {
return host; return host;
} }
public boolean isConnected() { private NetworkClient(String host, int port, PacketHandler packetHandler,
return socket != null && socket.isConnected() && !socket.isClosed() && socket.isBound() EventManager eventManager, Logger logger,
&& receiveThread.isAlive() && !receiveThread.isInterrupted(); int timeout, SSLSocketFactory sslSocketFactory,
SSLParameters sslParameters) {
this.host = host;
this.port = port;
this.packetHandler = packetHandler;
this.eventManager = eventManager;
this.logger = logger;
this.timeout = timeout;
this.sslSocketFactory = sslSocketFactory;
this.sslParameters = sslParameters;
this.packetHandler.setClientInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket());
} }
public boolean isAutoReconnectEnabled() { public boolean isConnected() {
return maxReconnectAttempts != 0; 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 (isConnected()) return false;
if (logger == null) System.out.println("Trying to connect to " + host + ":" + port + "..."); if (logger != null) logger.info("Trying to connect to " + host + ":" + port + "...");
else logger.info("Trying to connect to " + host + ":" + port + "..."); else System.out.println("Trying to connect to " + host + ":" + port + "...");
try { try {
socket = new Socket(host, port); if (sslSocketFactory == null) throw new ConnectException("SSL socket factory not set. Client certificate required!");
socket = (SSLSocket) sslSocketFactory.createSocket(host, port);
if (sslParameters != null) socket.setSSLParameters(sslParameters);
else {
SSLParameters defaultParams = socket.getSSLParameters();
defaultParams.setProtocols(new String[]{"TLSv1.3"});
socket.setSSLParameters(defaultParams);
}
socket.setTcpNoDelay(true); socket.setTcpNoDelay(true);
socket.setSoTimeout(timeout); socket.setSoTimeout(timeout);
try {
socket.startHandshake();
} catch (Exception handshakeEx) {
throw new ConnectException("Handshake failed: " + handshakeEx.getMessage());
}
outputStream = new ObjectOutputStream(socket.getOutputStream()); outputStream = new ObjectOutputStream(socket.getOutputStream());
inputStream = new ObjectInputStream(socket.getInputStream()); inputStream = new ObjectInputStream(socket.getInputStream());
receiveThread.start(); receiveThread.start();
if (currentAttempts == 0) currentAttempts++;
if (logger == null)
System.out.println("Connected to " + host + ":" + port + " (Attempts: " + currentAttempts + ")");
else logger.info("Connected to " + host + ":" + port + " (Attempts: " + currentAttempts + ")");
eventManager.executeEvent(new ClientConnectedEvent(this)); eventManager.executeEvent(new ClientConnectedEvent(this));
if (logger != null) logger.info("Connected to " + host + ":" + port);
currentAttempts = 0; else System.out.println("Connected to " + host + ":" + port);
return true; return true;
} catch (IOException exception) { } catch (Exception e) {
if (isAutoReconnectEnabled()) { throw new ConnectException("Failed to connect: " + e.getMessage());
try {
Thread.sleep(reconnectDelay);
} catch (InterruptedException sleepThreadException) {
if (logger == null) System.err.println("Reconnect exception: " + sleepThreadException.getMessage());
else logger.exception("Reconnect exception", sleepThreadException);
}
currentAttempts++;
if (currentAttempts < maxReconnectAttempts || maxReconnectAttempts < 0) return connect();
}
} }
throw new ConnectException("Failed to connect to " + host + ":" + port);
} }
private void receive() { private void receive() {
if (!isConnected()) return; try {
while (isConnected()) {
while (isConnected()) {
try {
Object received = inputStream.readObject(); Object received = inputStream.readObject();
handleReceived(received);
if (received instanceof Integer) {
int packetID = (Integer) received;
if (packetHandler.isPacketIDRegistered(packetID)) {
Packet packet = packetHandler.getPacketByID(packetID);
if (packetHandler.handlePacket(packetID, packet, inputStream))
eventManager.executeEvent(new C_PacketReceivedEvent(this, packet));
else
eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received));
} catch (SocketException ignored) {
try {
disconnect();
} catch (ConnectException exception) {
exception.printStackTrace();
}
} catch (Exception exception) {
exception.printStackTrace();
return;
} }
} catch (Exception e) { disconnect(); }
}
private void handleReceived(Object received) throws IOException, ClassNotFoundException {
if (received instanceof Integer id) {
if (packetHandler.isPacketIDRegistered(id)) {
Packet packet = packetHandler.getPacketByID(id);
boolean handled = packetHandler.handlePacket(id, packet, inputStream);
if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet));
else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received));
} else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received));
}
public synchronized boolean disconnect() {
if (!isConnected()) return false;
try {
receiveThread.interrupt();
if (outputStream != null) outputStream.close();
if (inputStream != null) inputStream.close();
if (socket != null) socket.close();
} catch (IOException e) {
if (logger != null) logger.exception("Error closing connection", e);
else System.err.println("Error closing connection: " + e.getMessage());
} finally {
socket = null;
outputStream = null;
inputStream = null;
eventManager.executeEvent(new ClientDisconnectedEvent(this));
} }
return true;
} }
public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException {
if (!isConnected()) return false; if (!isConnected()) return false;
boolean sent = packetHandler.sendPacket(packet, outputStream);
try { if (sent) eventManager.executeEvent(new C_PacketSendEvent(this, packet));
if (packetHandler.sendPacket(packet, outputStream)) { else eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet));
eventManager.executeEvent(new C_PacketSendEvent(this, packet)); return sent;
return true;
} else {
eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet));
return false;
}
} catch (IOException | ClassNotFoundException exception) {
throw exception;
}
}
public synchronized boolean disconnect() throws ConnectException {
boolean wasConnected = isConnected();
if (wasConnected) {
if (logger == null) System.out.println("Disconnecting from server...");
else logger.info("Disconnecting from server...");
}
if (receiveThread.isAlive() && !receiveThread.isInterrupted()) receiveThread.interrupt();
if (wasConnected) {
try {
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException exception) {
if (logger == null) System.err.println("Failed to close socket: " + exception.getMessage());
else logger.exception("Failed to close socket", exception);
}
}
outputStream = null;
inputStream = null;
socket = null;
currentAttempts = 0;
if (wasConnected) {
if (logger == null) System.out.println("Disconnected from server");
else logger.info("Disconnected from server");
}
eventManager.executeEvent(new ClientDisconnectedEvent(this));
clientID = -1;
if (isAutoReconnectEnabled() && (currentAttempts < maxReconnectAttempts || maxReconnectAttempts < 0))
return connect();
return true;
} }
// --- Builder ---
public static class ClientBuilder extends DefaultMethodsOverrider { public static class ClientBuilder extends DefaultMethodsOverrider {
private String host; private String host;
private int port; private int port;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private EventManager eventManager; private EventManager eventManager;
private Logger logger; private Logger logger;
private int timeout = 5000;
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private File caFolder;
private File clientFolder;
private File keyFolder;
private int maxReconnectAttempts = 0; public ClientBuilder setHost(String host) { this.host = host; return this; }
private int reconnectDelay = 3000; public ClientBuilder setPort(int port) { this.port = port; return this; }
private int timeout = 0; public ClientBuilder setPacketHandler(PacketHandler handler) { this.packetHandler = handler; return this; }
public ClientBuilder setEventManager(EventManager manager) { this.eventManager = manager; return this; }
public ClientBuilder setLogger(Logger logger) { this.logger = logger; return this; }
public ClientBuilder setTimeout(int timeout) { this.timeout = timeout; return this; }
public ClientBuilder setSSLSocketFactory(SSLSocketFactory factory) { this.sslSocketFactory = factory; return this; }
public ClientBuilder setSSLParameters(SSLParameters params) { this.sslParameters = params; return this; }
public ClientBuilder setRootCAFolder(File folder) { this.caFolder = folder; return this; }
public ClientBuilder setClientCertificatesFolder(File clientFolder, File keyFolder) { this.clientFolder = clientFolder; this.keyFolder = keyFolder; return this; }
public final NetworkClient build() { public NetworkClient build() {
return new NetworkClient(host, port, packetHandler, eventManager, logger, maxReconnectAttempts, reconnectDelay, timeout); if (sslSocketFactory == null && caFolder != null) {
try { sslSocketFactory = createSSLSocketFactory(caFolder, clientFolder, keyFolder); }
catch (Exception e) { throw new RuntimeException("Failed to create SSLFactory", e); }
}
return new NetworkClient(host, port, packetHandler, eventManager, logger,
timeout, sslSocketFactory, sslParameters);
} }
public final ClientBuilder setEventManager(EventManager eventManager) { public static SSLSocketFactory createSSLSocketFactory(File caFolder, File clientCertFolder, File clientKeyFolder) throws Exception {
this.eventManager = eventManager; // TrustStore (Root-CAs)
return this; KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
} trustStore.load(null, null);
public final ClientBuilder setHost(String host) { int caIndex = 1;
this.host = host; for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) {
return this; 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);
}
}
public final ClientBuilder setLogger(Logger logger) { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
this.logger = logger; tmf.init(trustStore);
return this;
}
public final ClientBuilder setMaxReconnectAttempts(int maxReconnectAttempts) { KeyStore keyStore = KeyStore.getInstance("PKCS12");
this.maxReconnectAttempts = maxReconnectAttempts; keyStore.load(null, null); // Kein Passwort nötig
return this;
}
public final ClientBuilder setPacketHandler(PacketHandler packetHandler) { int clientIndex = 1;
this.packetHandler = packetHandler; for (File certFile : clientCertFolder.listFiles((f) -> f.getName().endsWith(".crt"))) {
return this; String baseName = certFile.getName().replace(".crt", "");
} File keyFile = new File(clientKeyFolder, baseName + ".key");
if (!keyFile.exists()) continue;
public final ClientBuilder setPort(int port) { java.security.PrivateKey key = PemUtils.loadPrivateKey(keyFile);
this.port = port; java.security.cert.Certificate cert = PemUtils.loadCertificate(certFile);
return this;
}
public final ClientBuilder setReconnectDelay(int reconnectDelay) { keyStore.setKeyEntry("client" + (clientIndex++), key, null, new java.security.cert.Certificate[]{cert});
this.reconnectDelay = reconnectDelay; }
return this;
}
public final ClientBuilder setTimeout(int timeout) { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
this.timeout = timeout; kmf.init(keyStore, null);
return this;
// SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
} }
} }
} }

View File

@@ -1,35 +1,42 @@
/*
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package me.finn.unlegitlibrary.network.system.server; package me.finn.unlegitlibrary.network.system.server;
import me.finn.unlegitlibrary.network.system.packets.Packet; import me.finn.unlegitlibrary.network.system.packets.Packet;
import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket;
import me.finn.unlegitlibrary.network.system.server.events.*; import me.finn.unlegitlibrary.network.system.server.events.*;
import javax.net.ssl.SSLSession;
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.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.security.cert.X509Certificate;
public class ConnectionHandler { public class ConnectionHandler {
private SSLSocket socket;
public final NetworkServer networkServer;
private Socket socket;
private int clientID; private int clientID;
private ObjectOutputStream outputStream; private ObjectOutputStream outputStream;
private ObjectInputStream inputStream; private ObjectInputStream inputStream;
public ConnectionHandler(NetworkServer server, Socket socket, int clientID) throws IOException, ClassNotFoundException { private final NetworkServer server;
this.networkServer = server;
this.socket = socket; public SSLSocket getSocket() {
this.clientID = clientID; return socket;
}
public ObjectOutputStream getOutputStream() {
return outputStream;
}
public ObjectInputStream getInputStream() {
return inputStream;
}
public NetworkServer getServer() {
return server;
}
public ConnectionHandler(NetworkServer server, SSLSocket socket, int clientID) throws IOException, ClassNotFoundException {
this.server = server; this.socket = socket; this.clientID = clientID;
outputStream = new ObjectOutputStream(socket.getOutputStream()); outputStream = new ObjectOutputStream(socket.getOutputStream());
inputStream = new ObjectInputStream(socket.getInputStream()); inputStream = new ObjectInputStream(socket.getInputStream());
@@ -37,97 +44,57 @@ public class ConnectionHandler {
receiveThread.start(); receiveThread.start();
sendPacket(new ClientIDPacket()); sendPacket(new ClientIDPacket());
networkServer.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this)); server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this));
} public final Thread receiveThread = new Thread(this::receive);
public int getClientID() {
return clientID;
} }
public final boolean isConnected() { public final Thread receiveThread = new Thread(this::receive);
return networkServer.isRunning() && socket != null && socket.isConnected() && !socket.isClosed() && socket.isBound()
&& receiveThread.isAlive() && !receiveThread.isInterrupted(); public int getClientID() { return clientID; }
} public boolean isConnected() { return socket != null && socket.isConnected() && !socket.isClosed() && receiveThread.isAlive(); }
public synchronized boolean disconnect() { public synchronized boolean disconnect() {
boolean wasConnected = isConnected(); if (!isConnected()) return false;
if (receiveThread.isAlive()) receiveThread.interrupt();
if (wasConnected) { try { outputStream.close(); inputStream.close(); socket.close(); } catch (IOException ignored) {}
if (networkServer.getLogger() == null) socket = null; outputStream = null; inputStream = null; clientID = -1;
System.out.println("Client ID '" + clientID + "' is disconnecting from server...");
else networkServer.getLogger().info("Client ID '" + clientID + "' is disconnecting from server...");
}
if (receiveThread.isAlive() && !receiveThread.isInterrupted()) receiveThread.interrupt(); server.getConnectionHandlers().remove(this);
server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this));
if (wasConnected) {
try {
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException exception) {
if (networkServer.getLogger() == null)
System.err.println("Client ID '" + clientID + "' failed to close socket: " + exception.getMessage());
else
networkServer.getLogger().exception("Client ID '" + clientID + "' failed to close socket", exception);
}
}
outputStream = null;
inputStream = null;
socket = null;
networkServer.getConnectionHandlers().remove(this);
if (wasConnected) {
if (networkServer.getLogger() == null)
System.out.println("Client ID '" + clientID + "' disconnected from server");
else networkServer.getLogger().info("Client ID '" + clientID + "' disconnected from server");
}
networkServer.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this));
clientID = -1;
return true; return true;
} }
public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException {
if (!isConnected()) return false; if (!isConnected()) return false;
boolean sent = server.getPacketHandler().sendPacket(packet, outputStream);
if (networkServer.getPacketHandler().sendPacket(packet, outputStream)) { if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this));
networkServer.getEventManager().executeEvent(new S_PacketSendEvent(packet, this)); else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this));
return true;
} else { return sent;
networkServer.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this));
return false;
}
} }
private void receive() { private void receive() {
if (!isConnected()) return;
while (isConnected()) { while (isConnected()) {
try { try {
Object received = inputStream.readObject(); Object received = inputStream.readObject();
if (received instanceof Integer) { if (received instanceof Integer) {
int packetID = (Integer) received; int id = (Integer) received;
if (networkServer.getPacketHandler().isPacketIDRegistered(packetID)) {
Packet packet = networkServer.getPacketHandler().getPacketByID(packetID); if (server.getPacketHandler().isPacketIDRegistered(id)) {
if (networkServer.getPacketHandler().handlePacket(packetID, packet, inputStream)) Packet packet = server.getPacketHandler().getPacketByID(id);
networkServer.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet));
else if (server.getPacketHandler().handlePacket(id, packet, inputStream)) server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet));
networkServer.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet)); else server.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet));
} else } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this));
networkServer.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this));
} else networkServer.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); } catch (SocketException se) { disconnect(); }
} catch (SocketException ignored) { catch (Exception ex) {
if (server.getLogger() != null) server.getLogger().exception("Receive thread exception for client " + clientID, ex);
else System.err.println("Receive thread exception for client " + clientID + ": " + ex.getMessage());
disconnect(); disconnect();
} catch (Exception exception) {
exception.printStackTrace();
return;
} }
} }
} }
} }

View File

@@ -1,27 +1,21 @@
/*
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package me.finn.unlegitlibrary.network.system.server; package me.finn.unlegitlibrary.network.system.server;
import me.finn.unlegitlibrary.event.EventManager; import me.finn.unlegitlibrary.event.EventManager;
import me.finn.unlegitlibrary.network.system.packets.Packet;
import me.finn.unlegitlibrary.network.system.packets.PacketHandler; import me.finn.unlegitlibrary.network.system.packets.PacketHandler;
import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket;
import me.finn.unlegitlibrary.network.system.server.events.IncomingConnectionEvent; import me.finn.unlegitlibrary.network.system.server.events.IncomingConnectionEvent;
import me.finn.unlegitlibrary.network.utils.PemUtils;
import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider; import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider;
import me.finn.unlegitlibrary.utils.Logger; import me.finn.unlegitlibrary.utils.Logger;
import java.io.IOException; import javax.net.ssl.*;
import java.net.ServerSocket; import java.io.File;
import java.io.FileInputStream;
import java.net.Socket; import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public final class NetworkServer { public final class NetworkServer {
private final int port; private final int port;
@@ -29,33 +23,14 @@ public final class NetworkServer {
private final EventManager eventManager; private final EventManager eventManager;
private final Logger logger; private final Logger logger;
private final int timeout; private final int timeout;
private final int maxRestartAttempts; private SSLServerSocket serverSocket;
private final int restartDelay; private final SSLServerSocketFactory sslServerSocketFactory;
private final List<ConnectionHandler> connectionHandlers = new ArrayList<>(); private final List<ConnectionHandler> connectionHandlers = new ArrayList<>();
private int currentAttempts; private final Thread incomingThread = new Thread(this::incomingConnections);
private ServerSocket serverSocket;
private NetworkServer(int port, PacketHandler packetHandler, EventManager eventManager, Logger logger, int maxRestartAttempts, int restartDelay, int timeout) {
this.port = port;
this.timeout = timeout;
this.packetHandler = packetHandler; public List<ConnectionHandler> getConnectionHandlers() {
this.eventManager = eventManager; return connectionHandlers;
this.logger = logger;
this.maxRestartAttempts = maxRestartAttempts;
this.restartDelay = restartDelay;
this.currentAttempts = 0;
this.packetHandler.setServerInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket());
} public final Thread incomingConnectionThread = new Thread(this::incomingConnection);
public Logger getLogger() {
return logger;
}
public EventManager getEventManager() {
return eventManager;
} }
public int getPort() { public int getPort() {
@@ -66,164 +41,133 @@ public final class NetworkServer {
return packetHandler; return packetHandler;
} }
public List<ConnectionHandler> getConnectionHandlers() { public Logger getLogger() {
return connectionHandlers; return logger;
} }
public boolean isAutoRestartEnabled() { public SSLServerSocket getServerSocket() {
return maxRestartAttempts != 0; return serverSocket;
} }
public boolean isRunning() { public EventManager getEventManager() {
return serverSocket != null && !serverSocket.isClosed() && serverSocket.isBound() && return eventManager;
incomingConnectionThread.isAlive() && !incomingConnectionThread.isInterrupted();
} }
public ConnectionHandler getConnectionHandlerByID(int clientID) { private boolean requireClientCert;
for (ConnectionHandler connectionHandler : connectionHandlers)
if (connectionHandler.getClientID() == clientID) return connectionHandler; private NetworkServer(int port, PacketHandler packetHandler, EventManager eventManager,
return null; Logger logger, int timeout, SSLServerSocketFactory factory, boolean requireClientCert) {
this.port = port; this.packetHandler = packetHandler; this.eventManager = eventManager;
this.logger = logger; this.timeout = timeout; this.sslServerSocketFactory = factory;
this.packetHandler.setServerInstance(this);
this.packetHandler.registerPacket(new ClientIDPacket());
this.requireClientCert = requireClientCert;
} }
public synchronized boolean start() { public boolean start() {
if (isRunning()) return false;
if (logger == null) System.out.println("Trying to start on port " + port + "...");
else logger.info("Trying to start on port " + port + "...");
try { try {
serverSocket = new ServerSocket(port); serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
serverSocket.setNeedClientAuth(requireClientCert);
serverSocket.setSoTimeout(timeout); serverSocket.setSoTimeout(timeout);
serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"});
incomingConnectionThread.start(); incomingThread.start();
if (logger != null) logger.log("Server started on port " + port);
if (currentAttempts == 0) currentAttempts++; else System.out.println("Server started on port " + port);
if (logger == null) System.out.println("Started at port " + port + " (Attempts: " + currentAttempts + ")");
else logger.info("Started at port " + port + " (Attempts: " + currentAttempts + ")");
currentAttempts = 0;
return true; return true;
} catch (IOException exception) { } catch (Exception e) {if (logger != null) logger.exception("Failed to start", e);
if (maxRestartAttempts != 0) { else System.err.println("Failed to start: " + e.getMessage()); return false; }
try {
Thread.sleep(restartDelay);
} catch (InterruptedException sleepThreadException) {
if (logger == null) System.err.println("Restart exception: " + sleepThreadException.getMessage());
else logger.exception("Restart exception", sleepThreadException);
}
currentAttempts++;
if (currentAttempts <= maxRestartAttempts || maxRestartAttempts < 0) return start();
}
if (logger == null) System.err.println("Failed to start on port " + port + ": " + exception.getMessage());
else logger.exception("Failed to start on port " + port, exception);
}
return false;
} }
public boolean broadcastPacket(Packet packet) { private void incomingConnections() {
AtomicBoolean toReturn = new AtomicBoolean(false);
connectionHandlers.forEach(connectionHandler -> {
try {
if (!toReturn.get()) return;
toReturn.set(connectionHandler.sendPacket(packet));
} catch (IOException | ClassNotFoundException e) {
toReturn.set(false);
}
});
return toReturn.get();
}
private void incomingConnection() {
if (!isRunning()) return;
try { try {
while (isRunning()) { while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept(); Socket socket = serverSocket.accept();
socket.setTcpNoDelay(true); if (!(socket instanceof SSLSocket ssl)) { socket.close(); continue; }
socket.setSoTimeout(timeout); ssl.setTcpNoDelay(true);
ssl.setSoTimeout(timeout);
if (logger == null) System.out.println("Accepted connection from " + socket.getRemoteSocketAddress()); try { ssl.startHandshake(); }
else logger.info("Accepted connection from " + socket.getRemoteSocketAddress()); catch (Exception handshakeEx) {
if (logger != null) logger.exception("Handshake failed", handshakeEx);
IncomingConnectionEvent incomingConnectionEvent = new IncomingConnectionEvent(this, socket); else System.err.println("Handshake failed: " + handshakeEx.getMessage());
ssl.close();
eventManager.executeEvent(incomingConnectionEvent); continue;
if (incomingConnectionEvent.isCancelled()) {
socket.close();
return;
} }
ConnectionHandler connectionHandler = new ConnectionHandler(this, socket, connectionHandlers.size() + 1); IncomingConnectionEvent event = new IncomingConnectionEvent(this, ssl);
connectionHandlers.add(connectionHandler); eventManager.executeEvent(event);
if (event.isCancelled()) { ssl.close(); continue; }
try {
ConnectionHandler connectionHandler = new ConnectionHandler(this, ssl, connectionHandlers.size() + 1);
connectionHandlers.add(connectionHandler);
} catch (Exception exception) {
ssl.close();
continue;
}
} }
} catch (IOException | ClassNotFoundException exception) { } catch (Exception e) { e.printStackTrace(); }
exception.printStackTrace();
}
}
public boolean sendPacket(int clientID, Packet packet) throws IOException, ClassNotFoundException {
return getConnectionHandlerByID(clientID).sendPacket(packet);
}
public boolean sendPacket(Packet packet, int clientID) throws IOException, ClassNotFoundException {
return sendPacket(clientID, packet);
} }
// --- Builder ---
public static class ServerBuilder extends DefaultMethodsOverrider { public static class ServerBuilder extends DefaultMethodsOverrider {
private int port; private int port;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private EventManager eventManager; private EventManager eventManager;
private Logger logger; private Logger logger;
private int timeout = 5000;
private SSLServerSocketFactory factory;
private boolean requireClientCert;
private File caFolder;
private File serverCertFile;
private File serverKeyFile;
private int maxRestartAttempts = 0; public ServerBuilder setPort(int port) { this.port = port; return this; }
private int restartDelay = 3000; public ServerBuilder setPacketHandler(PacketHandler handler) { this.packetHandler = handler; return this; }
private int timeout = 0; public ServerBuilder setEventManager(EventManager manager) { this.eventManager = manager; return this; }
public ServerBuilder setLogger(Logger logger) { this.logger = logger; return this; }
public ServerBuilder setTimeout(int timeout) { this.timeout = timeout; return this; }
public ServerBuilder setSSLServerSocketFactory(SSLServerSocketFactory factory) { this.factory = factory; return this; }
public ServerBuilder setRequireClientCertificate(boolean requireClientCertificate) { this.requireClientCert = requireClientCertificate; return this; }
public ServerBuilder setRootCAFolder(File folder) { this.caFolder = folder; return this; }
public ServerBuilder setServerCertificate(File certFile, File keyFile) { this.serverCertFile = certFile; this.serverKeyFile = keyFile; return this; }
public final NetworkServer build() { public NetworkServer build() {
return new NetworkServer(port, packetHandler, eventManager, logger, maxRestartAttempts, restartDelay, timeout); if (factory == null && caFolder != null && serverCertFile != null && serverKeyFile != null) {
try { factory = createSSLServerSocketFactory(caFolder, serverCertFile, serverKeyFile); }
catch (Exception e) { throw new RuntimeException("Failed to create SSLServerSocketFactory", e); }
}
return new NetworkServer(port, packetHandler, eventManager, logger, timeout, factory, requireClientCert);
} }
public final ServerBuilder setEventManager(EventManager eventManager) { public static SSLServerSocketFactory createSSLServerSocketFactory(File caFolder, File serverCert, File serverKey) throws Exception {
this.eventManager = eventManager; // TrustStore (Root-CAs)
return this; KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
} trustStore.load(null, null);
public final ServerBuilder setLogger(Logger logger) { int caIndex = 1;
this.logger = logger; for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) {
return this; try (FileInputStream fis = new FileInputStream(caFile)) {
} java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile);
trustStore.setCertificateEntry("ca" + (caIndex++), cert);
}
}
public final ServerBuilder setMaxReconnectAttempts(int maxRestartAttempts) { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
this.maxRestartAttempts = maxRestartAttempts; tmf.init(trustStore);
return this;
}
public final ServerBuilder setPacketHandler(PacketHandler packetHandler) { KeyStore keyStore = KeyStore.getInstance("PKCS12");
this.packetHandler = packetHandler; keyStore.load(null, null);
return this; 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});
public final ServerBuilder setPort(int port) { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
this.port = port; kmf.init(keyStore, null);
return this;
}
public final ServerBuilder setReconnectDelay(int reconnectDelay) { SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
this.restartDelay = reconnectDelay; sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return this; return sslContext.getServerSocketFactory();
}
public final ServerBuilder setTimeout(int timeout) {
this.timeout = timeout;
return this;
} }
} }
} }

View File

@@ -11,13 +11,16 @@ package me.finn.unlegitlibrary.network.system.server.events;
import me.finn.unlegitlibrary.event.impl.CancellableEvent; import me.finn.unlegitlibrary.event.impl.CancellableEvent;
import me.finn.unlegitlibrary.network.system.server.NetworkServer; import me.finn.unlegitlibrary.network.system.server.NetworkServer;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.net.Socket; import java.net.Socket;
import java.security.cert.X509Certificate;
public class IncomingConnectionEvent extends CancellableEvent { public class IncomingConnectionEvent extends CancellableEvent {
public final NetworkServer server; public final NetworkServer server;
public final Socket socket; public final SSLSocket socket;
public IncomingConnectionEvent(NetworkServer server, Socket socket) { public IncomingConnectionEvent(NetworkServer server, SSLSocket socket) {
this.server = server; this.server = server;
this.socket = socket; this.socket = socket;
} }

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2025 UnlegitDqrk - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
* See LICENSE-File if exists
*/
package me.finn.unlegitlibrary.network.utils;
import java.io.*;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
public class PemUtils {
public static PrivateKey loadPrivateKey(File keyFile) throws Exception {
String keyPem = new String(Files.readAllBytes(keyFile.toPath()))
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(keyPem);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA"); // oder "EC" je nach Key
return kf.generatePrivate(spec);
}
public static X509Certificate loadCertificate(File certFile) throws Exception {
try (FileInputStream fis = new FileInputStream(certFile)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(fis);
}
}
}