Added SSL Support
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketFailedReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketSendEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.security.KeyStore;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TCP/UDP client with optional TLS and UDP encryption.
|
||||
*/
|
||||
public class NetworkClient {
|
||||
|
||||
private Socket tcpSocket;
|
||||
private DatagramChannel udpChannel;
|
||||
|
||||
private DataInputStream inputStream;
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
private SecretKey udpKey;
|
||||
|
||||
private final PacketHandler packetHandler;
|
||||
private final EventManager eventManager;
|
||||
private final Thread tcpReceiveThread;
|
||||
private final Thread udpReceiveThread;
|
||||
|
||||
private boolean sslEnabled = true;
|
||||
private volatile UUID uniqueID;
|
||||
|
||||
private String host;
|
||||
private int tcpPort = -1;
|
||||
private int udpPort = -1;
|
||||
private KeyStore keyStore;
|
||||
private char[] keyPassword;
|
||||
private KeyStore trustStore;
|
||||
private long handshakeTimeoutMillis = 5000;
|
||||
|
||||
/**
|
||||
* Creates a client with a default {@link EventManager}.
|
||||
*
|
||||
* @param packetHandler packet handler
|
||||
*/
|
||||
public NetworkClient(PacketHandler packetHandler) {
|
||||
this(packetHandler, new EventManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a client with a custom {@link EventManager}.
|
||||
*
|
||||
* @param packetHandler packet handler
|
||||
* @param eventManager event manager
|
||||
*/
|
||||
public NetworkClient(PacketHandler packetHandler, EventManager eventManager) {
|
||||
this.packetHandler = packetHandler;
|
||||
this.eventManager = eventManager;
|
||||
this.tcpReceiveThread = new Thread(this::tcpReceive);
|
||||
this.udpReceiveThread = new Thread(this::udpReceive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
*/
|
||||
public void configureSSL(boolean sslEnabled) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS with explicit key and trust stores.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
* @param keyStore client key store (optional)
|
||||
* @param keyPassword key store password
|
||||
* @param trustStore trust store (required when TLS is enabled)
|
||||
*/
|
||||
public void configureSSL(boolean sslEnabled,
|
||||
KeyStore keyStore,
|
||||
char[] keyPassword,
|
||||
KeyStore trustStore) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
this.keyStore = keyStore;
|
||||
this.keyPassword = keyPassword;
|
||||
this.trustStore = trustStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TCP handshake timeout in milliseconds.
|
||||
*
|
||||
* @param handshakeTimeoutMillis timeout in ms
|
||||
*/
|
||||
public void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) {
|
||||
this.handshakeTimeoutMillis = handshakeTimeoutMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a server.
|
||||
*
|
||||
* @param host server host
|
||||
* @param tcpPort server TCP port
|
||||
*/
|
||||
public void connect(String host, int tcpPort) throws Exception {
|
||||
this.host = host;
|
||||
this.tcpPort = tcpPort;
|
||||
|
||||
if (sslEnabled) {
|
||||
if (trustStore == null) {
|
||||
throw new IllegalStateException("SSL enabled but no TrustStore configured");
|
||||
}
|
||||
SSLContext ctx = SSLContexts.create(keyStore, keyPassword, trustStore);
|
||||
SSLSocket sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(host, tcpPort);
|
||||
sslSocket.startHandshake();
|
||||
tcpSocket = sslSocket;
|
||||
} else {
|
||||
tcpSocket = new Socket(host, tcpPort);
|
||||
}
|
||||
|
||||
inputStream = new DataInputStream(tcpSocket.getInputStream());
|
||||
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
|
||||
|
||||
tcpReceiveThread.start();
|
||||
|
||||
long deadline = System.currentTimeMillis() + handshakeTimeoutMillis;
|
||||
while (uniqueID == null) {
|
||||
if (System.currentTimeMillis() >= deadline) {
|
||||
disconnect();
|
||||
throw new IOException("TCP handshake timeout (no server response)");
|
||||
}
|
||||
Thread.sleep(5);
|
||||
}
|
||||
|
||||
Thread.sleep(10);
|
||||
eventManager.executeEvent(new ClientConnectedEvent(this));
|
||||
}
|
||||
|
||||
private void tcpReceive() {
|
||||
try {
|
||||
while (true) {
|
||||
UUID uuid = UUID.fromString(inputStream.readUTF());
|
||||
int packetId = inputStream.readInt();
|
||||
if (packetId == -1) {
|
||||
uniqueID = uuid;
|
||||
udpPort = inputStream.readInt();
|
||||
int keyLen = inputStream.readInt();
|
||||
byte[] keyBytes = new byte[keyLen];
|
||||
inputStream.readFully(keyBytes);
|
||||
udpKey = UdpCrypto.fromBytes(keyBytes);
|
||||
connectUDP();
|
||||
} else {
|
||||
Packet packet = null;
|
||||
if ((packet = packetHandler.readPacket(inputStream, uuid, packetId)) != null)
|
||||
eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.TCP));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void connectUDP() throws Exception {
|
||||
udpChannel = DatagramChannel.open();
|
||||
udpChannel.connect(new InetSocketAddress(host, udpPort));
|
||||
udpReceiveThread.start();
|
||||
|
||||
// initial handshake
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.writeUTF(uniqueID.toString());
|
||||
dos.writeInt(0);
|
||||
dos.flush();
|
||||
|
||||
ByteBuffer buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
|
||||
udpChannel.write(buffer);
|
||||
}
|
||||
|
||||
private void udpReceive() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(65536);
|
||||
int packetId = 0;
|
||||
Packet packet = null;
|
||||
while (udpChannel.isConnected()) {
|
||||
try {
|
||||
buffer.clear();
|
||||
udpChannel.receive(buffer);
|
||||
buffer.flip();
|
||||
|
||||
byte[] plaintext = UdpCrypto.decrypt(udpKey, buffer, null);
|
||||
|
||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(plaintext));
|
||||
UUID uuid = UUID.fromString(dis.readUTF());
|
||||
packetId = dis.readInt();
|
||||
if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) {
|
||||
eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.UDP));
|
||||
} else eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
|
||||
|
||||
} catch (Exception ignored) {
|
||||
eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet via TCP or UDP.
|
||||
*
|
||||
* @param packet packet to send
|
||||
* @param protocol transport protocol
|
||||
*/
|
||||
public void sendPacket(Packet packet, TransportProtocol protocol) throws Exception {
|
||||
if (protocol == TransportProtocol.TCP) {
|
||||
packetHandler.sendPacket(outputStream, packet, uniqueID);
|
||||
eventManager.executeEvent(new C_PacketSendEvent(this, packet, TransportProtocol.TCP));
|
||||
} else if (protocol == TransportProtocol.UDP && udpChannel != null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
packetHandler.sendPacket(dos, packet, uniqueID);
|
||||
dos.flush();
|
||||
|
||||
ByteBuffer buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
|
||||
udpChannel.write(buffer);
|
||||
eventManager.executeEvent(new C_PacketSendEvent(this, packet, TransportProtocol.UDP));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the client and releases resources.
|
||||
*/
|
||||
public void disconnect() {
|
||||
// Stop threads first
|
||||
tcpReceiveThread.interrupt();
|
||||
udpReceiveThread.interrupt();
|
||||
|
||||
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
|
||||
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
|
||||
|
||||
inputStream = null;
|
||||
outputStream = null;
|
||||
tcpSocket = null;
|
||||
udpChannel = null;
|
||||
|
||||
eventManager.executeEvent(new ClientDisconnectedEvent(this));
|
||||
|
||||
uniqueID = null;
|
||||
udpKey = null;
|
||||
udpPort = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link NetworkClient}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private PacketHandler packetHandler;
|
||||
private EventManager eventManager;
|
||||
private boolean sslEnabled = true;
|
||||
private KeyStore keyStore;
|
||||
private char[] keyPassword;
|
||||
private KeyStore trustStore;
|
||||
private long handshakeTimeoutMillis = 5000;
|
||||
|
||||
/**
|
||||
* Sets the packet handler (required).
|
||||
*
|
||||
* @param packetHandler packet handler
|
||||
* @return builder
|
||||
*/
|
||||
public Builder packetHandler(PacketHandler packetHandler) {
|
||||
this.packetHandler = packetHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom event manager.
|
||||
*
|
||||
* @param eventManager event manager
|
||||
* @return builder
|
||||
*/
|
||||
public Builder eventManager(EventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
* @return builder
|
||||
*/
|
||||
public Builder sslEnabled(boolean sslEnabled) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key store and its password.
|
||||
*
|
||||
* @param keyStore key store
|
||||
* @param keyPassword key store password
|
||||
* @return builder
|
||||
*/
|
||||
public Builder keyStore(KeyStore keyStore, char[] keyPassword) {
|
||||
this.keyStore = keyStore;
|
||||
this.keyPassword = keyPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the trust store (root CAs).
|
||||
*
|
||||
* @param trustStore trust store
|
||||
* @return builder
|
||||
*/
|
||||
public Builder trustStore(KeyStore trustStore) {
|
||||
this.trustStore = trustStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the handshake timeout in milliseconds.
|
||||
*
|
||||
* @param handshakeTimeoutMillis timeout in ms
|
||||
* @return builder
|
||||
*/
|
||||
public Builder handshakeTimeoutMillis(long handshakeTimeoutMillis) {
|
||||
this.handshakeTimeoutMillis = handshakeTimeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the client instance.
|
||||
*
|
||||
* @return network client
|
||||
*/
|
||||
public NetworkClient build() {
|
||||
if (packetHandler == null) {
|
||||
throw new IllegalStateException("PacketHandler is required");
|
||||
}
|
||||
|
||||
NetworkClient client = (eventManager == null)
|
||||
? new NetworkClient(packetHandler)
|
||||
: new NetworkClient(packetHandler, eventManager);
|
||||
|
||||
if (keyStore != null || trustStore != null) {
|
||||
client.configureSSL(sslEnabled, keyStore, keyPassword, trustStore);
|
||||
} else {
|
||||
client.configureSSL(sslEnabled);
|
||||
}
|
||||
client.setHandshakeTimeoutMillis(handshakeTimeoutMillis);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class C_PacketFailedReadEvent extends Event {
|
||||
|
||||
private final NetworkClient client;
|
||||
private final Packet packet;
|
||||
private final int packetId;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public C_PacketFailedReadEvent(NetworkClient client, Packet packet, int packetId, TransportProtocol protocol) {
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.packetId = packetId;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public NetworkClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class C_PacketReadEvent extends Event {
|
||||
private final NetworkClient client;
|
||||
private final Packet packet;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public C_PacketReadEvent(NetworkClient client, Packet packet, TransportProtocol protocol) {
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public NetworkClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class C_PacketSendEvent extends Event {
|
||||
|
||||
private final NetworkClient client;
|
||||
private final Packet packet;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public C_PacketSendEvent(NetworkClient client, Packet packet, TransportProtocol protocol) {
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public NetworkClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||
|
||||
public class ClientConnectedEvent extends Event {
|
||||
|
||||
private final NetworkClient client;
|
||||
|
||||
public ClientConnectedEvent(NetworkClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public NetworkClient getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||
|
||||
public class ClientDisconnectedEvent extends Event {
|
||||
|
||||
private final NetworkClient client;
|
||||
|
||||
public ClientDisconnectedEvent(NetworkClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public NetworkClient getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,13 @@ public class PacketHandler {
|
||||
* @param input DataInputStream to read from
|
||||
* @return Packet instance or null if failed
|
||||
*/
|
||||
public boolean readPacket(DataInputStream input, UUID clientID, int id) throws IOException {
|
||||
public Packet readPacket(DataInputStream input, UUID clientID, int id) throws IOException {
|
||||
Supplier<? extends Packet> factory = factories.get(id);
|
||||
if (factory == null) return false;
|
||||
if (factory == null) return null;
|
||||
|
||||
Packet packet = factory.get();
|
||||
packet.read(input, clientID);
|
||||
return true;
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,21 +6,24 @@
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketFailedReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketSendEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.*;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.NotYetBoundException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ConnectedClient {
|
||||
|
||||
@@ -30,33 +33,36 @@ public class ConnectedClient {
|
||||
private DataInputStream inputStream;
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
private DatagramChannel udpChannel; // für serverseitiges Senden
|
||||
private DatagramChannel udpChannel;
|
||||
private final Thread tcpReceiveThread;
|
||||
private final NetworkServer server;
|
||||
|
||||
private int udpPort = -1;
|
||||
|
||||
public Socket getTcpSocket() {
|
||||
return tcpSocket;
|
||||
}
|
||||
private SecretKey udpKey;
|
||||
|
||||
public ConnectedClient(NetworkServer server, Socket tcpSocket, UUID uniqueID, int udpPort) throws IOException {
|
||||
this.tcpSocket = tcpSocket;
|
||||
this.uniqueID = uniqueID;
|
||||
this.udpPort = udpPort;
|
||||
this.server = server;
|
||||
this.tcpReceiveThread = new Thread(this::tcpReceive);
|
||||
|
||||
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
|
||||
inputStream = new DataInputStream(tcpSocket.getInputStream());
|
||||
|
||||
this.udpKey = UdpCrypto.generateKey();
|
||||
|
||||
tcpReceiveThread = new Thread(this::tcpReceive);
|
||||
tcpReceiveThread.start();
|
||||
|
||||
server.getConnectedClients().add(this);
|
||||
|
||||
// Sende UUID & UDP-Port an Client
|
||||
outputStream.writeUTF(uniqueID.toString());
|
||||
outputStream.writeInt(-1);
|
||||
outputStream.writeInt(-1); // Handshake-Packet
|
||||
outputStream.writeInt(udpPort);
|
||||
byte[] keyBytes = udpKey.getEncoded();
|
||||
outputStream.writeInt(keyBytes.length);
|
||||
outputStream.write(keyBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
@@ -65,20 +71,17 @@ public class ConnectedClient {
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet, TransportProtocol protocol) throws IOException {
|
||||
if (protocol == TransportProtocol.UDP) {
|
||||
sendPacketUDP(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (protocol == TransportProtocol.TCP) {
|
||||
sendPacketTCP(packet);
|
||||
return;
|
||||
} else if (protocol == TransportProtocol.UDP) {
|
||||
sendPacketUDP(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacketTCP(Packet packet) throws IOException {
|
||||
if (!isTCPConnected()) return;
|
||||
server.getPacketHandler().sendPacket(outputStream, packet, uniqueID);
|
||||
server.getEventManager().executeEvent(new S_PacketSendEvent(server, this, packet, TransportProtocol.TCP));
|
||||
}
|
||||
|
||||
private void sendPacketUDP(Packet packet) throws IOException {
|
||||
@@ -89,28 +92,30 @@ public class ConnectedClient {
|
||||
server.getPacketHandler().sendPacket(dos, packet, uniqueID);
|
||||
dos.flush();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
|
||||
|
||||
// Hole die gespeicherte UDP-Adresse des Clients
|
||||
SocketAddress clientAddress = server.getClientUdpAddress(uniqueID);
|
||||
if (clientAddress == null) {
|
||||
return;
|
||||
ByteBuffer buffer;
|
||||
try {
|
||||
buffer = UdpCrypto.encrypt(udpKey, baos.toByteArray(), null);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to encrypt UDP packet", e);
|
||||
}
|
||||
|
||||
udpChannel.send(buffer, clientAddress); // korrekt an Client senden
|
||||
SocketAddress clientAddress = server.getClientUdpAddress(uniqueID);
|
||||
if (clientAddress != null && udpChannel != null && udpChannel.isOpen()) {
|
||||
udpChannel.send(buffer, clientAddress);
|
||||
server.getEventManager().executeEvent(new S_PacketSendEvent(server, this, packet, TransportProtocol.UDP));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void tcpReceive() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && tcpSocket.isBound()) {
|
||||
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && !tcpSocket.isClosed()) {
|
||||
UUID uuid = UUID.fromString(inputStream.readUTF());
|
||||
int packetId = inputStream.readInt();
|
||||
|
||||
if (!server.getPacketHandler().readPacket(inputStream, uuid, packetId)) {
|
||||
// TODO: UnknownPacketReceivedEvent
|
||||
}
|
||||
Packet packet = null;
|
||||
if ((packet = server.getPacketHandler().readPacket(inputStream, uuid, packetId)) != null)
|
||||
server.getEventManager().executeEvent(new S_PacketReadEvent(server, this, packet, TransportProtocol.TCP));
|
||||
else
|
||||
server.getEventManager().executeEvent(new S_PacketFailedReadEvent(server, this, packet, packetId, TransportProtocol.TCP));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
disconnect();
|
||||
@@ -123,11 +128,15 @@ public class ConnectedClient {
|
||||
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
|
||||
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
|
||||
|
||||
tcpSocket = null;
|
||||
udpChannel = null;
|
||||
udpPort = -1;
|
||||
inputStream = null;
|
||||
outputStream = null;
|
||||
udpPort = -1;
|
||||
|
||||
server.getEventManager().executeEvent(new S_ClientDisconnectedEvent(server, this));
|
||||
uniqueID = null;
|
||||
|
||||
server.getConnectedClients().remove(this);
|
||||
@@ -152,4 +161,12 @@ public class ConnectedClient {
|
||||
public int getUdpPort() {
|
||||
return udpPort;
|
||||
}
|
||||
|
||||
public Socket getTcpSocket() {
|
||||
return tcpSocket;
|
||||
}
|
||||
|
||||
public SecretKey getUdpKey() {
|
||||
return udpKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientConnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketFailedReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStartedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.security.KeyStore;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* TCP/UDP server with optional TLS and UDP encryption.
|
||||
*/
|
||||
public class NetworkServer {
|
||||
private ServerSocket tcpSocket;
|
||||
private Thread tcpThread;
|
||||
|
||||
private DatagramChannel udpChannel;
|
||||
private Thread udpThread;
|
||||
|
||||
private final Map<SocketAddress, UUID> clientUdpAddresses = new ConcurrentHashMap<>();
|
||||
private final PacketHandler packetHandler;
|
||||
private final EventManager eventManager;
|
||||
private final List<ConnectedClient> connectedClients;
|
||||
|
||||
private int udpPort = -1;
|
||||
|
||||
/* === TLS CONFIG === */
|
||||
private boolean sslEnabled = true;
|
||||
private ClientAuthMode clientAuthMode = ClientAuthMode.NONE;
|
||||
private KeyStore keyStore;
|
||||
private char[] keyPassword;
|
||||
private KeyStore trustStore;
|
||||
|
||||
/**
|
||||
* Creates a server with a custom {@link EventManager}.
|
||||
*
|
||||
* @param packetHandler packet handler
|
||||
* @param eventManager event manager
|
||||
*/
|
||||
private NetworkServer(PacketHandler packetHandler, EventManager eventManager) {
|
||||
this.packetHandler = packetHandler;
|
||||
this.eventManager = eventManager;
|
||||
this.connectedClients = Collections.synchronizedList(new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS and sets the client authentication mode.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
* @param clientAuthMode client authentication mode
|
||||
*/
|
||||
public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
this.clientAuthMode = clientAuthMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS with explicit key and trust stores.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
* @param clientAuthMode client authentication mode
|
||||
* @param keyStore server key store (required when TLS is enabled)
|
||||
* @param keyPassword key store password
|
||||
* @param trustStore trust store (required for client auth)
|
||||
*/
|
||||
public void configureSSL(boolean sslEnabled,
|
||||
ClientAuthMode clientAuthMode,
|
||||
KeyStore keyStore,
|
||||
char[] keyPassword,
|
||||
KeyStore trustStore) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
this.clientAuthMode = clientAuthMode;
|
||||
this.keyStore = keyStore;
|
||||
this.keyPassword = keyPassword;
|
||||
this.trustStore = trustStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*
|
||||
* @param tcpPort TCP port
|
||||
* @param udpPort UDP port (-1 to disable UDP)
|
||||
*/
|
||||
public void start(int tcpPort, int udpPort) throws IOException, InterruptedException {
|
||||
this.udpPort = udpPort;
|
||||
|
||||
if (sslEnabled) {
|
||||
if (keyStore == null) {
|
||||
throw new IllegalStateException("SSL enabled but no server KeyStore configured");
|
||||
}
|
||||
if (clientAuthMode != ClientAuthMode.NONE && trustStore == null) {
|
||||
throw new IllegalStateException("Client auth enabled but no TrustStore configured");
|
||||
}
|
||||
|
||||
SSLContext sslContext = SSLContexts.create(keyStore, keyPassword, trustStore);
|
||||
SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
|
||||
|
||||
tcpSocket = factory.createServerSocket(tcpPort);
|
||||
SSLServerSocket sslServerSocket = (SSLServerSocket) tcpSocket;
|
||||
|
||||
switch (clientAuthMode) {
|
||||
case REQUIRED -> sslServerSocket.setNeedClientAuth(true);
|
||||
case OPTIONAL -> sslServerSocket.setWantClientAuth(true);
|
||||
case NONE -> {
|
||||
sslServerSocket.setNeedClientAuth(false);
|
||||
sslServerSocket.setWantClientAuth(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tcpSocket = new ServerSocket(tcpPort);
|
||||
}
|
||||
|
||||
tcpThread = new Thread(this::tcpAcceptLoop);
|
||||
tcpThread.start();
|
||||
|
||||
if (isUDPEnabled()) {
|
||||
udpChannel = DatagramChannel.open();
|
||||
udpChannel.bind(new InetSocketAddress(udpPort));
|
||||
udpThread = new Thread(this::udpReceiveLoop);
|
||||
udpThread.start();
|
||||
}
|
||||
|
||||
Thread.sleep(10);
|
||||
eventManager.executeEvent(new ServerStartedEvent(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the server and disconnects all clients.
|
||||
*/
|
||||
public void stop() {
|
||||
if (tcpThread != null) tcpThread.interrupt();
|
||||
if (udpThread != null) udpThread.interrupt();
|
||||
|
||||
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
|
||||
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
|
||||
|
||||
List<ConnectedClient> snapshot;
|
||||
synchronized (connectedClients) {
|
||||
snapshot = new ArrayList<>(connectedClients);
|
||||
}
|
||||
for (ConnectedClient client : snapshot) {
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
tcpSocket = null;
|
||||
udpChannel = null;
|
||||
tcpThread = null;
|
||||
udpThread = null;
|
||||
udpPort = -1;
|
||||
|
||||
eventManager.executeEvent(new ServerStoppedEvent(this));
|
||||
}
|
||||
|
||||
private void tcpAcceptLoop() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = tcpSocket.accept();
|
||||
if (socket instanceof SSLSocket sslSocket) {
|
||||
sslSocket.startHandshake();
|
||||
}
|
||||
|
||||
ConnectedClient client = new ConnectedClient(this, socket, UUID.randomUUID(), udpPort);
|
||||
|
||||
if (isUDPEnabled()) client.setUdpChannel(udpChannel);
|
||||
Thread.sleep(100);
|
||||
|
||||
eventManager.executeEvent(new S_ClientConnectedEvent(this, client));
|
||||
} catch (IOException | InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void udpReceiveLoop() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(65536);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
buffer.clear();
|
||||
SocketAddress sender = udpChannel.receive(buffer);
|
||||
if (sender == null) continue;
|
||||
buffer.flip();
|
||||
if (buffer.remaining() < 13) continue; // 12-byte IV + at least 1 byte payload
|
||||
|
||||
UUID mappedUuid = clientUdpAddresses.get(sender);
|
||||
if (mappedUuid != null) {
|
||||
ConnectedClient mappedClient = getClientByUuid(mappedUuid);
|
||||
if (mappedClient != null) {
|
||||
if (tryHandleUdpPacket(mappedClient, sender, buffer.asReadOnlyBuffer())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try all clients if mapping missing or decrypt failed (e.g., NAT rebinding)
|
||||
List<ConnectedClient> snapshot;
|
||||
synchronized (connectedClients) {
|
||||
snapshot = new ArrayList<>(connectedClients);
|
||||
}
|
||||
for (ConnectedClient client : snapshot) {
|
||||
if (tryHandleUdpPacket(client, sender, buffer.asReadOnlyBuffer())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (tcpSocket != null) {
|
||||
try { tcpSocket.close(); } catch (IOException ignored) {}
|
||||
}
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if UDP is enabled
|
||||
*/
|
||||
public boolean isUDPEnabled() {
|
||||
return udpPort != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return packet handler
|
||||
*/
|
||||
public PacketHandler getPacketHandler() {
|
||||
return packetHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return connected clients list (synchronized)
|
||||
*/
|
||||
public List<ConnectedClient> getConnectedClients() {
|
||||
return connectedClients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UDP socket address for a client UUID.
|
||||
*
|
||||
* @param uuid client UUID
|
||||
* @return socket address or null
|
||||
*/
|
||||
public SocketAddress getClientUdpAddress(UUID uuid) {
|
||||
AtomicReference<SocketAddress> socket = new AtomicReference<>();
|
||||
clientUdpAddresses.forEach((socketAddress, client) -> {
|
||||
if (client.equals(uuid)) socket.set(socketAddress);
|
||||
});
|
||||
|
||||
return socket.get();
|
||||
}
|
||||
|
||||
private ConnectedClient getClientByUuid(UUID uuid) {
|
||||
synchronized (connectedClients) {
|
||||
for (ConnectedClient client : connectedClients) {
|
||||
if (uuid.equals(client.getUniqueID())) return client;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean tryHandleUdpPacket(ConnectedClient client, SocketAddress sender, ByteBuffer rawBuffer) {
|
||||
int packetId = 0;
|
||||
Packet packet = null;
|
||||
try {
|
||||
rawBuffer.rewind();
|
||||
byte[] plaintext = UdpCrypto.decrypt(client.getUdpKey(), rawBuffer, null);
|
||||
|
||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(plaintext));
|
||||
UUID uuid = UUID.fromString(dis.readUTF());
|
||||
packetId = dis.readInt();
|
||||
|
||||
if (!uuid.equals(client.getUniqueID())) return false;
|
||||
|
||||
if (packetId == 0) {
|
||||
clientUdpAddresses.put(sender, uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
clientUdpAddresses.put(sender, uuid);
|
||||
if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) {
|
||||
eventManager.executeEvent(new S_PacketReadEvent(this, client, packet, TransportProtocol.UDP));
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
|
||||
eventManager.executeEvent(new S_PacketFailedReadEvent(this, client, packet, packetId, TransportProtocol.UDP));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return event manager
|
||||
*/
|
||||
public EventManager getEventManager() {
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link NetworkServer}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private PacketHandler packetHandler;
|
||||
private EventManager eventManager;
|
||||
private boolean sslEnabled = true;
|
||||
private ClientAuthMode clientAuthMode = ClientAuthMode.NONE;
|
||||
private KeyStore keyStore;
|
||||
private char[] keyPassword;
|
||||
private KeyStore trustStore;
|
||||
|
||||
/**
|
||||
* Sets the packet handler (required).
|
||||
*
|
||||
* @param packetHandler packet handler
|
||||
* @return builder
|
||||
*/
|
||||
public Builder packetHandler(PacketHandler packetHandler) {
|
||||
this.packetHandler = packetHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom event manager.
|
||||
*
|
||||
* @param eventManager event manager
|
||||
* @return builder
|
||||
*/
|
||||
public Builder eventManager(EventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables TLS.
|
||||
*
|
||||
* @param sslEnabled enable TLS
|
||||
* @return builder
|
||||
*/
|
||||
public Builder sslEnabled(boolean sslEnabled) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client authentication mode.
|
||||
*
|
||||
* @param clientAuthMode client authentication mode
|
||||
* @return builder
|
||||
*/
|
||||
public Builder clientAuthMode(ClientAuthMode clientAuthMode) {
|
||||
this.clientAuthMode = clientAuthMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key store and its password.
|
||||
*
|
||||
* @param keyStore key store
|
||||
* @param keyPassword key store password
|
||||
* @return builder
|
||||
*/
|
||||
public Builder keyStore(KeyStore keyStore, char[] keyPassword) {
|
||||
this.keyStore = keyStore;
|
||||
this.keyPassword = keyPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the trust store (root CAs).
|
||||
*
|
||||
* @param trustStore trust store
|
||||
* @return builder
|
||||
*/
|
||||
public Builder trustStore(KeyStore trustStore) {
|
||||
this.trustStore = trustStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the server instance.
|
||||
*
|
||||
* @return network server
|
||||
*/
|
||||
public NetworkServer build() {
|
||||
if (packetHandler == null) {
|
||||
throw new IllegalStateException("PacketHandler is required");
|
||||
}
|
||||
|
||||
if (eventManager == null) {
|
||||
throw new IllegalStateException("EventManager is required");
|
||||
}
|
||||
|
||||
NetworkServer server = new NetworkServer(packetHandler, eventManager);
|
||||
|
||||
if (keyStore != null || trustStore != null || clientAuthMode != ClientAuthMode.NONE) {
|
||||
server.configureSSL(sslEnabled, clientAuthMode, keyStore, keyPassword, trustStore);
|
||||
} else {
|
||||
server.configureSSL(sslEnabled, clientAuthMode);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
|
||||
public class S_ClientConnectedEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
private final ConnectedClient client;
|
||||
|
||||
public S_ClientConnectedEvent(NetworkServer server, ConnectedClient client) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public ConnectedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
|
||||
public class S_ClientDisconnectedEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
private final ConnectedClient client;
|
||||
|
||||
public S_ClientDisconnectedEvent(NetworkServer server, ConnectedClient client) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public ConnectedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class S_PacketFailedReadEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
private final ConnectedClient client;
|
||||
private final Packet packet;
|
||||
private final int packetId;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public S_PacketFailedReadEvent(NetworkServer server, ConnectedClient client, Packet packet, int packetId, TransportProtocol protocol) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.packetId = packetId;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public ConnectedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class S_PacketReadEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
private final ConnectedClient client;
|
||||
private final Packet packet;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public S_PacketReadEvent(NetworkServer server, ConnectedClient client, Packet packet, TransportProtocol protocol) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public ConnectedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
public class S_PacketSendEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
private final ConnectedClient client;
|
||||
private final Packet packet;
|
||||
private final TransportProtocol protocol;
|
||||
|
||||
public S_PacketSendEvent(NetworkServer server, ConnectedClient client, Packet packet, TransportProtocol protocol) {
|
||||
this.server = server;
|
||||
this.client = client;
|
||||
this.packet = packet;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public ConnectedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public TransportProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
|
||||
public class ServerStartedEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
|
||||
public ServerStartedEvent(NetworkServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||
|
||||
public class ServerStoppedEvent extends Event {
|
||||
|
||||
private final NetworkServer server;
|
||||
|
||||
public ServerStoppedEvent(NetworkServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public NetworkServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PortUnreachableException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.util.UUID;
|
||||
|
||||
public class NetworkClient {
|
||||
|
||||
private Socket tcpSocket;
|
||||
private final Thread tcpReceiveThread;
|
||||
|
||||
private DatagramChannel udpChannel;
|
||||
private final Thread udpReceiveThread;
|
||||
|
||||
private DataInputStream inputStream;
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
private volatile UUID uniqueID;
|
||||
private final PacketHandler packetHandler;
|
||||
|
||||
private int tcpPort = -1;
|
||||
private String host;
|
||||
private int udpPort = -1;
|
||||
|
||||
public NetworkClient(PacketHandler packetHandler) {
|
||||
this.packetHandler = packetHandler;
|
||||
this.tcpReceiveThread = new Thread(this::tcpReceive);
|
||||
this.udpReceiveThread = new Thread(this::udpReceive);
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet, TransportProtocol protocol) throws IOException {
|
||||
if (protocol == TransportProtocol.TCP) {
|
||||
if (!isTCPConnected()) return;
|
||||
packetHandler.sendPacket(outputStream, packet, uniqueID);
|
||||
}
|
||||
|
||||
if (protocol == TransportProtocol.UDP) {
|
||||
if (!isUDPConnected()) return;
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream oos = new DataOutputStream(baos)) {
|
||||
packetHandler.sendPacket(oos, packet, uniqueID);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
|
||||
udpChannel.write(buffer);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the client to a server with optional UDP.
|
||||
*
|
||||
* @param host Hostname or IP (IPv4/IPv6)
|
||||
* @param tcpPort TCP port
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void connect(String host, int tcpPort) throws IOException, InterruptedException {
|
||||
this.host = host;
|
||||
this.tcpPort = tcpPort;
|
||||
|
||||
tcpSocket = new Socket(host, tcpPort);
|
||||
outputStream = new DataOutputStream(tcpSocket.getOutputStream());
|
||||
inputStream = new DataInputStream(tcpSocket.getInputStream());
|
||||
tcpReceiveThread.start();
|
||||
|
||||
while (uniqueID == null) Thread.sleep(10);
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
private void tcpReceive() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && tcpSocket.isBound()) {
|
||||
UUID uuid = UUID.fromString(inputStream.readUTF());
|
||||
int packetId = inputStream.readInt();
|
||||
|
||||
if (!packetHandler.readPacket(inputStream, uuid, packetId)) {
|
||||
if (packetId == -1) {
|
||||
uniqueID = uuid;
|
||||
this.udpPort = inputStream.readInt();
|
||||
if (isUDPEnabled()) {
|
||||
connectUDP();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void udpReceive() {
|
||||
if (!isUDPConnected()) return;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(65536);
|
||||
|
||||
while (!Thread.currentThread().isInterrupted() && isUDPConnected()) {
|
||||
try {
|
||||
buffer.clear();
|
||||
SocketAddress sender = udpChannel.receive(buffer);
|
||||
if (sender == null) continue;
|
||||
|
||||
buffer.flip();
|
||||
handleUdpPacket(buffer);
|
||||
|
||||
} catch (PortUnreachableException | AsynchronousCloseException ignored) {}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUdpPacket(ByteBuffer buffer) {
|
||||
try (DataInputStream ois = new DataInputStream(
|
||||
new ByteArrayInputStream(buffer.array(), 0, buffer.limit()))) {
|
||||
|
||||
UUID uuid = UUID.fromString(ois.readUTF());
|
||||
int packetId = ois.readInt();
|
||||
if (!packetHandler.readPacket(ois, uuid, packetId)) {
|
||||
// TODO: UnknownPacketReceivedEvent
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void connectUDP() throws IOException, InterruptedException {
|
||||
if (!isUDPEnabled()) return;
|
||||
if (udpReceiveThread.isAlive()) return;
|
||||
|
||||
udpChannel = DatagramChannel.open();
|
||||
udpChannel.socket().setReuseAddress(true);
|
||||
udpChannel.configureBlocking(true);
|
||||
udpChannel.connect(new InetSocketAddress(host, udpPort));
|
||||
udpReceiveThread.start();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{0});
|
||||
udpChannel.write(buffer);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
tcpReceiveThread.interrupt();
|
||||
|
||||
try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {}
|
||||
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
|
||||
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
|
||||
|
||||
tcpSocket = null;
|
||||
udpChannel = null;
|
||||
inputStream = null;
|
||||
outputStream = null;
|
||||
|
||||
host = null;
|
||||
tcpPort = -1;
|
||||
udpPort = -1;
|
||||
uniqueID = null;
|
||||
}
|
||||
|
||||
public boolean isTCPConnected() {
|
||||
return uniqueID != null && tcpSocket != null && tcpSocket.isConnected() && !tcpSocket.isClosed();
|
||||
}
|
||||
|
||||
public boolean isUDPEnabled() {
|
||||
return udpPort != -1;
|
||||
}
|
||||
|
||||
public boolean isUDPConnected() {
|
||||
return isUDPEnabled() && isTCPConnected() && udpChannel != null && udpChannel.isConnected();
|
||||
}
|
||||
|
||||
public UUID getUniqueID() {
|
||||
return uniqueID;
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||
*
|
||||
* You are unauthorized to remove this copyright.
|
||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||
* See LICENSE-File if exists
|
||||
*/
|
||||
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.tcp;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class NetworkServer {
|
||||
|
||||
// Events-Handler (optional)
|
||||
public abstract static class EventsServer {
|
||||
public abstract void onConnect(ConnectedClient client);
|
||||
}
|
||||
|
||||
public EventsServer events;
|
||||
|
||||
private ServerSocket tcpSocket;
|
||||
private Thread tcpThread;
|
||||
|
||||
private DatagramChannel udpChannel;
|
||||
private Thread udpThread;
|
||||
private final Map<UUID, SocketAddress> clientUdpAddresses = new ConcurrentHashMap<>();
|
||||
|
||||
private final PacketHandler packetHandler;
|
||||
private final List<ConnectedClient> connectedClients;
|
||||
|
||||
private int udpPort = -1;
|
||||
|
||||
public NetworkServer(PacketHandler packetHandler) {
|
||||
this.packetHandler = packetHandler;
|
||||
this.connectedClients = Collections.synchronizedList(new ArrayList<>());
|
||||
}
|
||||
|
||||
public void start(int tcpPort, int udpPort) throws IOException {
|
||||
this.udpPort = udpPort;
|
||||
|
||||
// TCP starten
|
||||
tcpSocket = new ServerSocket();
|
||||
tcpSocket.bind(new InetSocketAddress(tcpPort));
|
||||
|
||||
tcpThread = new Thread(this::tcpAcceptLoop);
|
||||
tcpThread.start();
|
||||
|
||||
// UDP starten, falls aktiviert
|
||||
if (isUDPEnabled()) {
|
||||
udpChannel = DatagramChannel.open();
|
||||
udpChannel.socket().setReuseAddress(true);
|
||||
udpChannel.configureBlocking(true);
|
||||
udpChannel.bind(new InetSocketAddress(udpPort));
|
||||
udpThread = new Thread(this::udpReceiveLoop);
|
||||
udpThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void tcpAcceptLoop() {
|
||||
while (!Thread.currentThread().isInterrupted() && tcpSocket.isBound()) {
|
||||
try {
|
||||
Socket clientSocket = tcpSocket.accept();
|
||||
ConnectedClient client = new ConnectedClient(this, clientSocket, UUID.randomUUID(), udpPort);
|
||||
|
||||
if (isUDPEnabled()) {
|
||||
client.setUdpChannel(udpChannel);
|
||||
}
|
||||
Thread.sleep(100);
|
||||
|
||||
if (events != null) events.onConnect(client);
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void udpReceiveLoop() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(65536);
|
||||
|
||||
while (!Thread.currentThread().isInterrupted() && isUDPEnabled()) {
|
||||
try {
|
||||
buffer.clear();
|
||||
SocketAddress sender = udpChannel.receive(buffer);
|
||||
if (sender == null) continue;
|
||||
|
||||
buffer.flip();
|
||||
|
||||
if (buffer.remaining() == 1 && buffer.get(0) == 0) {
|
||||
// Handshake: wir kennen die UUID über TCP
|
||||
// Suche den ConnectedClient mit passender TCP-Adresse
|
||||
Optional<ConnectedClient> clientOpt = connectedClients.stream()
|
||||
.filter(c -> c.getTcpSocket().getInetAddress().equals(((InetSocketAddress) sender).getAddress()))
|
||||
.findFirst();
|
||||
|
||||
if (clientOpt.isPresent()) {
|
||||
ConnectedClient client = clientOpt.get();
|
||||
clientUdpAddresses.put(client.getUniqueID(), sender);
|
||||
}
|
||||
|
||||
continue; // kein normales Packet parsen
|
||||
}
|
||||
|
||||
DataInputStream dis = new DataInputStream(
|
||||
new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining())
|
||||
);
|
||||
|
||||
UUID uuid = UUID.fromString(dis.readUTF());
|
||||
int packetId = dis.readInt();
|
||||
|
||||
// Speichere die UDP-Adresse des Clients
|
||||
|
||||
clientUdpAddresses.put(uuid, sender);
|
||||
|
||||
for (ConnectedClient connectedClient : connectedClients) {
|
||||
if (connectedClient.getUniqueID().equals(uuid)) {
|
||||
packetHandler.readPacket(dis, uuid, packetId);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void stop() {
|
||||
if (tcpThread != null) tcpThread.interrupt();
|
||||
if (udpThread != null) udpThread.interrupt();
|
||||
|
||||
new ArrayList<>(connectedClients).forEach(ConnectedClient::disconnect);
|
||||
|
||||
try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {}
|
||||
try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {}
|
||||
|
||||
tcpSocket = null;
|
||||
udpChannel = null;
|
||||
udpPort = -1;
|
||||
}
|
||||
|
||||
public boolean isTCPOnline() {
|
||||
return tcpSocket != null && tcpSocket.isBound();
|
||||
}
|
||||
|
||||
public boolean isUDPEnabled() {
|
||||
return udpPort != -1;
|
||||
}
|
||||
|
||||
public boolean isUDPConnected() {
|
||||
return isUDPEnabled() && isTCPOnline() && udpChannel != null && udpChannel.isOpen();
|
||||
}
|
||||
|
||||
public PacketHandler getPacketHandler() {
|
||||
return packetHandler;
|
||||
}
|
||||
|
||||
public List<ConnectedClient> getConnectedClients() {
|
||||
return connectedClients;
|
||||
}
|
||||
|
||||
public SocketAddress getClientUdpAddress(UUID uuid) {
|
||||
return clientUdpAddresses.get(uuid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
||||
|
||||
/**
|
||||
* Defines client certificate requirements for TLS.
|
||||
*/
|
||||
public enum ClientAuthMode {
|
||||
NONE,
|
||||
OPTIONAL,
|
||||
REQUIRED
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Utility class for creating SSLContexts.
|
||||
*/
|
||||
public final class SSLContexts {
|
||||
|
||||
private SSLContexts() {}
|
||||
|
||||
/**
|
||||
* Creates an SSLContext.
|
||||
*
|
||||
* @param trustAll If true, disables certificate validation (SSL still active)
|
||||
* @param keyStore Optional keystore for client authentication
|
||||
* @param keyPassword Keystore password
|
||||
*/
|
||||
public static SSLContext create(boolean trustAll, KeyStore keyStore, char[] keyPassword) {
|
||||
try {
|
||||
TrustManager[] trustManagers = trustAll
|
||||
? new TrustManager[]{new TrustAllX509()}
|
||||
: null;
|
||||
|
||||
KeyManager[] keyManagers = null;
|
||||
if (keyStore != null) {
|
||||
KeyManagerFactory kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, keyPassword);
|
||||
keyManagers = kmf.getKeyManagers();
|
||||
}
|
||||
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(keyManagers, trustManagers, new SecureRandom());
|
||||
return ctx;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to create SSLContext", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SSLContext using explicit key and trust stores.
|
||||
*
|
||||
* @param keyStore Optional keystore for client/server authentication
|
||||
* @param keyPassword Keystore password
|
||||
* @param trustStore Optional truststore for certificate validation (root CAs)
|
||||
*/
|
||||
public static SSLContext create(KeyStore keyStore, char[] keyPassword, KeyStore trustStore) {
|
||||
try {
|
||||
KeyManager[] keyManagers = null;
|
||||
if (keyStore != null) {
|
||||
KeyManagerFactory kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, keyPassword);
|
||||
keyManagers = kmf.getKeyManagers();
|
||||
}
|
||||
|
||||
TrustManager[] trustManagers = null;
|
||||
if (trustStore != null) {
|
||||
TrustManagerFactory tmf =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(trustStore);
|
||||
trustManagers = tmf.getTrustManagers();
|
||||
}
|
||||
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(keyManagers, trustManagers, new SecureRandom());
|
||||
return ctx;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to create SSLContext", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TrustManager that accepts all certificates.
|
||||
*/
|
||||
private static final class TrustAllX509 implements X509TrustManager {
|
||||
@Override public void checkClientTrusted(X509Certificate[] c, String a) {}
|
||||
@Override public void checkServerTrusted(X509Certificate[] c, String a) {}
|
||||
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Helper class for encrypting/decrypting UDP packets.
|
||||
*/
|
||||
public final class UdpCrypto {
|
||||
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
private static final int GCM_TAG_BITS = 128;
|
||||
|
||||
private UdpCrypto() {}
|
||||
|
||||
/**
|
||||
* Generates a random AES-256 key for UDP.
|
||||
*/
|
||||
public static SecretKey generateKey() {
|
||||
byte[] key = new byte[32];
|
||||
RNG.nextBytes(key);
|
||||
return new SecretKeySpec(key, "AES");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key from raw bytes.
|
||||
*/
|
||||
public static SecretKey fromBytes(byte[] keyBytes) {
|
||||
return new SecretKeySpec(keyBytes, "AES");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a UDP payload with AES-GCM.
|
||||
*
|
||||
* @param key AES key
|
||||
* @param plaintext payload
|
||||
* @param aad additional authenticated data (e.g., UUID + packetId)
|
||||
* @return ByteBuffer ready to send
|
||||
*/
|
||||
public static ByteBuffer encrypt(SecretKey key, byte[] plaintext, byte[] aad) throws Exception {
|
||||
byte[] iv = new byte[12];
|
||||
RNG.nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_BITS, iv));
|
||||
if (aad != null) cipher.updateAAD(aad);
|
||||
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(12 + ciphertext.length);
|
||||
buffer.put(iv).put(ciphertext);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a UDP payload with AES-GCM.
|
||||
*
|
||||
* @param key AES key
|
||||
* @param in ByteBuffer received
|
||||
* @param aad additional authenticated data (must match encryption)
|
||||
* @return plaintext
|
||||
*/
|
||||
public static byte[] decrypt(SecretKey key, ByteBuffer in, byte[] aad) throws Exception {
|
||||
byte[] iv = new byte[12];
|
||||
in.get(iv);
|
||||
byte[] ct = new byte[in.remaining()];
|
||||
in.get(ct);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_BITS, iv));
|
||||
if (aad != null) cipher.updateAAD(aad);
|
||||
|
||||
return cipher.doFinal(ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user