Compare commits

...

4 Commits

Author SHA1 Message Date
UnlegitDqrk
dc3ccd47ba Small change 2026-02-22 19:12:11 +01:00
UnlegitDqrk
fb0b8ecf76 Refactored using IntelliJ 2026-02-21 19:49:27 +01:00
UnlegitDqrk
66c87f4f28 Merge remote-tracking branch 'origin/master' 2026-02-08 21:27:21 +01:00
UnlegitDqrk
7b941e75f3 Bug fixed 2026-02-08 21:26:44 +01:00
16 changed files with 517 additions and 356 deletions

213
README.MD
View File

@@ -1,20 +1,49 @@
# UnlegitLibrary # UnlegitLibrary
## Overview UnlegitLibrary is a modular Java library focused on reusable core building blocks:
UnlegitLibrary is a general-purpose Java utility library that bundles a modular event handling, commands, addon loading, network communication (TCP/UDP with optional
event system, command framework, addon loader, networking (TCP/UDP with optional TLS), TLS), and various utility classes.
and a wide set of math/number/string/file/reflection helpers.
## Modules ## Contents
- Addon system: loader + lifecycle events - Features
- Maven Integration
- ArgumentParser
- NetworkSystem (TCP/UDP + TLS/mTLS)
- Create Certificates
- License
## Features
- Addon system: loader, lifecycle, addon events
- Event system: listeners, priorities, cancellable events - Event system: listeners, priorities, cancellable events
- Command system: command manager, permissions, execution events - Command system: command manager, permissions, execute events
- Network system: TCP/UDP transport, packet handling, optional TLS, UDP encryption - NetworkSystem: TCP/UDP, packet handling, optional TLS security, UDP encryption
- Utilities: math/number helpers, strings, colors, files, reflection, logging - Utilities: number/math, strings/colors, files, reflection, logging
- Argument parsing: register arguments, validate values, run callbacks - ArgumentParser: registration, validation, and execution of arguments
## Maven Integration
```xml
<repositories>
<repository>
<id>repounlegitdqrk</id>
<url>https://repo.unlegitdqrk.dev/api/packages/unlegitdqrk/maven</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>
```
## ArgumentParser ## ArgumentParser
Basic usage ### Basic Example
```java ```java
ArgumentParser parser = new ArgumentParser(args); ArgumentParser parser = new ArgumentParser(args);
@@ -29,76 +58,61 @@ parser.registerArgument(mode);
parser.runArguments(); parser.runArguments();
``` ```
Value rules ### Value Rules
- Constructor: `Argument(name, description, required, requireValue, optionalValue, values)` - Constructor: `Argument(name, description, required, requireValue, optionalValue, values)`
- `requireValue=false` and `optionalValue=false`: argument has no value - `requireValue=false` and `optionalValue=false`: no value allowed
- `requireValue=true` and `optionalValue=false`: value must be present - `requireValue=true` and `optionalValue=false`: value is required
- `requireValue=false` and `optionalValue=true`: value is optional - `requireValue=false` and `optionalValue=true`: value is optional
- `requireValue=true` and `optionalValue=true` is not allowed - `requireValue=true` and `optionalValue=true`: invalid combination
- If `values` is not empty, the provided value must be in that list - If `values` is not empty, the provided value must be in the list
## 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
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
## Include in own projects
````
<repositories>
<repository>
<id>repounlegitdqrk</id>
<url>https://repo.unlegitdqrk.dev/api/packages/unlegitdqrk/maven</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>
````
## NetworkSystem (TCP/UDP + TLS) ## NetworkSystem (TCP/UDP + TLS)
- TCP is the control channel (handshake, packet routing). - TCP is the control channel (handshake, routing, session data).
- UDP is optional and encrypted with a symmetric key negotiated over TCP. - UDP is optional and encrypted using a symmetric key negotiated over TCP.
- TLS can be enabled or disabled. For TLS, configure KeyStore/TrustStore explicitly. - TLS can be enabled or disabled.
- mTLS is supported: set client auth mode to REQUIRED and provide a TrustStore on the server. - mTLS is supported (`ClientAuthMode.REQUIRED` + TrustStore on the server side).
### Basic usage ### Basic Example
```java ```java
PacketHandler packetHandler = new PacketHandler(); PacketHandler packetHandler = new PacketHandler();
packetHandler.registerPacket(() -> new ExamplePacket("")); packetHandler.registerPacket(() -> new ExamplePacket(""));
EventManager eventManager = new EventManager();
NetworkServer server = new NetworkServer(packetHandler); NetworkServer server = new NetworkServer.Builder()
server.configureSSL(false, ClientAuthMode.NONE); .packetHandler(packetHandler)
.eventManager(eventManager)
.sslEnabled(false)
.clientAuthMode(ClientAuthMode.NONE)
.build();
server.start(25565, 25566); server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler); NetworkClient client = new NetworkClient.Builder()
client.configureSSL(false); .packetHandler(packetHandler)
.sslEnabled(false)
.build();
client.connect("127.0.0.1", 25565); client.connect("127.0.0.1", 25565);
``` ```
### TLS with TrustStore (server validation) ### TLS with TrustStore (Server Validation)
```java ```java
KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray()); KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray());
KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray()); KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray());
EventManager eventManager = new EventManager();
NetworkServer server = new NetworkServer(packetHandler); NetworkServer server = new NetworkServer.Builder()
server.configureSSL(true, ClientAuthMode.NONE, serverKeyStore, "changeit".toCharArray(), null); .packetHandler(packetHandler)
.eventManager(eventManager)
.sslEnabled(true)
.clientAuthMode(ClientAuthMode.NONE)
.keyStore(serverKeyStore, "changeit".toCharArray())
.build();
server.start(25565, 25566); server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler); NetworkClient client = new NetworkClient.Builder()
client.configureSSL(true, null, null, clientTrustStore); .packetHandler(packetHandler)
.sslEnabled(true)
.trustStore(clientTrustStore)
.build();
client.connect("127.0.0.1", 25565); client.connect("127.0.0.1", 25565);
``` ```
@@ -108,54 +122,55 @@ KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray()
KeyStore serverTrustStore = loadStore("certs/server-trust.p12", "changeit".toCharArray()); KeyStore serverTrustStore = loadStore("certs/server-trust.p12", "changeit".toCharArray());
KeyStore clientKeyStore = loadStore("certs/client.p12", "changeit".toCharArray()); KeyStore clientKeyStore = loadStore("certs/client.p12", "changeit".toCharArray());
KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray()); KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray());
EventManager eventManager = new EventManager();
NetworkServer server = new NetworkServer(packetHandler); NetworkServer server = new NetworkServer.Builder()
server.configureSSL(true, ClientAuthMode.REQUIRED, serverKeyStore, "changeit".toCharArray(), serverTrustStore); .packetHandler(packetHandler)
.eventManager(eventManager)
.sslEnabled(true)
.clientAuthMode(ClientAuthMode.REQUIRED)
.keyStore(serverKeyStore, "changeit".toCharArray())
.trustStore(serverTrustStore)
.build();
server.start(25565, 25566); server.start(25565, 25566);
NetworkClient client = new NetworkClient(packetHandler); NetworkClient client = new NetworkClient.Builder()
client.configureSSL(true, clientKeyStore, "changeit".toCharArray(), clientTrustStore); .packetHandler(packetHandler)
.sslEnabled(true)
.keyStore(clientKeyStore, "changeit".toCharArray())
.trustStore(clientTrustStore)
.build();
client.connect("127.0.0.1", 25565); client.connect("127.0.0.1", 25565);
``` ```
## Certificate generation for NetworkSystem ## Certificates for NetworkSystem
### Creating Root-CA: ### Create Root CA
```` ```bash
openssl genrsa -out myCA.key 4096 openssl genrsa -out myCA.key 4096
openssl req -x509 -new -key myCA.key -sha256 -days 365 -out myCA.pem -addtext basicConstraints=critical,CA:TRUE -addtext keyUsage=critical,keyCertSign,cRLSign openssl req -x509 -new -key myCA.key -sha256 -days 365 -out myCA.pem -addtext basicConstraints=critical,CA:TRUE -addtext keyUsage=critical,keyCertSign,cRLSign
```
- `myCA.key`: private CA key (keep secret)
- `myCA.pem`: public root certificate
myCA.key = private Key for CA (keep secret) ### Create Server Certificate Based on Root CA
myCA.pem = public Root-Certificate for signing server and client certificates ```bash
````
### Creating Server Certificate based on Root-CA:
````
openssl genrsa -out server.key 2048 openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr 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 365 -sha256 openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 365 -sha256
```
- `server.key`: private server key
- `server.crt`: server certificate signed by your root CA
server.key = private Key for Server ### Optional: Create Client Certificate Based on Root CA
server.crt = Server-Certificate signed by Root-CA ```bash
````
### Optional: Creating Client Certificate based on Root-CA:
````
openssl genrsa -out client.key 2048 openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr 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 365 -sha256 openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 365 -sha256
```
- `client.key`: private client key
- `client.crt`: client certificate signed by your root CA
client.key = private Key for Client ### Helper: Load PKCS12 Store in Java
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
### Helper: load PKCS12 stores in Java
```java ```java
private static KeyStore loadStore(String path, char[] password) throws Exception { private static KeyStore loadStore(String path, char[] password) throws Exception {
KeyStore store = KeyStore.getInstance("PKCS12"); KeyStore store = KeyStore.getInstance("PKCS12");
@@ -165,3 +180,17 @@ private static KeyStore loadStore(String path, char[] password) throws Exception
return store; return store;
} }
``` ```
## License
### GNU General Public License v3.0 (GPLv3)
Default license for all users, projects, and distributions unless explicitly
agreed otherwise.
- License file in project: `LICENSE`
- Reference: https://repo.unlegitdqrk.dev/UnlegitDqrk/UnlegitLibrary/src/LICENSE
### Open Autonomous Public License (OAPL)
Special exception exclusively for the Open Autonomous Connection (OAC) project.
Within that context, OAPL terms take precedence.
- Reference: https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL

26
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>dev.unlegitdqrk</groupId> <groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>unlegitlibrary</artifactId>
<version>1.8.0</version> <version>1.8.3</version>
<url>https://unlegitdqrk.dev/</url> <url>https://unlegitdqrk.dev/</url>
<description>Just a big library</description> <description>Just a big library</description>
@@ -65,26 +65,6 @@
For OAC, OAPL terms take precedence; for all other users, GPLv3 remains binding. For OAC, OAPL terms take precedence; for all other users, GPLv3 remains binding.
</comments> </comments>
</license> </license>
<license>
<name>LPGL 3</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.html#license-text</url>
</license>
<license>
<name>LPGL 2.1</name>
<url>https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.en#SEC1</url>
</license>
<license>
<name>WTPL License</name>
<url>https://github.com/ronmamo/reflections/tree/master?tab=WTFPL-1-ov-file</url>
</license>
<license>
<name>Apache License 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
<license>
<name>MIT License</name>
<url>https://opensource.org/license/mit</url>
</license>
</licenses> </licenses>
<build> <build>
@@ -94,8 +74,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
<configuration> <configuration>
<source>16</source> <source>25</source>
<target>16</target> <target>25</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -21,6 +21,22 @@ public class Argument {
private final boolean optionalValue; private final boolean optionalValue;
private ArgumentRun run; private ArgumentRun run;
public Argument(String name, String description, boolean argRequired) {
this(name, description, argRequired, false, false, new ArrayList<>());
}
public Argument(String name, String description, boolean argRequired, boolean requireValue, boolean optionalValue, List<String> values) {
if (requireValue && optionalValue) {
throw new IllegalArgumentException("requireValue and optionalValue cannot both be true");
}
this.name = name;
this.description = description;
this.required = argRequired;
this.values = values == null ? new ArrayList<>() : values;
this.requireValue = requireValue;
this.optionalValue = optionalValue;
}
public ArgumentRun getRun() { public ArgumentRun getRun() {
return run; return run;
} }
@@ -52,20 +68,4 @@ public class Argument {
public boolean isOptionalValue() { public boolean isOptionalValue() {
return optionalValue; return optionalValue;
} }
public Argument(String name, String description, boolean argRequired) {
this(name, description, argRequired, false, false, new ArrayList<>());
}
public Argument(String name, String description, boolean argRequired, boolean requireValue, boolean optionalValue, List<String> values) {
if (requireValue && optionalValue) {
throw new IllegalArgumentException("requireValue and optionalValue cannot both be true");
}
this.name = name;
this.description = description;
this.required = argRequired;
this.values = values == null ? new ArrayList<>() : values;
this.requireValue = requireValue;
this.optionalValue = optionalValue;
}
} }

View File

@@ -34,40 +34,50 @@ public class ArgumentParser {
} }
public void runArguments() { public void runArguments() {
arguments.stream().filter(Argument::isRequired).forEach(arg -> { arguments.stream().filter(Argument::isRequired).forEach(argument -> {
if (!Arrays.stream(args).toList().contains(arg.getName())) { boolean present = Arrays.stream(args).anyMatch(token -> token.equalsIgnoreCase(argument.getName()));
throw new IllegalArgumentException("Missing argument: " + arg.getName()); if (!present) throw new IllegalArgumentException("Missing argument: " + argument.getName());
}
}); });
for (int i = 0; i <= args.length - 1; i++) { for (int i = 0; i < args.length; i++) {
String arg = args[i]; Argument argument = findArgument(args[i]);
if (argument == null) continue;
for (Argument argument : arguments) { Optional<String> value = Optional.empty();
if (argument.getName().equalsIgnoreCase(arg)) { boolean nextTokenAvailable = i + 1 < args.length;
if (argument.isRequireValue()) { boolean nextTokenIsArgument = nextTokenAvailable && findArgument(args[i + 1]) != null;
if (i + 1 >= args.length) {
if (argument.isOptionalValue()) {
if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.empty());
} else {
throw new IllegalArgumentException("Missing value for argument: " + argument.getName());
}
} else {
String value = args[i + 1];
if (!argument.getValues().isEmpty() && !argument.getValues().contains(value)) {
StringBuilder possibleValues = new StringBuilder();
for (int i1 = 0; i1 < argument.getValues().size(); i1++) {
possibleValues.append(argument.getValues().get(i1));
if (i1 != argument.getValues().size() - 1) possibleValues.append(", ");
}
throw new IllegalArgumentException("Invalid argument value '" + value + "' for argument '" + argument.getName() + "'! Possible: " + possibleValues.toString()); if (argument.isRequireValue()) {
} else if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.of(value)); if (!nextTokenAvailable || nextTokenIsArgument) {
i++; throw new IllegalArgumentException("Missing value for argument: " + argument.getName());
}
} else if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.empty());
} }
String nextValue = args[++i];
validateValue(argument, nextValue);
value = Optional.of(nextValue);
} else if (argument.isOptionalValue() && nextTokenAvailable && !nextTokenIsArgument) {
String nextValue = args[++i];
validateValue(argument, nextValue);
value = Optional.of(nextValue);
} }
if (argument.getRun() != null) argument.getRun().onRun(argument, value);
} }
} }
private Argument findArgument(String token) {
for (Argument argument : arguments) {
if (argument.getName().equalsIgnoreCase(token)) return argument;
}
return null;
}
private void validateValue(Argument argument, String value) {
if (argument.getValues().isEmpty()) return;
if (argument.getValues().contains(value)) return;
String possibleValues = String.join(", ", argument.getValues());
throw new IllegalArgumentException(
"Invalid argument value '" + value + "' for argument '" + argument.getName() + "'! Possible: " + possibleValues
);
}
} }

View File

@@ -17,10 +17,16 @@ public final class CommandManager {
// Registriere die Klasse, instanziiere sie einmalig // Registriere die Klasse, instanziiere sie einmalig
public void registerCommand(Class<? extends Command> commandClass) { public void registerCommand(Class<? extends Command> commandClass) {
if (commandClass == null) throw new IllegalArgumentException("commandClass cannot be null");
if (commandInstances.containsKey(commandClass)) return; if (commandInstances.containsKey(commandClass)) return;
try { try {
Command instance = commandClass.getDeclaredConstructor().newInstance(); Command instance;
try {
instance = commandClass.getDeclaredConstructor(CommandManager.class).newInstance(this);
} catch (NoSuchMethodException ignored) {
instance = commandClass.getDeclaredConstructor().newInstance();
}
commandInstances.put(commandClass, instance); commandInstances.put(commandClass, instance);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) { NoSuchMethodException e) {
@@ -58,7 +64,12 @@ public final class CommandManager {
} }
public void execute(CommandExecutor executor, String line) { public void execute(CommandExecutor executor, String line) {
String[] split = line.trim().split("\\s+"); if (line == null) return;
String trimmed = line.trim();
if (trimmed.isEmpty()) return;
String[] split = trimmed.split("\\s+");
if (split.length == 0) return; if (split.length == 0) return;
String commandLabel = split[0]; String commandLabel = split[0];

View File

@@ -4,47 +4,135 @@ import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class EventManager { public final class EventManager {
// eventClass -> priority -> listenerInstance -> method private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<Class<? extends Event>, Map<EventPriority, Map<Object, Method>>> registeredListener =
new ConcurrentHashMap<>();
// listenerClass -> set of listener instances (identity-based) // eventClass -> priority -> handlers
private final Map<Class<? extends Event>, Map<EventPriority, List<RegisteredListener>>> registeredListeners =
new IdentityHashMap<>();
// listenerClass -> listener instances (identity-based)
private final Map<Class<? extends EventListener>, Set<EventListener>> eventListeners = private final Map<Class<? extends EventListener>, Set<EventListener>> eventListeners =
new ConcurrentHashMap<>(); new IdentityHashMap<>();
public EventManager() { // listener instance -> registered handler descriptors (identity-based)
} private final Map<EventListener, List<RegisteredListener>> listenerRegistrations =
new IdentityHashMap<>();
public void registerListener(Class<? extends EventListener> clazz) throws Exception { public void registerListener(Class<? extends EventListener> clazz) throws Exception {
if (clazz == null) throw new IllegalArgumentException("Listener class cannot be null"); if (clazz == null) throw new IllegalArgumentException("Listener class cannot be null");
EventListener instance = clazz.getDeclaredConstructor().newInstance(); registerListener(clazz.getDeclaredConstructor().newInstance());
registerListener(instance);
} }
public void registerListener(EventListener listener) { public void registerListener(EventListener listener) {
if (listener == null) throw new IllegalArgumentException("Listener instance cannot be null"); if (listener == null) throw new IllegalArgumentException("Listener instance cannot be null");
// Track instance set for class (identity-based) lock.writeLock().lock();
eventListeners try {
.computeIfAbsent(listener.getClass(), k -> Collections.newSetFromMap(new IdentityHashMap<>())) Set<EventListener> instances = eventListeners
.add(listener); .computeIfAbsent(listener.getClass(), ignored -> Collections.newSetFromMap(new IdentityHashMap<>()));
registerListenerInstance(listener); // Ignore duplicate registrations of the same listener instance.
if (!instances.add(listener)) {
return;
}
registerListenerInstance(listener);
} finally {
lock.writeLock().unlock();
}
} }
private void registerListenerInstance(Object listener) { public void unregisterListener(Class<? extends EventListener> clazz) {
// Walk up class hierarchy so superclass @Listener methods are registered too if (clazz == null) return;
lock.writeLock().lock();
try {
Set<EventListener> instances = eventListeners.remove(clazz);
if (instances == null || instances.isEmpty()) return;
for (EventListener listener : new ArrayList<>(instances)) {
unregisterListenerInstance(listener);
}
} finally {
lock.writeLock().unlock();
}
}
public void unregisterListener(EventListener listener) {
if (listener == null) return;
lock.writeLock().lock();
try {
Set<EventListener> instances = eventListeners.get(listener.getClass());
if (instances != null) {
instances.remove(listener);
if (instances.isEmpty()) {
eventListeners.remove(listener.getClass());
}
}
unregisterListenerInstance(listener);
} finally {
lock.writeLock().unlock();
}
}
public boolean isListenerRegistered(Class<? extends EventListener> clazz) {
if (clazz == null) return false;
lock.readLock().lock();
try {
Set<EventListener> instances = eventListeners.get(clazz);
return instances != null && !instances.isEmpty();
} finally {
lock.readLock().unlock();
}
}
public void executeEvent(Event event) {
if (event == null) return;
List<RegisteredListener> snapshot = new ArrayList<>();
lock.readLock().lock();
try {
Map<EventPriority, List<RegisteredListener>> byPriority = registeredListeners.get(event.getClass());
if (byPriority == null) return;
for (EventPriority priority : EventPriority.values()) {
List<RegisteredListener> listeners = byPriority.get(priority);
if (listeners != null && !listeners.isEmpty()) {
snapshot.addAll(listeners);
}
}
} finally {
lock.readLock().unlock();
}
for (RegisteredListener registered : snapshot) {
try {
registered.method.setAccessible(true);
registered.method.invoke(registered.listener, event);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
private void registerListenerInstance(EventListener listener) {
List<RegisteredListener> registered = listenerRegistrations.computeIfAbsent(listener, ignored -> new ArrayList<>());
// Walk up class hierarchy so superclass listener methods are also registered.
for (Class<?> c = listener.getClass(); c != null && c != Object.class; c = c.getSuperclass()) { for (Class<?> c = listener.getClass(); c != null && c != Object.class; c = c.getSuperclass()) {
Method[] methods = c.getDeclaredMethods(); Method[] methods = c.getDeclaredMethods();
for (Method method : methods) { for (Method method : methods) {
Listener annotation = method.getAnnotation(Listener.class); Listener annotation = method.getAnnotation(Listener.class);
if (annotation == null) continue; if (annotation == null) continue;
if (method.getParameterCount() != 1) continue; if (method.getParameterCount() != 1) continue;
Class<?> param = method.getParameterTypes()[0]; Class<?> param = method.getParameterTypes()[0];
@@ -53,68 +141,37 @@ public final class EventManager {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<? extends Event> eventClass = (Class<? extends Event>) param; Class<? extends Event> eventClass = (Class<? extends Event>) param;
registeredListener RegisteredListener registration = new RegisteredListener(listener, method, eventClass, annotation.priority());
.computeIfAbsent(eventClass, k -> new EnumMap<>(EventPriority.class))
.computeIfAbsent(annotation.priority(), k -> Collections.synchronizedMap(new IdentityHashMap<>())) registeredListeners
.put(listener, method); .computeIfAbsent(eventClass, ignored -> new EnumMap<>(EventPriority.class))
.computeIfAbsent(annotation.priority(), ignored -> new ArrayList<>())
.add(registration);
registered.add(registration);
} }
} }
} }
public synchronized void unregisterListener(Class<? extends EventListener> clazz) { private void unregisterListenerInstance(EventListener listener) {
if (clazz == null) return; List<RegisteredListener> registrations = listenerRegistrations.remove(listener);
if (registrations == null || registrations.isEmpty()) return;
Set<EventListener> instances = eventListeners.remove(clazz); for (RegisteredListener registration : registrations) {
if (instances == null || instances.isEmpty()) return; Map<EventPriority, List<RegisteredListener>> byPriority = registeredListeners.get(registration.eventClass);
if (byPriority == null) continue;
for (EventListener instance : instances) { List<RegisteredListener> listeners = byPriority.get(registration.priority);
unregisterListener(instance); if (listeners == null) continue;
listeners.removeIf(registered -> registered.listener == listener && registered.method.equals(registration.method));
if (listeners.isEmpty()) byPriority.remove(registration.priority);
if (byPriority.isEmpty()) registeredListeners.remove(registration.eventClass);
} }
} }
public synchronized void unregisterListener(EventListener listener) { private record RegisteredListener(EventListener listener, Method method, Class<? extends Event> eventClass,
if (listener == null) return; EventPriority priority) {
Set<EventListener> instances = eventListeners.get(listener.getClass());
if (instances != null) {
instances.remove(listener);
if (instances.isEmpty()) eventListeners.remove(listener.getClass());
}
for (Map<EventPriority, Map<Object, Method>> byPriority : registeredListener.values()) {
for (Map<Object, Method> listeners : byPriority.values()) {
listeners.remove(listener);
}
}
// cleanup empty maps
registeredListener.entrySet().removeIf(e ->
e.getValue().values().stream().allMatch(Map::isEmpty)
);
}
public boolean isListenerRegistered(Class<? extends EventListener> clazz) {
Set<EventListener> instances = eventListeners.get(clazz);
return instances != null && !instances.isEmpty();
}
public void executeEvent(Event event) {
if (event == null) return;
Map<EventPriority, Map<Object, Method>> byPriority = registeredListener.get(event.getClass());
if (byPriority == null) return;
for (EventPriority priority : EventPriority.values()) {
Map<Object, Method> listeners = byPriority.getOrDefault(priority, Collections.emptyMap());
for (Map.Entry<Object, Method> entry : listeners.entrySet()) {
try {
Method method = entry.getValue();
method.setAccessible(true);
method.invoke(entry.getKey(), event);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
} }
} }

View File

@@ -20,8 +20,10 @@ import java.util.zip.ZipInputStream;
public final class FileUtils extends DefaultMethodsOverrider { public final class FileUtils extends DefaultMethodsOverrider {
public static String getSuffix(File file) { public static String getSuffix(File file) {
String[] splitName = file.getName().split("\\."); String name = file.getName();
return splitName[splitName.length - 1]; int index = name.lastIndexOf('.');
if (index < 0 || index == name.length() - 1) return "";
return name.substring(index + 1);
} }
public static String readFileFromResource(String filePath) throws IOException { public static String readFileFromResource(String filePath) throws IOException {
@@ -31,7 +33,7 @@ public final class FileUtils extends DefaultMethodsOverrider {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line; String line;
while ((line = reader.readLine()) != null) content.append(line).append(""); while ((line = reader.readLine()) != null) content.append(line);
} }
inputStream.close(); inputStream.close();
@@ -49,8 +51,10 @@ public final class FileUtils extends DefaultMethodsOverrider {
} }
public static String getName(File file) { public static String getName(File file) {
String[] splitName = file.getName().split("\\."); String name = file.getName();
return splitName[splitName.length - 2]; int index = name.lastIndexOf('.');
if (index <= 0) return name;
return name.substring(0, index);
} }
@Deprecated @Deprecated
@@ -142,9 +146,10 @@ public final class FileUtils extends DefaultMethodsOverrider {
public static List<String> readFileLines(File file) throws IOException { public static List<String> readFileLines(File file) throws IOException {
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
String str; String str;
while ((str = in.readLine()) != null) lines.add(str); while ((str = in.readLine()) != null) lines.add(str);
}
return lines; return lines;
} }

View File

@@ -16,15 +16,16 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientC
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent; 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.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; 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.SSLContexts;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import java.io.*; import java.io.*;
import java.net.*; import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel; import java.nio.channels.DatagramChannel;
import java.security.KeyStore; import java.security.KeyStore;
@@ -35,18 +36,15 @@ import java.util.UUID;
*/ */
public class NetworkClient { public class NetworkClient {
private Socket tcpSocket;
private DatagramChannel udpChannel;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private SecretKey udpKey;
private final PacketHandler packetHandler; private final PacketHandler packetHandler;
private final EventManager eventManager; private final EventManager eventManager;
private final Thread tcpReceiveThread; private Socket tcpSocket;
private final Thread udpReceiveThread; private DatagramChannel udpChannel;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private SecretKey udpKey;
private Thread tcpReceiveThread;
private Thread udpReceiveThread;
private boolean sslEnabled = true; private boolean sslEnabled = true;
private volatile UUID uniqueID; private volatile UUID uniqueID;
@@ -72,13 +70,11 @@ public class NetworkClient {
* Creates a client with a custom {@link EventManager}. * Creates a client with a custom {@link EventManager}.
* *
* @param packetHandler packet handler * @param packetHandler packet handler
* @param eventManager event manager * @param eventManager event manager
*/ */
public NetworkClient(PacketHandler packetHandler, EventManager eventManager) { public NetworkClient(PacketHandler packetHandler, EventManager eventManager) {
this.packetHandler = packetHandler; this.packetHandler = packetHandler;
this.eventManager = eventManager; this.eventManager = eventManager;
this.tcpReceiveThread = new Thread(this::tcpReceive);
this.udpReceiveThread = new Thread(this::udpReceive);
} }
/** /**
@@ -93,10 +89,10 @@ public class NetworkClient {
/** /**
* Enables or disables TLS with explicit key and trust stores. * Enables or disables TLS with explicit key and trust stores.
* *
* @param sslEnabled enable TLS * @param sslEnabled enable TLS
* @param keyStore client key store (optional) * @param keyStore client key store (optional)
* @param keyPassword key store password * @param keyPassword key store password
* @param trustStore trust store (required when TLS is enabled) * @param trustStore trust store (required when TLS is enabled)
*/ */
public void configureSSL(boolean sslEnabled, public void configureSSL(boolean sslEnabled,
KeyStore keyStore, KeyStore keyStore,
@@ -120,10 +116,12 @@ public class NetworkClient {
/** /**
* Connects to a server. * Connects to a server.
* *
* @param host server host * @param host server host
* @param tcpPort server TCP port * @param tcpPort server TCP port
*/ */
public void connect(String host, int tcpPort) throws Exception { public void connect(String host, int tcpPort) throws Exception {
if (isTCPConnected()) throw new IllegalStateException("Client is already connected");
this.host = host; this.host = host;
this.tcpPort = tcpPort; this.tcpPort = tcpPort;
@@ -142,6 +140,7 @@ public class NetworkClient {
inputStream = new DataInputStream(tcpSocket.getInputStream()); inputStream = new DataInputStream(tcpSocket.getInputStream());
outputStream = new DataOutputStream(tcpSocket.getOutputStream()); outputStream = new DataOutputStream(tcpSocket.getOutputStream());
tcpReceiveThread = new Thread(this::tcpReceive, "unlegitlibrary-client-tcp-receive");
tcpReceiveThread.start(); tcpReceiveThread.start();
long deadline = System.currentTimeMillis() + handshakeTimeoutMillis; long deadline = System.currentTimeMillis() + handshakeTimeoutMillis;
@@ -176,7 +175,6 @@ public class NetworkClient {
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
disconnect(); disconnect();
} }
} }
@@ -190,6 +188,7 @@ public class NetworkClient {
udpChannel = DatagramChannel.open(); udpChannel = DatagramChannel.open();
udpChannel.connect(new InetSocketAddress(host, udpPort)); udpChannel.connect(new InetSocketAddress(host, udpPort));
udpReceiveThread = new Thread(this::udpReceive, "unlegitlibrary-client-udp-receive");
udpReceiveThread.start(); udpReceiveThread.start();
// initial handshake // initial handshake
@@ -223,7 +222,8 @@ public class NetworkClient {
packetId = dis.readInt(); packetId = dis.readInt();
if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) { if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) {
eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.UDP)); eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.UDP));
} else eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP)); } else
eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
} catch (Exception ignored) { } catch (Exception ignored) {
eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP)); eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
@@ -234,7 +234,7 @@ public class NetworkClient {
/** /**
* Sends a packet via TCP or UDP. * Sends a packet via TCP or UDP.
* *
* @param packet packet to send * @param packet packet to send
* @param protocol transport protocol * @param protocol transport protocol
*/ */
public void sendPacket(Packet packet, TransportProtocol protocol) throws Exception { public void sendPacket(Packet packet, TransportProtocol protocol) throws Exception {
@@ -258,18 +258,32 @@ public class NetworkClient {
*/ */
public void disconnect() { public void disconnect() {
// Stop threads first // Stop threads first
tcpReceiveThread.interrupt(); if (tcpReceiveThread != null) tcpReceiveThread.interrupt();
udpReceiveThread.interrupt(); if (udpReceiveThread != null) udpReceiveThread.interrupt();
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {} try {
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {} if (inputStream != null) inputStream.close();
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} } catch (IOException ignored) {
try { if (udpChannel != null) udpChannel.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; inputStream = null;
outputStream = null; outputStream = null;
tcpSocket = null; tcpSocket = null;
udpChannel = null; udpChannel = null;
tcpReceiveThread = null;
udpReceiveThread = null;
eventManager.executeEvent(new ClientDisconnectedEvent(this)); eventManager.executeEvent(new ClientDisconnectedEvent(this));
@@ -360,7 +374,7 @@ public class NetworkClient {
/** /**
* Sets the key store and its password. * Sets the key store and its password.
* *
* @param keyStore key store * @param keyStore key store
* @param keyPassword key store password * @param keyPassword key store password
* @return builder * @return builder
*/ */

View File

@@ -15,10 +15,13 @@ import java.util.UUID;
public abstract class Packet { public abstract class Packet {
public Packet() {} public Packet() {
}
public abstract int getPacketID(); public abstract int getPacketID();
public abstract void read(DataInputStream stream, UUID clientID) throws IOException; public abstract void read(DataInputStream stream, UUID clientID) throws IOException;
public abstract void write(DataOutputStream stream) throws IOException; public abstract void write(DataOutputStream stream) throws IOException;

View File

@@ -1,7 +1,8 @@
package dev.unlegitdqrk.unlegitlibrary.network.system.packets; package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
import java.io.*; import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@@ -26,13 +27,13 @@ public class PacketHandler {
factories.put(id, factory); factories.put(id, factory);
} }
/** /**
* Reads a packet from the input stream. * Reads a packet from the input stream.
* Expects the packet to be serialized as an object. * Expects the packet to be serialized as an object.
* *
* @param input DataInputStream to read from * @param input DataInputStream to read from
* @return Packet instance or null if failed * @return Packet instance or null if failed
*/ */
public Packet 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); Supplier<? extends Packet> factory = factories.get(id);
if (factory == null) return null; if (factory == null) return null;

View File

@@ -17,28 +17,27 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.io.*; import java.io.ByteArrayOutputStream;
import java.net.SocketAddress; import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel; import java.nio.channels.DatagramChannel;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public class ConnectedClient { public class ConnectedClient {
private volatile UUID uniqueID;
private Socket tcpSocket;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private DatagramChannel udpChannel;
private final Thread tcpReceiveThread; private final Thread tcpReceiveThread;
private final NetworkServer server; private final NetworkServer server;
private volatile UUID uniqueID;
private Socket tcpSocket;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private DatagramChannel udpChannel;
private int udpPort = -1; private int udpPort = -1;
private SecretKey udpKey; private final SecretKey udpKey;
public ConnectedClient(NetworkServer server, Socket tcpSocket, UUID uniqueID, int udpPort) throws IOException { public ConnectedClient(NetworkServer server, Socket tcpSocket, UUID uniqueID, int udpPort) throws IOException {
this.tcpSocket = tcpSocket; this.tcpSocket = tcpSocket;
@@ -125,10 +124,18 @@ public class ConnectedClient {
public void disconnect() { public void disconnect() {
tcpReceiveThread.interrupt(); tcpReceiveThread.interrupt();
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} try {
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {} if (tcpSocket != null) tcpSocket.close();
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {} } catch (IOException ignored) {
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {} }
try {
if (inputStream != null) inputStream.close();
} catch (IOException ignored) {
}
try {
if (outputStream != null) outputStream.close();
} catch (IOException ignored) {
}
tcpSocket = null; tcpSocket = null;
udpChannel = null; udpChannel = null;
@@ -156,7 +163,8 @@ public class ConnectedClient {
public boolean isConnected() { public boolean isConnected() {
if (!isTCPConnected()) return false; if (!isTCPConnected()) return false;
return isUDPEnabled() && isUDPConnected(); if (isUDPEnabled()) return isUDPConnected();
return true;
} }
public UUID getUniqueID() { public UUID getUniqueID() {

View File

@@ -21,11 +21,17 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
import javax.net.ssl.*; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel; import java.nio.channels.DatagramChannel;
import java.security.KeyStore; import java.security.KeyStore;
@@ -37,17 +43,14 @@ import java.util.concurrent.atomic.AtomicReference;
* TCP/UDP server with optional TLS and UDP encryption. * TCP/UDP server with optional TLS and UDP encryption.
*/ */
public class NetworkServer { 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 Map<SocketAddress, UUID> clientUdpAddresses = new ConcurrentHashMap<>();
private final PacketHandler packetHandler; private final PacketHandler packetHandler;
private final EventManager eventManager; private final EventManager eventManager;
private final List<ConnectedClient> connectedClients; private final List<ConnectedClient> connectedClients;
private ServerSocket tcpSocket;
private Thread tcpThread;
private DatagramChannel udpChannel;
private Thread udpThread;
private int udpPort = -1; private int udpPort = -1;
/* === TLS CONFIG === */ /* === TLS CONFIG === */
@@ -61,7 +64,7 @@ public class NetworkServer {
* Creates a server with a custom {@link EventManager}. * Creates a server with a custom {@link EventManager}.
* *
* @param packetHandler packet handler * @param packetHandler packet handler
* @param eventManager event manager * @param eventManager event manager
*/ */
private NetworkServer(PacketHandler packetHandler, EventManager eventManager) { private NetworkServer(PacketHandler packetHandler, EventManager eventManager) {
this.packetHandler = packetHandler; this.packetHandler = packetHandler;
@@ -72,7 +75,7 @@ public class NetworkServer {
/** /**
* Enables or disables TLS and sets the client authentication mode. * Enables or disables TLS and sets the client authentication mode.
* *
* @param sslEnabled enable TLS * @param sslEnabled enable TLS
* @param clientAuthMode client authentication mode * @param clientAuthMode client authentication mode
*/ */
public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode) { public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode) {
@@ -83,11 +86,11 @@ public class NetworkServer {
/** /**
* Enables or disables TLS with explicit key and trust stores. * Enables or disables TLS with explicit key and trust stores.
* *
* @param sslEnabled enable TLS * @param sslEnabled enable TLS
* @param clientAuthMode client authentication mode * @param clientAuthMode client authentication mode
* @param keyStore server key store (required when TLS is enabled) * @param keyStore server key store (required when TLS is enabled)
* @param keyPassword key store password * @param keyPassword key store password
* @param trustStore trust store (required for client auth) * @param trustStore trust store (required for client auth)
*/ */
public void configureSSL(boolean sslEnabled, public void configureSSL(boolean sslEnabled,
ClientAuthMode clientAuthMode, ClientAuthMode clientAuthMode,
@@ -157,8 +160,14 @@ public class NetworkServer {
if (tcpThread != null) tcpThread.interrupt(); if (tcpThread != null) tcpThread.interrupt();
if (udpThread != null) udpThread.interrupt(); if (udpThread != null) udpThread.interrupt();
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} try {
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {} if (tcpSocket != null) tcpSocket.close();
} catch (IOException ignored) {
}
try {
if (udpChannel != null) udpChannel.close();
} catch (IOException ignored) {
}
List<ConnectedClient> snapshot; List<ConnectedClient> snapshot;
synchronized (connectedClients) { synchronized (connectedClients) {
@@ -209,39 +218,45 @@ public class NetworkServer {
private void udpReceiveLoop() { private void udpReceiveLoop() {
ByteBuffer buffer = ByteBuffer.allocate(65536); ByteBuffer buffer = ByteBuffer.allocate(65536);
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
try { try {
buffer.clear(); buffer.clear();
SocketAddress sender = udpChannel.receive(buffer); SocketAddress sender = udpChannel.receive(buffer);
if (sender == null) continue; if (sender == null) continue;
buffer.flip(); buffer.flip();
if (buffer.remaining() < 13) continue; // 12-byte IV + at least 1 byte payload if (buffer.remaining() < 13) continue;
UUID mappedUuid = clientUdpAddresses.get(sender); UUID mappedUuid = clientUdpAddresses.get(sender);
if (mappedUuid != null) { if (mappedUuid != null) {
ConnectedClient mappedClient = getClientByUuid(mappedUuid); ConnectedClient mappedClient = getClientByUuid(mappedUuid);
if (mappedClient != null) { if (mappedClient != null && tryHandleUdpPacket(mappedClient, sender, buffer.asReadOnlyBuffer())) {
if (tryHandleUdpPacket(mappedClient, sender, buffer.asReadOnlyBuffer())) { continue;
continue;
}
} }
} }
// Fallback: try all clients if mapping missing or decrypt failed (e.g., NAT rebinding)
List<ConnectedClient> snapshot; List<ConnectedClient> snapshot;
synchronized (connectedClients) { synchronized (connectedClients) {
snapshot = new ArrayList<>(connectedClients); snapshot = new ArrayList<>(connectedClients);
} }
for (ConnectedClient client : snapshot) { for (ConnectedClient client : snapshot) {
if (tryHandleUdpPacket(client, sender, buffer.asReadOnlyBuffer())) { if (tryHandleUdpPacket(client, sender, buffer.asReadOnlyBuffer())) break;
break;
}
} }
} catch (java.nio.channels.ClosedChannelException e) {
// Expected on shutdown / close from another thread.
break;
} catch (IOException e) { } catch (IOException e) {
if (tcpSocket != null) { // Not fatal for TCP. Keep UDP loop alive unless channel is actually closed.
try { tcpSocket.close(); } catch (IOException ignored) {} if (udpChannel == null || !udpChannel.isOpen() || Thread.currentThread().isInterrupted()) {
break;
} }
if (Thread.currentThread().isInterrupted()) { e.printStackTrace();
try {
Thread.sleep(50);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break; break;
} }
} }
@@ -331,7 +346,8 @@ public class NetworkServer {
eventManager.executeEvent(new S_PacketReadEvent(this, client, packet, TransportProtocol.UDP)); eventManager.executeEvent(new S_PacketReadEvent(this, client, packet, TransportProtocol.UDP));
return true; return true;
} }
} catch (Exception ignored) {} } catch (Exception ignored) {
}
eventManager.executeEvent(new S_PacketFailedReadEvent(this, client, packet, packetId, TransportProtocol.UDP)); eventManager.executeEvent(new S_PacketFailedReadEvent(this, client, packet, packetId, TransportProtocol.UDP));
@@ -405,7 +421,7 @@ public class NetworkServer {
/** /**
* Sets the key store and its password. * Sets the key store and its password.
* *
* @param keyStore key store * @param keyStore key store
* @param keyPassword key store password * @param keyPassword key store password
* @return builder * @return builder
*/ */

View File

@@ -10,13 +10,14 @@ import java.security.cert.X509Certificate;
*/ */
public final class SSLContexts { public final class SSLContexts {
private SSLContexts() {} private SSLContexts() {
}
/** /**
* Creates an SSLContext. * Creates an SSLContext.
* *
* @param trustAll If true, disables certificate validation (SSL still active) * @param trustAll If true, disables certificate validation (SSL still active)
* @param keyStore Optional keystore for client authentication * @param keyStore Optional keystore for client authentication
* @param keyPassword Keystore password * @param keyPassword Keystore password
*/ */
public static SSLContext create(boolean trustAll, KeyStore keyStore, char[] keyPassword) { public static SSLContext create(boolean trustAll, KeyStore keyStore, char[] keyPassword) {
@@ -45,9 +46,9 @@ public final class SSLContexts {
/** /**
* Creates an SSLContext using explicit key and trust stores. * Creates an SSLContext using explicit key and trust stores.
* *
* @param keyStore Optional keystore for client/server authentication * @param keyStore Optional keystore for client/server authentication
* @param keyPassword Keystore password * @param keyPassword Keystore password
* @param trustStore Optional truststore for certificate validation (root CAs) * @param trustStore Optional truststore for certificate validation (root CAs)
*/ */
public static SSLContext create(KeyStore keyStore, char[] keyPassword, KeyStore trustStore) { public static SSLContext create(KeyStore keyStore, char[] keyPassword, KeyStore trustStore) {
try { try {
@@ -79,8 +80,17 @@ public final class SSLContexts {
* TrustManager that accepts all certificates. * TrustManager that accepts all certificates.
*/ */
private static final class TrustAllX509 implements X509TrustManager { private static final class TrustAllX509 implements X509TrustManager {
@Override public void checkClientTrusted(X509Certificate[] c, String a) {} @Override
@Override public void checkServerTrusted(X509Certificate[] c, String a) {} public void checkClientTrusted(X509Certificate[] c, String a) {
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }
@Override
public void checkServerTrusted(X509Certificate[] c, String a) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
} }
} }

View File

@@ -15,7 +15,8 @@ public final class UdpCrypto {
private static final SecureRandom RNG = new SecureRandom(); private static final SecureRandom RNG = new SecureRandom();
private static final int GCM_TAG_BITS = 128; private static final int GCM_TAG_BITS = 128;
private UdpCrypto() {} private UdpCrypto() {
}
/** /**
* Generates a random AES-256 key for UDP. * Generates a random AES-256 key for UDP.
@@ -36,9 +37,9 @@ public final class UdpCrypto {
/** /**
* Encrypts a UDP payload with AES-GCM. * Encrypts a UDP payload with AES-GCM.
* *
* @param key AES key * @param key AES key
* @param plaintext payload * @param plaintext payload
* @param aad additional authenticated data (e.g., UUID + packetId) * @param aad additional authenticated data (e.g., UUID + packetId)
* @return ByteBuffer ready to send * @return ByteBuffer ready to send
*/ */
public static ByteBuffer encrypt(SecretKey key, byte[] plaintext, byte[] aad) throws Exception { public static ByteBuffer encrypt(SecretKey key, byte[] plaintext, byte[] aad) throws Exception {
@@ -61,7 +62,7 @@ public final class UdpCrypto {
* Decrypts a UDP payload with AES-GCM. * Decrypts a UDP payload with AES-GCM.
* *
* @param key AES key * @param key AES key
* @param in ByteBuffer received * @param in ByteBuffer received
* @param aad additional authenticated data (must match encryption) * @param aad additional authenticated data (must match encryption)
* @return plaintext * @return plaintext
*/ */

View File

@@ -14,23 +14,29 @@ public class WebUtils extends DefaultMethodsOverrider {
private static final String ACCEPTED_RESPONSE = "application/json"; private static final String ACCEPTED_RESPONSE = "application/json";
public static String httpGet(String url) throws Exception { public static String httpGet(String url) throws Exception {
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
connection.connect(); connection.connect();
if (connection.getResponseCode() == 204) return null; try {
if (connection.getResponseCode() == 204) return null;
return InputStreamUtils.readInputStream(connection.getInputStream()); return InputStreamUtils.readInputStream(connection.getInputStream());
} finally {
connection.disconnect();
}
} }
public static byte[] httpGetByte(String url) throws Exception { public static byte[] httpGetByte(String url) throws Exception {
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
connection.connect(); connection.connect();
if (connection.getResponseCode() == 204) return null; try {
if (connection.getResponseCode() == 204) return null;
return InputStreamUtils.readInputStream2Byte(connection.getInputStream()); return InputStreamUtils.readInputStream2Byte(connection.getInputStream());
} finally {
connection.disconnect();
}
} }
public static String toHttps(String url) { public static String toHttps(String url) {

View File

@@ -9,9 +9,15 @@ import java.util.Random;
public class MathHelper extends DefaultMethodsOverrider { public class MathHelper extends DefaultMethodsOverrider {
private static final float[] SIN_TABLE = new float[65536]; private static final float[] SIN_TABLE = new float[65536];
private static final double TAU = 60.283185307179586D; private static final double TAU = 6.283185307179586D;
private static final Random rng = new Random(); private static final Random rng = new Random();
static {
for (int i = 0; i < SIN_TABLE.length; i++) {
SIN_TABLE[i] = (float) Math.sin(i * TAU / SIN_TABLE.length);
}
}
public static double round(double value, int places) { public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException(); if (places < 0) throw new IllegalArgumentException();
@@ -129,11 +135,15 @@ public class MathHelper extends DefaultMethodsOverrider {
} }
public static double randomInRange(double min, double max) { public static double randomInRange(double min, double max) {
return (double) rng.nextInt((int) (max - min + 1.0D)) + max; if (max < min) throw new IllegalArgumentException("max must be >= min");
if (max == min) return min;
return min + rng.nextDouble() * (max - min);
} }
public static double getRandomFloat(float min, float max) { public static double getRandomFloat(float min, float max) {
return (float) rng.nextInt((int) (max - min + 1.0F)) + max; if (max < min) throw new IllegalArgumentException("max must be >= min");
if (max == min) return min;
return min + rng.nextFloat() * (max - min);
} }
public static double randomNumber(double max, double min) { public static double randomNumber(double max, double min) {
@@ -141,10 +151,10 @@ public class MathHelper extends DefaultMethodsOverrider {
} }
public static double wrapRadians(double angle) { public static double wrapRadians(double angle) {
angle %= 20.283185307179586D; angle %= TAU;
if (angle >= 1.141592653589793D) angle -= 20.283185307179586D; if (angle >= Math.PI) angle -= TAU;
if (angle < -1.141592653589793D) angle += 20.283185307179586D; if (angle < -Math.PI) angle += TAU;
return angle; return angle;
} }