Rewritten NetworkSystem
This commit is contained in:
@@ -1,723 +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.client;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive.C_PacketReceivedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive.C_PacketReceivedFailedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send.C_PacketSendEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send.C_PacketSendFailedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect.ClientConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect.ClientFullyConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect.ClientDisconnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect.ClientFullyDisconnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketCodec;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ConnectionIdPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ProtocolRequirementsPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindAckPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpHelloPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.DtlsEndpoint;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.Endpoint;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secure client supporting TCP/TLS and UDP/DTLS (independently).
|
|
||||||
*
|
|
||||||
* <p>Connectivity rules:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #isConnected()} becomes {@code true} only after:
|
|
||||||
* <ul>
|
|
||||||
* <li>the server-assigned {@link #connectionId()} is present,</li>
|
|
||||||
* <li>the server-required protocol set (via {@link ProtocolRequirementsPacket}) was received, and</li>
|
|
||||||
* <li>all server-required protocols are connected/ready.</li>
|
|
||||||
* </ul>
|
|
||||||
* </li>
|
|
||||||
* <li>UDP is considered ready only after:
|
|
||||||
* <ul>
|
|
||||||
* <li>DTLS handshake is complete, and</li>
|
|
||||||
* <li>server acknowledged bind via {@link UdpBindAckPacket}, or UDP-only bootstrap assigned the id.</li>
|
|
||||||
* </ul>
|
|
||||||
* </li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class NetworkClient {
|
|
||||||
|
|
||||||
private final Endpoint tcpEndpoint;
|
|
||||||
private final Endpoint udpEndpoint;
|
|
||||||
private final EnumSet<NetworkProtocol> enabledProtocols;
|
|
||||||
|
|
||||||
private final PacketCodec codec;
|
|
||||||
private final SSLSocketFactory tlsSocketFactory;
|
|
||||||
private final SSLParameters tlsParameters; // optional
|
|
||||||
private final SSLContext dtlsContext;
|
|
||||||
|
|
||||||
private final int timeoutMillis;
|
|
||||||
private final int mtu;
|
|
||||||
|
|
||||||
private final EventManager eventManager;
|
|
||||||
|
|
||||||
private final AtomicBoolean lifecycleUp = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private volatile boolean tcpConnected;
|
|
||||||
private volatile boolean udpConnected; // DTLS handshake ok
|
|
||||||
private volatile boolean udpBound; // server acknowledged bind (or UDP-only hello assigned)
|
|
||||||
private volatile boolean wasFullyConnected;
|
|
||||||
|
|
||||||
private volatile SSLSocket tcpSocket;
|
|
||||||
private volatile ObjectOutputStream tcpOut;
|
|
||||||
private volatile ObjectInputStream tcpIn;
|
|
||||||
private volatile Thread tcpThread;
|
|
||||||
|
|
||||||
private volatile DatagramChannel udpChannel;
|
|
||||||
private volatile DtlsEndpoint dtlsEndpoint;
|
|
||||||
private volatile InetSocketAddress udpRemote;
|
|
||||||
private volatile Thread udpThread;
|
|
||||||
|
|
||||||
private volatile UUID connectionId;
|
|
||||||
|
|
||||||
private volatile EnumSet<NetworkProtocol> serverRequiredProtocols; // received from server
|
|
||||||
|
|
||||||
private NetworkClient(
|
|
||||||
Endpoint tcpEndpoint,
|
|
||||||
Endpoint udpEndpoint,
|
|
||||||
EnumSet<NetworkProtocol> enabledProtocols,
|
|
||||||
PacketCodec codec,
|
|
||||||
SSLSocketFactory tlsSocketFactory,
|
|
||||||
SSLParameters tlsParameters,
|
|
||||||
SSLContext dtlsContext,
|
|
||||||
int timeoutMillis,
|
|
||||||
int mtu,
|
|
||||||
EventManager eventManager
|
|
||||||
) {
|
|
||||||
this.tcpEndpoint = Objects.requireNonNull(tcpEndpoint, "tcpEndpoint");
|
|
||||||
this.udpEndpoint = Objects.requireNonNull(udpEndpoint, "udpEndpoint");
|
|
||||||
this.enabledProtocols = EnumSet.copyOf(Objects.requireNonNull(enabledProtocols, "enabledProtocols"));
|
|
||||||
|
|
||||||
this.codec = Objects.requireNonNull(codec, "codec");
|
|
||||||
this.tlsSocketFactory = Objects.requireNonNull(tlsSocketFactory, "tlsSocketFactory");
|
|
||||||
this.tlsParameters = tlsParameters;
|
|
||||||
this.dtlsContext = Objects.requireNonNull(dtlsContext, "dtlsContext");
|
|
||||||
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
this.mtu = mtu;
|
|
||||||
|
|
||||||
this.eventManager = Objects.requireNonNull(eventManager, "eventManager");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects the client.
|
|
||||||
*
|
|
||||||
* <p>If UDP is enabled and TCP is disabled, the client sends {@link UdpHelloPacket} to obtain
|
|
||||||
* a {@link ConnectionIdPacket} + {@link ProtocolRequirementsPacket} over UDP.</p>
|
|
||||||
*
|
|
||||||
* @return true if started, false if already started
|
|
||||||
* @throws Exception on connection/handshake errors
|
|
||||||
*/
|
|
||||||
public synchronized boolean connect() throws Exception {
|
|
||||||
if (lifecycleUp.get()) return false;
|
|
||||||
|
|
||||||
// Reset state
|
|
||||||
connectionId = null;
|
|
||||||
serverRequiredProtocols = null;
|
|
||||||
|
|
||||||
tcpConnected = false;
|
|
||||||
udpConnected = false;
|
|
||||||
udpBound = false;
|
|
||||||
wasFullyConnected = false;
|
|
||||||
|
|
||||||
// Mark lifecycle up early so checkFullyConnectedState can succeed during connect.
|
|
||||||
lifecycleUp.set(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (enabledProtocols.contains(NetworkProtocol.TCP)) {
|
|
||||||
connectTcpTls();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabledProtocols.contains(NetworkProtocol.UDP)) {
|
|
||||||
connectUdpDtls();
|
|
||||||
|
|
||||||
// UDP-only bootstrap: ask server to create/assign an id over UDP.
|
|
||||||
if (!enabledProtocols.contains(NetworkProtocol.TCP)) {
|
|
||||||
sendPacket(new UdpHelloPacket(), NetworkProtocol.UDP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
disconnect();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the client and stops background threads.
|
|
||||||
*
|
|
||||||
* @return true if a disconnect happened, false if it was already down
|
|
||||||
*/
|
|
||||||
public synchronized boolean disconnect() {
|
|
||||||
boolean wasUp = lifecycleUp.getAndSet(false);
|
|
||||||
|
|
||||||
Thread t1 = tcpThread;
|
|
||||||
Thread t2 = udpThread;
|
|
||||||
if (t1 != null) t1.interrupt();
|
|
||||||
if (t2 != null) t2.interrupt();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (tcpOut != null) tcpOut.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (tcpIn != null) tcpIn.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (tcpSocket != null) tcpSocket.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (udpChannel != null) udpChannel.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean tcpWas = tcpConnected;
|
|
||||||
boolean udpWas = udpConnected;
|
|
||||||
|
|
||||||
tcpConnected = false;
|
|
||||||
udpConnected = false;
|
|
||||||
udpBound = false;
|
|
||||||
|
|
||||||
tcpSocket = null;
|
|
||||||
tcpOut = null;
|
|
||||||
tcpIn = null;
|
|
||||||
tcpThread = null;
|
|
||||||
|
|
||||||
udpChannel = null;
|
|
||||||
dtlsEndpoint = null;
|
|
||||||
udpRemote = null;
|
|
||||||
udpThread = null;
|
|
||||||
|
|
||||||
connectionId = null;
|
|
||||||
serverRequiredProtocols = null;
|
|
||||||
|
|
||||||
if (tcpWas) {
|
|
||||||
fireDisconnected(NetworkProtocol.TCP);
|
|
||||||
}
|
|
||||||
if (udpWas) {
|
|
||||||
fireDisconnected(NetworkProtocol.UDP);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasFullyConnected) {
|
|
||||||
wasFullyConnected = false;
|
|
||||||
eventManager.executeEvent(new ClientFullyDisconnectedEvent(this, requiredProtocolsForClientView()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return wasUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server-assigned connection id, if already received.
|
|
||||||
*
|
|
||||||
* @return connection id or null
|
|
||||||
*/
|
|
||||||
public UUID connectionId() {
|
|
||||||
return connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocols the client enabled locally.
|
|
||||||
*
|
|
||||||
* @return enabled protocols
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> enabledProtocols() {
|
|
||||||
return EnumSet.copyOf(enabledProtocols);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server-required protocol set if received, otherwise null.
|
|
||||||
*
|
|
||||||
* @return required protocols from server or null
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> serverRequiredProtocols() {
|
|
||||||
EnumSet<NetworkProtocol> r = serverRequiredProtocols;
|
|
||||||
return r == null ? null : EnumSet.copyOf(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a snapshot of currently connected protocols (ready for app traffic).
|
|
||||||
*
|
|
||||||
* @return connected protocols
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> connectedProtocols() {
|
|
||||||
EnumSet<NetworkProtocol> set = EnumSet.noneOf(NetworkProtocol.class);
|
|
||||||
if (isTcpConnected()) set.add(NetworkProtocol.TCP);
|
|
||||||
if (isUdpConnected()) set.add(NetworkProtocol.UDP);
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the client is fully connected:
|
|
||||||
* <ul>
|
|
||||||
* <li>lifecycle is up</li>
|
|
||||||
* <li>connection id assigned</li>
|
|
||||||
* <li>server-required protocols received</li>
|
|
||||||
* <li>all server-required protocols are connected/ready</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @return true if fully connected
|
|
||||||
*/
|
|
||||||
public boolean isConnected() {
|
|
||||||
if (!lifecycleUp.get()) return false;
|
|
||||||
if (connectionId == null) return false;
|
|
||||||
if (serverRequiredProtocols == null) return false;
|
|
||||||
|
|
||||||
EnumSet<NetworkProtocol> required = EnumSet.copyOf(serverRequiredProtocols);
|
|
||||||
|
|
||||||
boolean tcpOk = !required.contains(NetworkProtocol.TCP) || isTcpConnected();
|
|
||||||
boolean udpOk = !required.contains(NetworkProtocol.UDP) || isUdpConnected();
|
|
||||||
|
|
||||||
return tcpOk && udpOk;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if TCP is up.
|
|
||||||
*
|
|
||||||
* @return true if TCP connected
|
|
||||||
*/
|
|
||||||
public boolean isTcpConnected() {
|
|
||||||
return tcpConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if UDP is up AND bound/acknowledged AND client id is set.
|
|
||||||
*
|
|
||||||
* @return true if UDP is ready for application traffic
|
|
||||||
*/
|
|
||||||
public boolean isUdpConnected() {
|
|
||||||
return udpConnected && udpBound && connectionId != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a packet via TCP/TLS or UDP/DTLS.
|
|
||||||
*
|
|
||||||
* <p>Note: UDP packets can be sent before UDP is "bound" because the bind/hello flow itself
|
|
||||||
* is transported via UDP.</p>
|
|
||||||
*
|
|
||||||
* @param packet packet
|
|
||||||
* @param protocol protocol to use
|
|
||||||
* @return true if sent, false if not possible (not up / protocol not enabled / not ready)
|
|
||||||
* @throws Exception on errors
|
|
||||||
*/
|
|
||||||
public boolean sendPacket(Packet packet, NetworkProtocol protocol) throws Exception {
|
|
||||||
Objects.requireNonNull(packet, "packet");
|
|
||||||
Objects.requireNonNull(protocol, "protocol");
|
|
||||||
|
|
||||||
if (!lifecycleUp.get()) return false;
|
|
||||||
if (!enabledProtocols.contains(protocol)) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean sent = switch (protocol) {
|
|
||||||
case TCP -> sendTcp(packet);
|
|
||||||
case UDP -> sendUdp(packet);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sent) {
|
|
||||||
eventManager.executeEvent(new C_PacketSendEvent(this, packet, protocol));
|
|
||||||
} else {
|
|
||||||
eventManager.executeEvent(new C_PacketSendFailedEvent(
|
|
||||||
this, packet, protocol, new IllegalStateException("Packet not sent")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sent;
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet, protocol, e));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectTcpTls() throws Exception {
|
|
||||||
InetSocketAddress addr = tcpEndpoint.resolveBest();
|
|
||||||
|
|
||||||
SSLSocket socket = (SSLSocket) tlsSocketFactory.createSocket(addr.getAddress(), addr.getPort());
|
|
||||||
socket.setTcpNoDelay(true);
|
|
||||||
socket.setSoTimeout(timeoutMillis);
|
|
||||||
|
|
||||||
if (tlsParameters != null) {
|
|
||||||
socket.setSSLParameters(tlsParameters);
|
|
||||||
} else {
|
|
||||||
SSLParameters p = socket.getSSLParameters();
|
|
||||||
p.setProtocols(new String[]{"TLSv1.3"});
|
|
||||||
socket.setSSLParameters(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.startHandshake();
|
|
||||||
|
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
|
||||||
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
this.tcpSocket = socket;
|
|
||||||
this.tcpOut = out;
|
|
||||||
this.tcpIn = in;
|
|
||||||
|
|
||||||
this.tcpConnected = true;
|
|
||||||
eventManager.executeEvent(new ClientConnectedEvent(this, NetworkProtocol.TCP));
|
|
||||||
checkFullyConnectedState();
|
|
||||||
|
|
||||||
this.tcpThread = new Thread(this::tcpReceiveLoop, "SecureNetworkClient-TCP-Receive");
|
|
||||||
this.tcpThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectUdpDtls() throws Exception {
|
|
||||||
InetSocketAddress addr = udpEndpoint.resolveBest();
|
|
||||||
|
|
||||||
DatagramChannel ch = DatagramChannel.open();
|
|
||||||
ch.configureBlocking(false);
|
|
||||||
ch.connect(addr);
|
|
||||||
|
|
||||||
DtlsEndpoint endpoint = new DtlsEndpoint(
|
|
||||||
ch,
|
|
||||||
dtlsContext,
|
|
||||||
true,
|
|
||||||
mtu,
|
|
||||||
timeoutMillis,
|
|
||||||
ClientAuthMode.OPTIONAL, // ignored for clientMode=true
|
|
||||||
this::onDtlsApplicationData
|
|
||||||
);
|
|
||||||
|
|
||||||
endpoint.handshake(addr);
|
|
||||||
|
|
||||||
this.udpChannel = ch;
|
|
||||||
this.dtlsEndpoint = endpoint;
|
|
||||||
this.udpRemote = addr;
|
|
||||||
|
|
||||||
this.udpConnected = true;
|
|
||||||
eventManager.executeEvent(new ClientConnectedEvent(this, NetworkProtocol.UDP));
|
|
||||||
checkFullyConnectedState();
|
|
||||||
|
|
||||||
this.udpThread = new Thread(this::udpPollLoop, "SecureNetworkClient-UDP-DTLS");
|
|
||||||
this.udpThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendTcp(Packet packet) throws Exception {
|
|
||||||
if (!tcpConnected) return false;
|
|
||||||
|
|
||||||
ObjectOutputStream out = tcpOut;
|
|
||||||
SSLSocket s = tcpSocket;
|
|
||||||
if (out == null || s == null || s.isClosed()) return false;
|
|
||||||
|
|
||||||
codec.sendToStream(packet, out);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendUdp(Packet packet) throws Exception {
|
|
||||||
if (!udpConnected) return false;
|
|
||||||
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
InetSocketAddress remote = udpRemote;
|
|
||||||
if (endpoint == null || remote == null) return false;
|
|
||||||
|
|
||||||
ByteBuffer buf = codec.encodeToBuffer(packet);
|
|
||||||
endpoint.sendApplication(remote, buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tcpReceiveLoop() {
|
|
||||||
try {
|
|
||||||
while (!Thread.currentThread().isInterrupted() && tcpConnected) {
|
|
||||||
SSLSocket s = tcpSocket;
|
|
||||||
ObjectInputStream in = tcpIn;
|
|
||||||
if (s == null || in == null || s.isClosed()) break;
|
|
||||||
|
|
||||||
Packet packet = codec.receiveFromStream(in);
|
|
||||||
if (packet == null) continue;
|
|
||||||
|
|
||||||
if (processServerControlPacket(packet, NetworkProtocol.TCP)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventManager.executeEvent(new C_PacketReceivedEvent(this, packet, NetworkProtocol.TCP));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, null, NetworkProtocol.TCP, e));
|
|
||||||
} finally {
|
|
||||||
boolean tcpWas = tcpConnected;
|
|
||||||
tcpConnected = false;
|
|
||||||
|
|
||||||
if (tcpWas) {
|
|
||||||
fireDisconnected(NetworkProtocol.TCP);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFullyDisconnectedIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void udpPollLoop() {
|
|
||||||
try {
|
|
||||||
while (!Thread.currentThread().isInterrupted() && udpConnected) {
|
|
||||||
DatagramChannel ch = udpChannel;
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
if (ch == null || endpoint == null || !ch.isOpen()) break;
|
|
||||||
|
|
||||||
endpoint.poll();
|
|
||||||
Thread.onSpinWait();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, null, NetworkProtocol.UDP, e));
|
|
||||||
} finally {
|
|
||||||
boolean udpWas = udpConnected;
|
|
||||||
|
|
||||||
udpConnected = false;
|
|
||||||
udpBound = false;
|
|
||||||
|
|
||||||
if (udpWas) {
|
|
||||||
fireDisconnected(NetworkProtocol.UDP);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFullyDisconnectedIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDtlsApplicationData(SocketAddress remote, ByteBuffer data) {
|
|
||||||
try {
|
|
||||||
Packet packet = codec.decodeFromBuffer(data);
|
|
||||||
|
|
||||||
if (processServerControlPacket(packet, NetworkProtocol.UDP)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventManager.executeEvent(new C_PacketReceivedEvent(this, packet, NetworkProtocol.UDP));
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, null, NetworkProtocol.UDP, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes connection/bootstrap/control packets that affect connectivity state.
|
|
||||||
*
|
|
||||||
* @param packet received packet
|
|
||||||
* @param sourceProtocol source protocol
|
|
||||||
* @return true if handled (should not be forwarded as application packet)
|
|
||||||
*/
|
|
||||||
private boolean processServerControlPacket(Packet packet, NetworkProtocol sourceProtocol) {
|
|
||||||
if (packet instanceof ConnectionIdPacket idPacket) {
|
|
||||||
this.connectionId = idPacket.connectionId();
|
|
||||||
|
|
||||||
// UDP-only bootstrap: once server assigned an id via UDP, the remote is already bound.
|
|
||||||
if (sourceProtocol == NetworkProtocol.UDP && !enabledProtocols.contains(NetworkProtocol.TCP)) {
|
|
||||||
this.udpBound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFullyConnectedState();
|
|
||||||
|
|
||||||
// If TCP delivered the id and UDP is enabled, attempt bind now.
|
|
||||||
if (sourceProtocol == NetworkProtocol.TCP && enabledProtocols.contains(NetworkProtocol.UDP)) {
|
|
||||||
tryAutoBindUdp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet instanceof ProtocolRequirementsPacket reqPacket) {
|
|
||||||
this.serverRequiredProtocols = EnumSet.copyOf(reqPacket.requiredProtocols());
|
|
||||||
checkFullyConnectedState();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet instanceof UdpBindAckPacket ackPacket) {
|
|
||||||
UUID id = connectionId;
|
|
||||||
if (id != null && id.equals(ackPacket.connectionId())) {
|
|
||||||
this.udpBound = true;
|
|
||||||
checkFullyConnectedState();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryAutoBindUdp() {
|
|
||||||
UUID id = connectionId;
|
|
||||||
if (id == null) return;
|
|
||||||
if (!udpConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Bind request goes over UDP/DTLS. Server responds with UdpBindAckPacket.
|
|
||||||
sendPacket(new UdpBindPacket(id), NetworkProtocol.UDP);
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new C_PacketSendFailedEvent(this, new UdpBindPacket(id), NetworkProtocol.UDP, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fireDisconnected(NetworkProtocol protocol) {
|
|
||||||
eventManager.executeEvent(new ClientDisconnectedEvent(this, protocol));
|
|
||||||
}
|
|
||||||
|
|
||||||
private EnumSet<NetworkProtocol> requiredProtocolsForClientView() {
|
|
||||||
EnumSet<NetworkProtocol> r = serverRequiredProtocols;
|
|
||||||
if (r != null) return EnumSet.copyOf(r);
|
|
||||||
|
|
||||||
// Fallback for UI/debug/events only; does NOT influence isConnected().
|
|
||||||
return EnumSet.copyOf(enabledProtocols);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkFullyConnectedState() {
|
|
||||||
if (wasFullyConnected) return;
|
|
||||||
|
|
||||||
if (!isConnected()) return;
|
|
||||||
|
|
||||||
wasFullyConnected = true;
|
|
||||||
eventManager.executeEvent(new ClientFullyConnectedEvent(this, requiredProtocolsForClientView()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkFullyDisconnectedIfNeeded() {
|
|
||||||
if (!wasFullyConnected) return;
|
|
||||||
|
|
||||||
if (isConnected()) return;
|
|
||||||
|
|
||||||
wasFullyConnected = false;
|
|
||||||
eventManager.executeEvent(new ClientFullyDisconnectedEvent(this, requiredProtocolsForClientView()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builder for {@link NetworkClient}.
|
|
||||||
*/
|
|
||||||
public static final class Builder {
|
|
||||||
|
|
||||||
private Endpoint tcpEndpoint;
|
|
||||||
private Endpoint udpEndpoint;
|
|
||||||
private EnumSet<NetworkProtocol> enabledProtocols = EnumSet.of(NetworkProtocol.TCP, NetworkProtocol.UDP);
|
|
||||||
|
|
||||||
private PacketCodec codec;
|
|
||||||
private SSLSocketFactory tlsSocketFactory;
|
|
||||||
private SSLParameters tlsParameters;
|
|
||||||
private SSLContext dtlsContext;
|
|
||||||
|
|
||||||
private int timeoutMillis = 5000;
|
|
||||||
private int mtu = 1400;
|
|
||||||
|
|
||||||
private EventManager eventManager;
|
|
||||||
|
|
||||||
public Builder setTcpEndpoint(Endpoint endpoint) {
|
|
||||||
this.tcpEndpoint = endpoint;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setUdpEndpoint(Endpoint endpoint) {
|
|
||||||
this.udpEndpoint = endpoint;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setEnabledProtocols(EnumSet<NetworkProtocol> protocols) {
|
|
||||||
this.enabledProtocols = EnumSet.copyOf(Objects.requireNonNull(protocols, "protocols"));
|
|
||||||
if (this.enabledProtocols.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("enabledProtocols must not be empty");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setCodec(PacketCodec codec) {
|
|
||||||
this.codec = codec;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTlsSocketFactory(SSLSocketFactory factory) {
|
|
||||||
this.tlsSocketFactory = factory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTlsParameters(SSLParameters parameters) {
|
|
||||||
this.tlsParameters = parameters;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setDtlsContext(SSLContext context) {
|
|
||||||
this.dtlsContext = context;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTimeoutMillis(int timeoutMillis) {
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setMtu(int mtu) {
|
|
||||||
this.mtu = mtu;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setEventManager(EventManager eventManager) {
|
|
||||||
this.eventManager = eventManager;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkClient build() {
|
|
||||||
if (codec == null) throw new IllegalStateException("codec not set");
|
|
||||||
if (eventManager == null) throw new IllegalStateException("eventManager not set");
|
|
||||||
|
|
||||||
if (enabledProtocols.contains(NetworkProtocol.TCP)) {
|
|
||||||
if (tcpEndpoint == null) throw new IllegalStateException("tcpEndpoint not set (TCP enabled)");
|
|
||||||
if (tlsSocketFactory == null) throw new IllegalStateException("tlsSocketFactory not set (TLS mandatory)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabledProtocols.contains(NetworkProtocol.UDP)) {
|
|
||||||
if (udpEndpoint == null) throw new IllegalStateException("udpEndpoint not set (UDP enabled)");
|
|
||||||
if (dtlsContext == null) throw new IllegalStateException("dtlsContext not set (DTLS mandatory for UDP)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeoutMillis <= 0) throw new IllegalStateException("timeoutMillis must be > 0");
|
|
||||||
if (mtu < 256) throw new IllegalStateException("mtu too small");
|
|
||||||
|
|
||||||
// Placeholders are never used when protocol is disabled, but must be non-null for constructor invariants.
|
|
||||||
Endpoint tcpEp = tcpEndpoint != null ? tcpEndpoint : new Endpoint("127.0.0.1", 1);
|
|
||||||
Endpoint udpEp = udpEndpoint != null ? udpEndpoint : new Endpoint("127.0.0.1", 1);
|
|
||||||
SSLContext dtls = dtlsContext != null ? dtlsContext : emptyDtls();
|
|
||||||
|
|
||||||
return new NetworkClient(
|
|
||||||
tcpEp,
|
|
||||||
udpEp,
|
|
||||||
enabledProtocols,
|
|
||||||
codec,
|
|
||||||
tlsSocketFactory != null ? tlsSocketFactory : (SSLSocketFactory) SSLSocketFactory.getDefault(),
|
|
||||||
tlsParameters,
|
|
||||||
dtls,
|
|
||||||
timeoutMillis,
|
|
||||||
mtu,
|
|
||||||
eventManager
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SSLContext emptyDtls() {
|
|
||||||
try {
|
|
||||||
return SSLContext.getInstance("DTLS");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Failed to create DTLS context", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet was received by the client on a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class C_PacketReceivedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet received event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param packet received packet
|
|
||||||
* @param protocol protocol the packet was received on
|
|
||||||
*/
|
|
||||||
public C_PacketReceivedEvent(NetworkClient client, Packet packet, NetworkProtocol protocol) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client instance.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the received packet.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol the packet was received on.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when receiving or decoding a packet failed on the client
|
|
||||||
* for a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class C_PacketReceivedFailedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
private final Exception error;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet receive failed event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param packet packet that failed to be processed (may be null if undecodable)
|
|
||||||
* @param protocol protocol the failure happened on
|
|
||||||
* @param error root cause
|
|
||||||
*/
|
|
||||||
public C_PacketReceivedFailedEvent(
|
|
||||||
NetworkClient client,
|
|
||||||
Packet packet,
|
|
||||||
NetworkProtocol protocol,
|
|
||||||
Exception error
|
|
||||||
) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.packet = packet; // may be null
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
this.error = Objects.requireNonNull(error, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client instance.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet that failed to be processed, if available.
|
|
||||||
*
|
|
||||||
* @return packet or null
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol the failure occurred on.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying error.
|
|
||||||
*
|
|
||||||
* @return error
|
|
||||||
*/
|
|
||||||
public Exception getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when an unknown (non-packet) object was received on a specific protocol.
|
|
||||||
*
|
|
||||||
* <p>Note: In v2 the default transport is {@link dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketCodec}
|
|
||||||
* based and typically produces {@link Packet}.
|
|
||||||
* This event exists for backwards compatibility and for custom codecs/handlers.</p>
|
|
||||||
*/
|
|
||||||
public final class C_UnknownObjectReceivedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final Object received;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new unknown object received event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param received received object
|
|
||||||
* @param protocol protocol the object was received on
|
|
||||||
*/
|
|
||||||
public C_UnknownObjectReceivedEvent(NetworkClient client, Object received, NetworkProtocol protocol) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.received = Objects.requireNonNull(received, "received");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client instance.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the received object.
|
|
||||||
*
|
|
||||||
* @return received object
|
|
||||||
*/
|
|
||||||
public Object getReceived() {
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol the object was received on.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet was successfully sent by the client on a specific protocol.
|
|
||||||
*
|
|
||||||
* <p>v2 mapping:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link NetworkProtocol#TCP} → TLS/TCP</li>
|
|
||||||
* <li>{@link NetworkProtocol#UDP} → DTLS/UDP</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class C_PacketSendEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet send event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param packet sent packet
|
|
||||||
* @param protocol used protocol
|
|
||||||
*/
|
|
||||||
public C_PacketSendEvent(
|
|
||||||
NetworkClient client,
|
|
||||||
Packet packet,
|
|
||||||
NetworkProtocol protocol
|
|
||||||
) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sent packet.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol used.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.send;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet send failed on the client on a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class C_PacketSendFailedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
private final Exception error;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet send failed event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param packet packet that failed to be sent
|
|
||||||
* @param protocol intended protocol
|
|
||||||
* @param error root cause
|
|
||||||
*/
|
|
||||||
public C_PacketSendFailedEvent(
|
|
||||||
NetworkClient client,
|
|
||||||
Packet packet,
|
|
||||||
NetworkProtocol protocol,
|
|
||||||
Exception error
|
|
||||||
) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
this.error = Objects.requireNonNull(error, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet that failed to be sent.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the intended protocol.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying error.
|
|
||||||
*
|
|
||||||
* @return error
|
|
||||||
*/
|
|
||||||
public Exception getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the client established a specific protocol connection.
|
|
||||||
*
|
|
||||||
* <p>Protocol-specific:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link NetworkProtocol#TCP}: after TLS handshake + TCP streams are ready</li>
|
|
||||||
* <li>{@link NetworkProtocol#UDP}: after DTLS handshake (channel/engine ready)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClientConnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client connected event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param protocol connected protocol
|
|
||||||
*/
|
|
||||||
public ClientConnectedEvent(NetworkClient client, NetworkProtocol protocol) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol that was connected.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.connect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the client satisfies its enabled protocol requirements.
|
|
||||||
*
|
|
||||||
* <p>In v2 this typically means:
|
|
||||||
* <ul>
|
|
||||||
* <li>TCP is connected (TLS ok)</li>
|
|
||||||
* <li>and if UDP is enabled: DTLS is established and the bind flow is completed</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClientFullyConnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final EnumSet<NetworkProtocol> requiredProtocols;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param requiredProtocols protocols that are required and now satisfied
|
|
||||||
*/
|
|
||||||
public ClientFullyConnectedEvent(NetworkClient client, EnumSet<NetworkProtocol> requiredProtocols) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.requiredProtocols = EnumSet.copyOf(Objects.requireNonNull(requiredProtocols, "requiredProtocols"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the required protocols that are now established.
|
|
||||||
*
|
|
||||||
* @return required protocols (copy)
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> getRequiredProtocols() {
|
|
||||||
return EnumSet.copyOf(requiredProtocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the client lost/closed a specific protocol connection.
|
|
||||||
*
|
|
||||||
* <p>Protocol-specific:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link NetworkProtocol#TCP}: TCP/TLS socket closed or receive loop ended</li>
|
|
||||||
* <li>{@link NetworkProtocol#UDP}: UDP channel/DTLS endpoint closed</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClientDisconnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client disconnected event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param protocol disconnected protocol
|
|
||||||
*/
|
|
||||||
public ClientDisconnectedEvent(NetworkClient client, NetworkProtocol protocol) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol that was disconnected.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.disconnect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the client previously satisfied its required protocols and then stopped satisfying them.
|
|
||||||
*/
|
|
||||||
public final class ClientFullyDisconnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final NetworkClient client;
|
|
||||||
private final EnumSet<NetworkProtocol> requiredProtocols;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new fully disconnected event.
|
|
||||||
*
|
|
||||||
* @param client client instance
|
|
||||||
* @param requiredProtocols protocols that were required for full connectivity
|
|
||||||
*/
|
|
||||||
public ClientFullyDisconnectedEvent(NetworkClient client, EnumSet<NetworkProtocol> requiredProtocols) {
|
|
||||||
this.client = Objects.requireNonNull(client, "client");
|
|
||||||
this.requiredProtocols = EnumSet.copyOf(Objects.requireNonNull(requiredProtocols, "requiredProtocols"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client.
|
|
||||||
*
|
|
||||||
* @return client
|
|
||||||
*/
|
|
||||||
public NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns protocols that were required for full connectivity.
|
|
||||||
*
|
|
||||||
* @return required protocols (copy)
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> getRequiredProtocols() {
|
|
||||||
return EnumSet.copyOf(requiredProtocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,61 +2,21 @@
|
|||||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||||
*
|
*
|
||||||
* You are unauthorized to remove this copyright.
|
* You are unauthorized to remove this copyright.
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/
|
* 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
|
* See LICENSE-File if exists
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
|
package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.ObjectInputStream;
|
import java.util.UUID;
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base type for packets used by the v2 secure network system.
|
|
||||||
*
|
|
||||||
* <p>Packets are encoded as:
|
|
||||||
* <pre>
|
|
||||||
* [int packetId][payload bytes...]
|
|
||||||
* </pre>
|
|
||||||
* Payload is written/read by the packet itself through Java serialization streams.</p>
|
|
||||||
*/
|
|
||||||
public abstract class Packet {
|
public abstract class Packet {
|
||||||
|
|
||||||
private final int packetId;
|
public Packet() {}
|
||||||
|
|
||||||
|
public abstract int getPacketID();
|
||||||
|
public abstract void read(DataInputStream stream, UUID clientID) throws IOException;
|
||||||
|
public abstract void write(DataOutputStream stream) throws IOException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet.
|
|
||||||
*
|
|
||||||
* @param packetId unique packet id
|
|
||||||
*/
|
|
||||||
protected Packet(int packetId) {
|
|
||||||
this.packetId = packetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet id.
|
|
||||||
*
|
|
||||||
* @return id
|
|
||||||
*/
|
|
||||||
public final int packetId() {
|
|
||||||
return packetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes payload fields to the stream.
|
|
||||||
*
|
|
||||||
* @param out output stream
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
*/
|
|
||||||
public abstract void write(ObjectOutputStream out) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads payload fields from the stream.
|
|
||||||
*
|
|
||||||
* @param in input stream
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
* @throws ClassNotFoundException on deserialization errors
|
|
||||||
*/
|
|
||||||
public abstract void read(ObjectInputStream in) throws IOException, ClassNotFoundException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes and decodes {@link Packet} instances for TCP (stream) and UDP (datagram).
|
|
||||||
*
|
|
||||||
* <p>Wire format (transport-agnostic):
|
|
||||||
* <pre>
|
|
||||||
* [int packetId][ObjectStream payload...]
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
public final class PacketCodec {
|
|
||||||
|
|
||||||
private final PacketRegistry registry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new codec.
|
|
||||||
*
|
|
||||||
* @param registry packet registry
|
|
||||||
*/
|
|
||||||
public PacketCodec(PacketRegistry registry) {
|
|
||||||
this.registry = Objects.requireNonNull(registry, "registry");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a packet into a byte buffer suitable for UDP/DTLS sending.
|
|
||||||
*
|
|
||||||
* @param packet packet to encode
|
|
||||||
* @return encoded buffer (position=0, limit=length)
|
|
||||||
* @throws IOException on serialization errors
|
|
||||||
*/
|
|
||||||
public ByteBuffer encodeToBuffer(Packet packet) throws IOException {
|
|
||||||
Objects.requireNonNull(packet, "packet");
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
|
|
||||||
try (DataOutputStream dos = new DataOutputStream(baos)) {
|
|
||||||
dos.writeInt(packet.packetId());
|
|
||||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
|
||||||
packet.write(oos);
|
|
||||||
oos.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ByteBuffer.wrap(baos.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a packet from a received datagram buffer.
|
|
||||||
*
|
|
||||||
* @param buffer received buffer (position..limit)
|
|
||||||
* @return decoded packet
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
* @throws ClassNotFoundException on deserialization errors
|
|
||||||
*/
|
|
||||||
public Packet decodeFromBuffer(ByteBuffer buffer) throws IOException, ClassNotFoundException {
|
|
||||||
Objects.requireNonNull(buffer, "buffer");
|
|
||||||
|
|
||||||
ByteArrayInputStream bais =
|
|
||||||
new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining());
|
|
||||||
|
|
||||||
int packetId;
|
|
||||||
try (DataInputStream dis = new DataInputStream(bais)) {
|
|
||||||
packetId = dis.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
Packet packet = registry.create(packetId);
|
|
||||||
|
|
||||||
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
|
|
||||||
packet.read(ois);
|
|
||||||
}
|
|
||||||
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a packet over a TCP/TLS stream.
|
|
||||||
*
|
|
||||||
* @param packet packet
|
|
||||||
* @param out output stream
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
*/
|
|
||||||
public void sendToStream(Packet packet, ObjectOutputStream out) throws IOException {
|
|
||||||
Objects.requireNonNull(packet, "packet");
|
|
||||||
Objects.requireNonNull(out, "out");
|
|
||||||
|
|
||||||
out.writeInt(packet.packetId());
|
|
||||||
packet.write(out);
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives a packet from a TCP/TLS stream.
|
|
||||||
*
|
|
||||||
* @param in input stream
|
|
||||||
* @return decoded packet
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
* @throws ClassNotFoundException on deserialization errors
|
|
||||||
*/
|
|
||||||
public Packet receiveFromStream(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
||||||
Objects.requireNonNull(in, "in");
|
|
||||||
|
|
||||||
int packetId = in.readInt();
|
|
||||||
Packet packet = registry.create(packetId);
|
|
||||||
packet.read(in);
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic packet handler that allows registering packet instances directly.
|
||||||
|
* Uses the packet's own getPacketID() to store it internally.
|
||||||
|
*/
|
||||||
|
public class PacketHandler {
|
||||||
|
|
||||||
|
private final Map<Integer, Supplier<? extends Packet>> factories = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a packet instance. The packet ID is automatically taken from getPacketID().
|
||||||
|
*
|
||||||
|
* @param factory Packet
|
||||||
|
*/
|
||||||
|
public void registerPacket(Supplier<? extends Packet> factory) {
|
||||||
|
int id = factory.get().getPacketID();
|
||||||
|
if (id <= 0) throw new IllegalArgumentException("Packet ID must be > 0");
|
||||||
|
factories.put(id, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a packet from the input stream.
|
||||||
|
* Expects the packet to be serialized as an object.
|
||||||
|
*
|
||||||
|
* @param input DataInputStream to read from
|
||||||
|
* @return Packet instance or null if failed
|
||||||
|
*/
|
||||||
|
public boolean readPacket(DataInputStream input, UUID clientID, int id) throws IOException {
|
||||||
|
Supplier<? extends Packet> factory = factories.get(id);
|
||||||
|
if (factory == null) return false;
|
||||||
|
|
||||||
|
Packet packet = factory.get();
|
||||||
|
packet.read(input, clientID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a packet to the output stream.
|
||||||
|
*
|
||||||
|
* @param output DataOutputStream to write to
|
||||||
|
* @param packet Packet instance to send
|
||||||
|
*/
|
||||||
|
public void sendPacket(DataOutputStream output, Packet packet, UUID clientID) throws IOException {
|
||||||
|
output.writeUTF(clientID.toString());
|
||||||
|
output.writeInt(packet.getPacketID());
|
||||||
|
packet.write(output);
|
||||||
|
output.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.*;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread-safe registry that maps packet ids to factories creating new packet instances.
|
|
||||||
*
|
|
||||||
* <p>This avoids reusing the same packet instance across threads/connections.</p>
|
|
||||||
*/
|
|
||||||
public final class PacketRegistry {
|
|
||||||
|
|
||||||
private final Map<Integer, Supplier<? extends Packet>> factories = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public PacketRegistry() {
|
|
||||||
register(ConnectionIdPacket::new);
|
|
||||||
register(ProtocolRequirementsPacket::new);
|
|
||||||
register(UdpBindAckPacket::new);
|
|
||||||
register(UdpBindPacket::new);
|
|
||||||
register(UdpHelloPacket::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a packet factory for the given id.
|
|
||||||
*
|
|
||||||
* @param factory instance factory
|
|
||||||
* @return true if newly registered, false if id already present
|
|
||||||
*/
|
|
||||||
public boolean register(Supplier<? extends Packet> factory) {
|
|
||||||
Objects.requireNonNull(factory, "factory");
|
|
||||||
return factories.putIfAbsent(factory.get().packetId(), factory) == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a packet id is registered.
|
|
||||||
*
|
|
||||||
* @param packetId id
|
|
||||||
* @return true if registered
|
|
||||||
*/
|
|
||||||
public boolean isRegistered(int packetId) {
|
|
||||||
return factories.containsKey(packetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet instance for the given id.
|
|
||||||
*
|
|
||||||
* @param packetId id
|
|
||||||
* @return new packet instance
|
|
||||||
* @throws IllegalArgumentException if id is not registered or factory returns null
|
|
||||||
*/
|
|
||||||
public Packet create(int packetId) {
|
|
||||||
Supplier<? extends Packet> supplier = factories.get(packetId);
|
|
||||||
if (supplier == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown packetId: " + packetId);
|
|
||||||
}
|
|
||||||
Packet p = supplier.get();
|
|
||||||
if (p == null) {
|
|
||||||
throw new IllegalArgumentException("Packet factory returned null for packetId: " + packetId);
|
|
||||||
}
|
|
||||||
if (p.packetId() != packetId) {
|
|
||||||
throw new IllegalArgumentException("Packet factory produced mismatching id: expected="
|
|
||||||
+ packetId + ", got=" + p.packetId());
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server -> Client packet that assigns a server-side connection id.
|
|
||||||
*
|
|
||||||
* <p>This id is used to bind the DTLS/UDP endpoint to the already authenticated TCP/TLS connection
|
|
||||||
* via {@link UdpBindPacket}.</p>
|
|
||||||
*/
|
|
||||||
public final class ConnectionIdPacket extends Packet {
|
|
||||||
|
|
||||||
private UUID connectionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an empty packet instance for decoding.
|
|
||||||
*/
|
|
||||||
public ConnectionIdPacket() {
|
|
||||||
super(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a packet instance with a connection id for sending.
|
|
||||||
*
|
|
||||||
* @param connectionId connection id (must not be null)
|
|
||||||
*/
|
|
||||||
public ConnectionIdPacket(UUID connectionId) {
|
|
||||||
this();
|
|
||||||
this.connectionId = Objects.requireNonNull(connectionId, "connectionId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the assigned connection id.
|
|
||||||
*
|
|
||||||
* @return connection id
|
|
||||||
*/
|
|
||||||
public UUID connectionId() {
|
|
||||||
return connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ObjectOutputStream out) throws IOException {
|
|
||||||
if (connectionId == null) {
|
|
||||||
throw new IOException("ConnectionIdPacket.connectionId is null");
|
|
||||||
}
|
|
||||||
out.writeLong(connectionId.getMostSignificantBits());
|
|
||||||
out.writeLong(connectionId.getLeastSignificantBits());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(ObjectInputStream in) throws IOException {
|
|
||||||
long msb = in.readLong();
|
|
||||||
long lsb = in.readLong();
|
|
||||||
this.connectionId = new UUID(msb, lsb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server -> client packet containing the server-required protocol set.
|
|
||||||
*
|
|
||||||
* <p>This packet allows the client to evaluate "fully connected" based on the server policy.</p>
|
|
||||||
*/
|
|
||||||
public final class ProtocolRequirementsPacket extends Packet {
|
|
||||||
|
|
||||||
private EnumSet<NetworkProtocol> requiredProtocols = EnumSet.noneOf(NetworkProtocol.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an empty packet for deserialization.
|
|
||||||
*/
|
|
||||||
public ProtocolRequirementsPacket() {
|
|
||||||
super(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the packet.
|
|
||||||
*
|
|
||||||
* @param requiredProtocols required protocol set
|
|
||||||
*/
|
|
||||||
public ProtocolRequirementsPacket(EnumSet<NetworkProtocol> requiredProtocols) {
|
|
||||||
super(2);
|
|
||||||
this.requiredProtocols = EnumSet.copyOf(Objects.requireNonNull(requiredProtocols, "requiredProtocols"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the required protocol set.
|
|
||||||
*
|
|
||||||
* @return required protocols
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> requiredProtocols() {
|
|
||||||
return EnumSet.copyOf(requiredProtocols);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ObjectOutputStream out) throws IOException {
|
|
||||||
Objects.requireNonNull(out, "out");
|
|
||||||
out.writeInt(toMask(requiredProtocols));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(ObjectInputStream in) throws IOException {
|
|
||||||
Objects.requireNonNull(in, "in");
|
|
||||||
int mask = in.readInt();
|
|
||||||
this.requiredProtocols = fromMask(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int toMask(EnumSet<NetworkProtocol> set) {
|
|
||||||
int mask = 0;
|
|
||||||
if (set.contains(NetworkProtocol.TCP)) mask |= 1;
|
|
||||||
if (set.contains(NetworkProtocol.UDP)) mask |= 2;
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EnumSet<NetworkProtocol> fromMask(int mask) {
|
|
||||||
EnumSet<NetworkProtocol> set = EnumSet.noneOf(NetworkProtocol.class);
|
|
||||||
if ((mask & 1) != 0) set.add(NetworkProtocol.TCP);
|
|
||||||
if ((mask & 2) != 0) set.add(NetworkProtocol.UDP);
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server -> client acknowledgement for {@link UdpBindPacket}.
|
|
||||||
*
|
|
||||||
* <p>The client must only consider UDP "bound/ready" after receiving this ACK with the matching connection id.</p>
|
|
||||||
*
|
|
||||||
* <p><strong>IMPORTANT:</strong> Ensure {@link #PACKET_ID} does not conflict with your other packet ids and
|
|
||||||
* register it in {@code PacketRegistry}.</p>
|
|
||||||
*/
|
|
||||||
public final class UdpBindAckPacket extends Packet {
|
|
||||||
|
|
||||||
private UUID connectionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an empty packet for deserialization.
|
|
||||||
*/
|
|
||||||
public UdpBindAckPacket() {
|
|
||||||
super(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the ACK packet.
|
|
||||||
*
|
|
||||||
* @param connectionId connection id being acknowledged
|
|
||||||
*/
|
|
||||||
public UdpBindAckPacket(UUID connectionId) {
|
|
||||||
super(3);
|
|
||||||
this.connectionId = Objects.requireNonNull(connectionId, "connectionId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the acknowledged connection id.
|
|
||||||
*
|
|
||||||
* @return connection id
|
|
||||||
*/
|
|
||||||
public UUID connectionId() {
|
|
||||||
return connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ObjectOutputStream out) throws IOException {
|
|
||||||
Objects.requireNonNull(out, "out");
|
|
||||||
out.writeLong(connectionId.getMostSignificantBits());
|
|
||||||
out.writeLong(connectionId.getLeastSignificantBits());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(ObjectInputStream in) throws IOException {
|
|
||||||
Objects.requireNonNull(in, "in");
|
|
||||||
long msb = in.readLong();
|
|
||||||
long lsb = in.readLong();
|
|
||||||
this.connectionId = new UUID(msb, lsb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client -> Server packet sent over UDP/DTLS to bind the DTLS remote address to an existing TCP/TLS connection.
|
|
||||||
*
|
|
||||||
* <p>Flow:
|
|
||||||
* <ol>
|
|
||||||
* <li>Client connects via TCP/TLS</li>
|
|
||||||
* <li>Server sends {@link ConnectionIdPacket} over TCP</li>
|
|
||||||
* <li>Client completes DTLS handshake and sends {@link UdpBindPacket} over UDP/DTLS with the same connection id</li>
|
|
||||||
* <li>Server attaches UDP remote to the matching {@code SecureConnection}</li>
|
|
||||||
* </ol>
|
|
||||||
*/
|
|
||||||
public final class UdpBindPacket extends Packet {
|
|
||||||
|
|
||||||
private UUID connectionId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an empty packet instance for decoding.
|
|
||||||
*/
|
|
||||||
public UdpBindPacket() {
|
|
||||||
super(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a bind packet for sending.
|
|
||||||
*
|
|
||||||
* @param connectionId server-assigned connection id (must not be null)
|
|
||||||
*/
|
|
||||||
public UdpBindPacket(UUID connectionId) {
|
|
||||||
this();
|
|
||||||
this.connectionId = Objects.requireNonNull(connectionId, "connectionId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection id this UDP endpoint wants to bind to.
|
|
||||||
*
|
|
||||||
* @return connection id
|
|
||||||
*/
|
|
||||||
public UUID connectionId() {
|
|
||||||
return connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ObjectOutputStream out) throws IOException {
|
|
||||||
if (connectionId == null) {
|
|
||||||
throw new IOException("UdpBindPacket.connectionId is null");
|
|
||||||
}
|
|
||||||
out.writeLong(connectionId.getMostSignificantBits());
|
|
||||||
out.writeLong(connectionId.getLeastSignificantBits());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(ObjectInputStream in) throws IOException {
|
|
||||||
long msb = in.readLong();
|
|
||||||
long lsb = in.readLong();
|
|
||||||
this.connectionId = new UUID(msb, lsb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client -> server packet used for UDP-only bootstrap.
|
|
||||||
*
|
|
||||||
* <p>The client sends this packet over UDP/DTLS to request the server to create/assign a connection id
|
|
||||||
* and return bootstrap information (e.g. ConnectionIdPacket + ProtocolRequirementsPacket) over UDP.</p>
|
|
||||||
*/
|
|
||||||
public final class UdpHelloPacket extends Packet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an empty packet for serialization/deserialization.
|
|
||||||
*/
|
|
||||||
public UdpHelloPacket() {
|
|
||||||
super(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ObjectOutputStream out) throws IOException {
|
|
||||||
Objects.requireNonNull(out, "out");
|
|
||||||
// No payload.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(ObjectInputStream in) throws IOException {
|
|
||||||
Objects.requireNonNull(in, "in");
|
|
||||||
// No payload.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,363 +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://unlegitdqrk.dev/
|
|
||||||
* 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.PacketCodec;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send.S_PacketSendEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send.S_PacketSendFailedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.DtlsEndpoint;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents one connected client on the server side, with optional TCP/TLS and/or UDP/DTLS channels.
|
|
||||||
*
|
|
||||||
* <p>Connection usability is defined by {@link ServerProtocolMode#required()}.</p>
|
|
||||||
*/
|
|
||||||
public final class ClientConnection {
|
|
||||||
|
|
||||||
private final UUID connectionId;
|
|
||||||
private final PacketCodec codec;
|
|
||||||
private final ServerProtocolMode protocolMode;
|
|
||||||
private final EventManager eventManager;
|
|
||||||
|
|
||||||
// TCP (TLS) - optional
|
|
||||||
private final SSLSocket tcpSocket;
|
|
||||||
private final ObjectOutputStream tcpOut;
|
|
||||||
private final ObjectInputStream tcpIn;
|
|
||||||
|
|
||||||
// UDP (DTLS) - optional
|
|
||||||
private volatile SocketAddress udpRemote;
|
|
||||||
private volatile DtlsEndpoint.DtlsSession udpSession;
|
|
||||||
private volatile DtlsEndpoint dtlsEndpoint;
|
|
||||||
private volatile DatagramChannel udpChannel;
|
|
||||||
private volatile boolean udpBound;
|
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new connection with a predefined connection id.
|
|
||||||
*
|
|
||||||
* @param connectionId server-side unique connection id
|
|
||||||
* @param codec packet codec
|
|
||||||
* @param protocolMode server protocol mode (supported/required)
|
|
||||||
* @param eventManager event manager used to dispatch server events
|
|
||||||
* @param tcpSocket TLS socket (nullable for UDP-only)
|
|
||||||
* @param tcpOut TCP output stream (nullable for UDP-only)
|
|
||||||
* @param tcpIn TCP input stream (nullable for UDP-only)
|
|
||||||
*/
|
|
||||||
public ClientConnection(
|
|
||||||
UUID connectionId,
|
|
||||||
PacketCodec codec,
|
|
||||||
ServerProtocolMode protocolMode,
|
|
||||||
EventManager eventManager,
|
|
||||||
SSLSocket tcpSocket,
|
|
||||||
ObjectOutputStream tcpOut,
|
|
||||||
ObjectInputStream tcpIn
|
|
||||||
) {
|
|
||||||
this.connectionId = Objects.requireNonNull(connectionId, "connectionId");
|
|
||||||
this.codec = Objects.requireNonNull(codec, "codec");
|
|
||||||
this.protocolMode = Objects.requireNonNull(protocolMode, "protocolMode");
|
|
||||||
this.eventManager = Objects.requireNonNull(eventManager, "eventManager");
|
|
||||||
this.tcpSocket = tcpSocket;
|
|
||||||
this.tcpOut = tcpOut;
|
|
||||||
this.tcpIn = tcpIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new TCP-accepted connection (TLS).
|
|
||||||
*
|
|
||||||
* @param codec packet codec
|
|
||||||
* @param protocolMode server protocol mode (supported/required)
|
|
||||||
* @param eventManager event manager used to dispatch server events
|
|
||||||
* @param tcpSocket TLS socket
|
|
||||||
* @param tcpOut TCP output stream
|
|
||||||
* @param tcpIn TCP input stream
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public static ClientConnection tcpAccepted(
|
|
||||||
PacketCodec codec,
|
|
||||||
ServerProtocolMode protocolMode,
|
|
||||||
EventManager eventManager,
|
|
||||||
SSLSocket tcpSocket,
|
|
||||||
ObjectOutputStream tcpOut,
|
|
||||||
ObjectInputStream tcpIn
|
|
||||||
) {
|
|
||||||
return new ClientConnection(
|
|
||||||
UUID.randomUUID(),
|
|
||||||
codec,
|
|
||||||
protocolMode,
|
|
||||||
eventManager,
|
|
||||||
Objects.requireNonNull(tcpSocket, "tcpSocket"),
|
|
||||||
Objects.requireNonNull(tcpOut, "tcpOut"),
|
|
||||||
Objects.requireNonNull(tcpIn, "tcpIn")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new UDP-only connection.
|
|
||||||
*
|
|
||||||
* @param codec packet codec
|
|
||||||
* @param protocolMode server protocol mode (supported/required)
|
|
||||||
* @param eventManager event manager used to dispatch server events
|
|
||||||
* @param connectionId pre-generated connection id
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public static ClientConnection udpOnly(
|
|
||||||
PacketCodec codec,
|
|
||||||
ServerProtocolMode protocolMode,
|
|
||||||
EventManager eventManager,
|
|
||||||
UUID connectionId
|
|
||||||
) {
|
|
||||||
return new ClientConnection(
|
|
||||||
Objects.requireNonNull(connectionId, "connectionId"),
|
|
||||||
codec,
|
|
||||||
protocolMode,
|
|
||||||
eventManager,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a server-side unique connection id.
|
|
||||||
*
|
|
||||||
* @return connection id
|
|
||||||
*/
|
|
||||||
public UUID connectionId() {
|
|
||||||
return connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns required protocols snapshot (server policy snapshot).
|
|
||||||
*
|
|
||||||
* @return required protocols
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> requiredProtocols() {
|
|
||||||
return protocolMode.required();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the TCP/TLS channel is currently connected.
|
|
||||||
*
|
|
||||||
* @return true if TCP connected
|
|
||||||
*/
|
|
||||||
public boolean isTcpConnected() {
|
|
||||||
if (closed.get()) return false;
|
|
||||||
SSLSocket s = tcpSocket;
|
|
||||||
return s != null && !s.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the UDP/DTLS channel is currently connected and bound.
|
|
||||||
*
|
|
||||||
* @return true if UDP connected
|
|
||||||
*/
|
|
||||||
public boolean isUdpConnected() {
|
|
||||||
if (closed.get()) return false;
|
|
||||||
if (!udpBound) return false;
|
|
||||||
if (udpRemote == null || dtlsEndpoint == null || udpSession == null) return false;
|
|
||||||
return udpSession.isHandshakeComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this connection satisfies the server required protocol set.
|
|
||||||
*
|
|
||||||
* @return true if fully connected according to {@link ServerProtocolMode#required()}
|
|
||||||
*/
|
|
||||||
public boolean isFullyConnected() {
|
|
||||||
EnumSet<NetworkProtocol> required = protocolMode.required();
|
|
||||||
boolean tcpOk = !required.contains(NetworkProtocol.TCP) || isTcpConnected();
|
|
||||||
boolean udpOk = !required.contains(NetworkProtocol.UDP) || isUdpConnected();
|
|
||||||
return tcpOk && udpOk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently bound UDP remote, or null.
|
|
||||||
*
|
|
||||||
* @return udp remote
|
|
||||||
*/
|
|
||||||
public SocketAddress udpRemote() {
|
|
||||||
return udpRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether UDP is bound/acknowledged.
|
|
||||||
*
|
|
||||||
* @param udpBound bound flag
|
|
||||||
*/
|
|
||||||
public void setUdpBound(boolean udpBound) {
|
|
||||||
this.udpBound = udpBound;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches the DTLS side (after bind/hello flow).
|
|
||||||
*
|
|
||||||
* @param udpRemote remote address
|
|
||||||
* @param udpSession DTLS session
|
|
||||||
* @param dtlsEndpoint DTLS endpoint
|
|
||||||
* @param udpChannel UDP channel (server channel)
|
|
||||||
*/
|
|
||||||
public void attachUdp(
|
|
||||||
SocketAddress udpRemote,
|
|
||||||
DtlsEndpoint.DtlsSession udpSession,
|
|
||||||
DtlsEndpoint dtlsEndpoint,
|
|
||||||
DatagramChannel udpChannel
|
|
||||||
) {
|
|
||||||
this.udpRemote = Objects.requireNonNull(udpRemote, "udpRemote");
|
|
||||||
this.udpSession = Objects.requireNonNull(udpSession, "udpSession");
|
|
||||||
this.dtlsEndpoint = Objects.requireNonNull(dtlsEndpoint, "dtlsEndpoint");
|
|
||||||
this.udpChannel = Objects.requireNonNull(udpChannel, "udpChannel");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a packet via the selected protocol (TCP/TLS or UDP/DTLS).
|
|
||||||
*
|
|
||||||
* <p>Emits:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link S_PacketSendEvent} on success</li>
|
|
||||||
* <li>{@link S_PacketSendFailedEvent} on failure</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param packet packet to send
|
|
||||||
* @param protocol protocol to use
|
|
||||||
* @return true if sent, false if not possible/unsupported/not ready/closed
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
*/
|
|
||||||
public boolean sendPacket(Packet packet, NetworkProtocol protocol) throws IOException {
|
|
||||||
Objects.requireNonNull(packet, "packet");
|
|
||||||
Objects.requireNonNull(protocol, "protocol");
|
|
||||||
|
|
||||||
if (closed.get()) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean ok = switch (protocol) {
|
|
||||||
case TCP -> sendTcp(packet);
|
|
||||||
case UDP -> sendUdp(packet);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
eventManager.executeEvent(new S_PacketSendEvent(packet, this, protocol));
|
|
||||||
} else {
|
|
||||||
eventManager.executeEvent(new S_PacketSendFailedEvent(
|
|
||||||
packet, this, protocol, new IllegalStateException("Packet not sent")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
|
||||||
} catch (IOException | RuntimeException e) {
|
|
||||||
eventManager.executeEvent(new S_PacketSendFailedEvent(packet, this, protocol, e));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives the next packet from the TCP stream.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
* @throws ClassNotFoundException on deserialization errors
|
|
||||||
*/
|
|
||||||
public Packet receiveTcp() throws IOException, ClassNotFoundException {
|
|
||||||
if (closed.get()) throw new IOException("Connection closed");
|
|
||||||
if (tcpIn == null) throw new IOException("TCP not available for this connection");
|
|
||||||
return codec.receiveFromStream(tcpIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the TCP output stream (nullable).
|
|
||||||
*
|
|
||||||
* @return tcp output stream or null
|
|
||||||
*/
|
|
||||||
public ObjectOutputStream tcpOut() {
|
|
||||||
return tcpOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the TCP input stream (nullable).
|
|
||||||
*
|
|
||||||
* @return tcp input stream or null
|
|
||||||
*/
|
|
||||||
public ObjectInputStream tcpIn() {
|
|
||||||
return tcpIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this connection (TCP socket if present). UDP association is cleared.
|
|
||||||
*
|
|
||||||
* @throws IOException on close errors
|
|
||||||
*/
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (!closed.compareAndSet(false, true)) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (tcpOut != null) tcpOut.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (tcpIn != null) tcpIn.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (tcpSocket != null) tcpSocket.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
udpRemote = null;
|
|
||||||
udpSession = null;
|
|
||||||
dtlsEndpoint = null;
|
|
||||||
udpChannel = null;
|
|
||||||
udpBound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendTcp(Packet packet) throws IOException {
|
|
||||||
if (!isTcpConnected()) return false;
|
|
||||||
if (tcpOut == null) return false;
|
|
||||||
codec.sendToStream(packet, tcpOut);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendUdp(Packet packet) throws IOException {
|
|
||||||
if (closed.get()) return false;
|
|
||||||
|
|
||||||
SocketAddress remote = udpRemote;
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
if (remote == null || endpoint == null) return false;
|
|
||||||
|
|
||||||
// Allow sending bind/hello/ack even if udpBound==false (handshake must still be complete).
|
|
||||||
DtlsEndpoint.DtlsSession s = udpSession;
|
|
||||||
if (s == null || !s.isHandshakeComplete()) return false;
|
|
||||||
|
|
||||||
ByteBuffer buf = codec.encodeToBuffer(packet);
|
|
||||||
endpoint.sendApplication(remote, buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,587 +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://unlegitdqrk.dev/
|
|
||||||
* 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.PacketCodec;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ConnectionIdPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.ProtocolRequirementsPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindAckPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpBindPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.impl.UdpHelloPacket;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive.S_PacketReceivedFailedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect.ClientConnectionConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect.ClientConnectionFullyConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect.ClientConnectionDisconnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect.ClientConnectionFullyDisconnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming.TCPIncomingConnectionEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming.UDPIncomingConnectionEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.DtlsEndpoint;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.SSLServerSocket;
|
|
||||||
import javax.net.ssl.SSLServerSocketFactory;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secure server supporting TCP/TLS and UDP/DTLS.
|
|
||||||
*
|
|
||||||
* <p>Listeners are started based on {@link ServerProtocolMode#required()}.</p>
|
|
||||||
* <p>Fully-connected is evaluated based on {@link ServerProtocolMode#required()}.</p>
|
|
||||||
*/
|
|
||||||
public final class NetworkServer {
|
|
||||||
|
|
||||||
private final ServerProtocolMode protocolMode;
|
|
||||||
private final ClientAuthMode clientAuthMode;
|
|
||||||
|
|
||||||
private final PacketCodec codec;
|
|
||||||
private final EventManager eventManager;
|
|
||||||
|
|
||||||
private final int tcpPort;
|
|
||||||
private final int udpPort;
|
|
||||||
|
|
||||||
private final int timeoutMillis;
|
|
||||||
private final int mtu;
|
|
||||||
|
|
||||||
private final SSLServerSocketFactory tlsServerSocketFactory;
|
|
||||||
private final SSLParameters tlsParameters; // optional
|
|
||||||
private final SSLContext dtlsContext;
|
|
||||||
|
|
||||||
private volatile SSLServerSocket tcpServerSocket;
|
|
||||||
private volatile DatagramChannel udpChannel;
|
|
||||||
private volatile DtlsEndpoint dtlsEndpoint;
|
|
||||||
|
|
||||||
private volatile Thread tcpAcceptThread;
|
|
||||||
private volatile Thread udpThread;
|
|
||||||
|
|
||||||
private final Map<UUID, ClientConnection> connectionsById = new ConcurrentHashMap<>();
|
|
||||||
private final Map<SocketAddress, ClientConnection> connectionsByUdpRemote = new ConcurrentHashMap<>();
|
|
||||||
private final List<ClientConnection> connections = java.util.Collections.synchronizedList(new ArrayList<>());
|
|
||||||
|
|
||||||
private NetworkServer(
|
|
||||||
ServerProtocolMode protocolMode,
|
|
||||||
ClientAuthMode clientAuthMode,
|
|
||||||
PacketCodec codec,
|
|
||||||
EventManager eventManager,
|
|
||||||
int tcpPort,
|
|
||||||
int udpPort,
|
|
||||||
int timeoutMillis,
|
|
||||||
int mtu,
|
|
||||||
SSLServerSocketFactory tlsServerSocketFactory,
|
|
||||||
SSLParameters tlsParameters,
|
|
||||||
SSLContext dtlsContext
|
|
||||||
) {
|
|
||||||
this.protocolMode = Objects.requireNonNull(protocolMode, "protocolMode");
|
|
||||||
this.clientAuthMode = Objects.requireNonNull(clientAuthMode, "clientAuthMode");
|
|
||||||
this.codec = Objects.requireNonNull(codec, "codec");
|
|
||||||
this.eventManager = Objects.requireNonNull(eventManager, "eventManager");
|
|
||||||
this.tcpPort = tcpPort;
|
|
||||||
this.udpPort = udpPort;
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
this.mtu = mtu;
|
|
||||||
this.tlsServerSocketFactory = Objects.requireNonNull(tlsServerSocketFactory, "tlsServerSocketFactory");
|
|
||||||
this.tlsParameters = tlsParameters;
|
|
||||||
this.dtlsContext = Objects.requireNonNull(dtlsContext, "dtlsContext");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the server.
|
|
||||||
*
|
|
||||||
* @return true if started, false if already started
|
|
||||||
* @throws Exception on startup errors
|
|
||||||
*/
|
|
||||||
public synchronized boolean start() throws Exception {
|
|
||||||
if (tcpAcceptThread != null || udpThread != null) return false;
|
|
||||||
|
|
||||||
// FIX: only required exists -> listener start is based on required()
|
|
||||||
EnumSet<NetworkProtocol> required = protocolMode.required();
|
|
||||||
|
|
||||||
if (required.contains(NetworkProtocol.TCP)) {
|
|
||||||
if (tcpPort <= 0) throw new IllegalStateException("tcpPort not set (TCP required)");
|
|
||||||
startTcp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.contains(NetworkProtocol.UDP)) {
|
|
||||||
if (udpPort <= 0) throw new IllegalStateException("udpPort not set (UDP required)");
|
|
||||||
startUdp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized boolean stop() {
|
|
||||||
boolean wasRunning = (tcpAcceptThread != null) || (udpThread != null);
|
|
||||||
|
|
||||||
Thread t1 = tcpAcceptThread;
|
|
||||||
Thread t2 = udpThread;
|
|
||||||
if (t1 != null) t1.interrupt();
|
|
||||||
if (t2 != null) t2.interrupt();
|
|
||||||
|
|
||||||
tcpAcceptThread = null;
|
|
||||||
udpThread = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (tcpServerSocket != null) tcpServerSocket.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
} finally {
|
|
||||||
tcpServerSocket = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (udpChannel != null) udpChannel.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
} finally {
|
|
||||||
udpChannel = null;
|
|
||||||
dtlsEndpoint = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnumSet<NetworkProtocol> required = protocolMode.required();
|
|
||||||
|
|
||||||
for (ClientConnection c : new ArrayList<>(connections)) {
|
|
||||||
boolean wasFully = isFullyConnected(c);
|
|
||||||
|
|
||||||
boolean tcpWas = isConnected(c, NetworkProtocol.TCP);
|
|
||||||
boolean udpWas = isConnected(c, NetworkProtocol.UDP);
|
|
||||||
|
|
||||||
SocketAddress udpRemote = c.udpRemote();
|
|
||||||
|
|
||||||
try {
|
|
||||||
c.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (udpRemote != null) {
|
|
||||||
connectionsByUdpRemote.remove(udpRemote);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tcpWas) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionDisconnectedEvent(c, NetworkProtocol.TCP));
|
|
||||||
}
|
|
||||||
if (udpWas) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionDisconnectedEvent(c, NetworkProtocol.UDP));
|
|
||||||
}
|
|
||||||
if (wasFully) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionFullyDisconnectedEvent(c, required));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connections.clear();
|
|
||||||
connectionsById.clear();
|
|
||||||
connectionsByUdpRemote.clear();
|
|
||||||
|
|
||||||
return wasRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerProtocolMode protocolMode() {
|
|
||||||
return protocolMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnected(ClientConnection connection, NetworkProtocol protocol) {
|
|
||||||
Objects.requireNonNull(connection, "connection");
|
|
||||||
Objects.requireNonNull(protocol, "protocol");
|
|
||||||
|
|
||||||
return switch (protocol) {
|
|
||||||
case TCP -> connection.isTcpConnected();
|
|
||||||
case UDP -> connection.isUdpConnected();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFullyConnected(ClientConnection connection) {
|
|
||||||
Objects.requireNonNull(connection, "connection");
|
|
||||||
return connection.isFullyConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ClientConnection> connections() {
|
|
||||||
synchronized (connections) {
|
|
||||||
return List.copyOf(connections);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startTcp() throws Exception {
|
|
||||||
SSLServerSocket serverSocket = (SSLServerSocket) tlsServerSocketFactory.createServerSocket();
|
|
||||||
serverSocket.bind(new InetSocketAddress(tcpPort));
|
|
||||||
serverSocket.setSoTimeout(timeoutMillis);
|
|
||||||
|
|
||||||
if (clientAuthMode == ClientAuthMode.REQUIRED) {
|
|
||||||
serverSocket.setNeedClientAuth(true);
|
|
||||||
} else {
|
|
||||||
serverSocket.setWantClientAuth(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tlsParameters != null) {
|
|
||||||
serverSocket.setSSLParameters(tlsParameters);
|
|
||||||
} else {
|
|
||||||
serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tcpServerSocket = serverSocket;
|
|
||||||
|
|
||||||
this.tcpAcceptThread = new Thread(this::tcpAcceptLoop, "SecureNetworkServer-TCP-Accept");
|
|
||||||
this.tcpAcceptThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startUdp() throws Exception {
|
|
||||||
DatagramChannel ch = DatagramChannel.open();
|
|
||||||
ch.bind(new InetSocketAddress(udpPort));
|
|
||||||
ch.configureBlocking(false);
|
|
||||||
|
|
||||||
DtlsEndpoint endpoint = new DtlsEndpoint(
|
|
||||||
ch,
|
|
||||||
dtlsContext,
|
|
||||||
false,
|
|
||||||
mtu,
|
|
||||||
timeoutMillis,
|
|
||||||
clientAuthMode,
|
|
||||||
this::onDtlsApplicationData
|
|
||||||
);
|
|
||||||
|
|
||||||
this.udpChannel = ch;
|
|
||||||
this.dtlsEndpoint = endpoint;
|
|
||||||
|
|
||||||
this.udpThread = new Thread(this::udpLoop, "SecureNetworkServer-UDP-DTLS");
|
|
||||||
this.udpThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tcpAcceptLoop() {
|
|
||||||
try {
|
|
||||||
while (tcpServerSocket != null && !tcpServerSocket.isClosed() && !Thread.currentThread().isInterrupted()) {
|
|
||||||
SSLSocket socket = (SSLSocket) tcpServerSocket.accept();
|
|
||||||
socket.setTcpNoDelay(true);
|
|
||||||
socket.setSoTimeout(timeoutMillis);
|
|
||||||
|
|
||||||
TCPIncomingConnectionEvent incoming = new TCPIncomingConnectionEvent(this, socket);
|
|
||||||
eventManager.executeEvent(incoming);
|
|
||||||
if (incoming.isCancelled()) {
|
|
||||||
safeClose(socket);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
socket.startHandshake();
|
|
||||||
} catch (Exception e) {
|
|
||||||
safeClose(socket);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
|
||||||
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
ClientConnection connection = ClientConnection.tcpAccepted(codec, protocolMode, eventManager, socket, out, in);
|
|
||||||
|
|
||||||
connections.add(connection);
|
|
||||||
connectionsById.put(connection.connectionId(), connection);
|
|
||||||
|
|
||||||
eventManager.executeEvent(new ClientConnectionConnectedEvent(connection, NetworkProtocol.TCP));
|
|
||||||
|
|
||||||
codec.sendToStream(new ConnectionIdPacket(connection.connectionId()), connection.tcpOut());
|
|
||||||
codec.sendToStream(new ProtocolRequirementsPacket(protocolMode.required()), connection.tcpOut());
|
|
||||||
|
|
||||||
if (isFullyConnected(connection)) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionFullyConnectedEvent(connection, protocolMode.required()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread rx = new Thread(() -> tcpReceiveLoop(connection),
|
|
||||||
"SecureNetworkServer-TCP-Rx-" + connection.connectionId());
|
|
||||||
rx.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
safeClose(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tcpReceiveLoop(ClientConnection connection) {
|
|
||||||
boolean wasFully = isFullyConnected(connection);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!Thread.currentThread().isInterrupted() && connection.isTcpConnected()) {
|
|
||||||
Packet packet = codec.receiveFromStream(connection.tcpIn());
|
|
||||||
if (packet == null) continue;
|
|
||||||
|
|
||||||
eventManager.executeEvent(new S_PacketReceivedEvent(connection, packet, NetworkProtocol.TCP));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
eventManager.executeEvent(new S_PacketReceivedFailedEvent(connection, null, NetworkProtocol.TCP, e));
|
|
||||||
} finally {
|
|
||||||
cleanupConnection(connection, wasFully);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void udpLoop() {
|
|
||||||
try {
|
|
||||||
while (udpChannel != null && udpChannel.isOpen() && !Thread.currentThread().isInterrupted()) {
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
if (endpoint != null) endpoint.poll();
|
|
||||||
Thread.onSpinWait();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDtlsApplicationData(SocketAddress remote, ByteBuffer data) {
|
|
||||||
UDPIncomingConnectionEvent incoming = new UDPIncomingConnectionEvent(this, remote, data.asReadOnlyBuffer());
|
|
||||||
eventManager.executeEvent(incoming);
|
|
||||||
if (incoming.isCancelled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Packet packet = codec.decodeFromBuffer(data);
|
|
||||||
|
|
||||||
if (packet instanceof UdpHelloPacket) {
|
|
||||||
handleUdpHello(remote);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet instanceof UdpBindPacket bind) {
|
|
||||||
handleUdpBind(remote, bind);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientConnection conn = connectionsByUdpRemote.get(remote);
|
|
||||||
if (conn != null) {
|
|
||||||
eventManager.executeEvent(new S_PacketReceivedEvent(conn, packet, NetworkProtocol.UDP));
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUdpHello(SocketAddress remote) {
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
DatagramChannel ch = udpChannel;
|
|
||||||
if (endpoint == null || ch == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
endpoint.handshake(remote);
|
|
||||||
|
|
||||||
UUID id = UUID.randomUUID();
|
|
||||||
ClientConnection conn = ClientConnection.udpOnly(codec, protocolMode, eventManager, id);
|
|
||||||
|
|
||||||
boolean wasFullyBefore = isFullyConnected(conn);
|
|
||||||
|
|
||||||
conn.attachUdp(remote, endpoint.session(remote), endpoint, ch);
|
|
||||||
conn.setUdpBound(true);
|
|
||||||
|
|
||||||
connections.add(conn);
|
|
||||||
connectionsById.put(conn.connectionId(), conn);
|
|
||||||
connectionsByUdpRemote.put(remote, conn);
|
|
||||||
|
|
||||||
eventManager.executeEvent(new ClientConnectionConnectedEvent(conn, NetworkProtocol.UDP));
|
|
||||||
|
|
||||||
conn.sendPacket(new ConnectionIdPacket(conn.connectionId()), NetworkProtocol.UDP);
|
|
||||||
conn.sendPacket(new ProtocolRequirementsPacket(protocolMode.required()), NetworkProtocol.UDP);
|
|
||||||
|
|
||||||
boolean fullyNow = isFullyConnected(conn);
|
|
||||||
if (!wasFullyBefore && fullyNow) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionFullyConnectedEvent(conn, protocolMode.required()));
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUdpBind(SocketAddress remote, UdpBindPacket bind) {
|
|
||||||
ClientConnection conn = connectionsById.get(bind.connectionId());
|
|
||||||
if (conn == null) return;
|
|
||||||
|
|
||||||
DtlsEndpoint endpoint = dtlsEndpoint;
|
|
||||||
DatagramChannel ch = udpChannel;
|
|
||||||
if (endpoint == null || ch == null) return;
|
|
||||||
|
|
||||||
boolean wasFullyBefore = isFullyConnected(conn);
|
|
||||||
|
|
||||||
try {
|
|
||||||
endpoint.handshake(remote);
|
|
||||||
|
|
||||||
conn.attachUdp(remote, endpoint.session(remote), endpoint, ch);
|
|
||||||
conn.setUdpBound(true);
|
|
||||||
|
|
||||||
connectionsByUdpRemote.put(remote, conn);
|
|
||||||
|
|
||||||
eventManager.executeEvent(new ClientConnectionConnectedEvent(conn, NetworkProtocol.UDP));
|
|
||||||
|
|
||||||
conn.sendPacket(new UdpBindAckPacket(conn.connectionId()), NetworkProtocol.UDP);
|
|
||||||
|
|
||||||
boolean fullyNow = isFullyConnected(conn);
|
|
||||||
if (!wasFullyBefore && fullyNow) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionFullyConnectedEvent(conn, protocolMode.required()));
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanupConnection(ClientConnection connection, boolean wasFully) {
|
|
||||||
boolean tcpWas = connection.isTcpConnected();
|
|
||||||
boolean udpWas = connection.isUdpConnected();
|
|
||||||
|
|
||||||
SocketAddress udpRemote = connection.udpRemote();
|
|
||||||
|
|
||||||
try {
|
|
||||||
connection.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
connections.remove(connection);
|
|
||||||
connectionsById.remove(connection.connectionId());
|
|
||||||
|
|
||||||
if (udpRemote != null) {
|
|
||||||
connectionsByUdpRemote.remove(udpRemote);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tcpWas) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionDisconnectedEvent(connection, NetworkProtocol.TCP));
|
|
||||||
}
|
|
||||||
if (udpWas) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionDisconnectedEvent(connection, NetworkProtocol.UDP));
|
|
||||||
}
|
|
||||||
if (wasFully) {
|
|
||||||
eventManager.executeEvent(new ClientConnectionFullyDisconnectedEvent(connection, protocolMode.required()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void safeClose(SSLSocket socket) {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
|
|
||||||
private ServerProtocolMode protocolMode = ServerProtocolMode.bothRequired();
|
|
||||||
private ClientAuthMode clientAuthMode = ClientAuthMode.OPTIONAL;
|
|
||||||
|
|
||||||
private PacketCodec codec;
|
|
||||||
private EventManager eventManager;
|
|
||||||
|
|
||||||
private int tcpPort;
|
|
||||||
private int udpPort;
|
|
||||||
|
|
||||||
private int timeoutMillis = 5000;
|
|
||||||
private int mtu = 1400;
|
|
||||||
|
|
||||||
private SSLServerSocketFactory tlsServerSocketFactory;
|
|
||||||
private SSLParameters tlsParameters;
|
|
||||||
private SSLContext dtlsContext;
|
|
||||||
|
|
||||||
public Builder setProtocolMode(ServerProtocolMode protocolMode) {
|
|
||||||
this.protocolMode = protocolMode;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setClientAuthMode(ClientAuthMode clientAuthMode) {
|
|
||||||
this.clientAuthMode = clientAuthMode;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setCodec(PacketCodec codec) {
|
|
||||||
this.codec = codec;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setEventManager(EventManager eventManager) {
|
|
||||||
this.eventManager = eventManager;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTcpPort(int tcpPort) {
|
|
||||||
this.tcpPort = tcpPort;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setUdpPort(int udpPort) {
|
|
||||||
this.udpPort = udpPort;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTimeoutMillis(int timeoutMillis) {
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setMtu(int mtu) {
|
|
||||||
this.mtu = mtu;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTlsServerSocketFactory(SSLServerSocketFactory tlsServerSocketFactory) {
|
|
||||||
this.tlsServerSocketFactory = tlsServerSocketFactory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTlsParameters(SSLParameters tlsParameters) {
|
|
||||||
this.tlsParameters = tlsParameters;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setDtlsContext(SSLContext dtlsContext) {
|
|
||||||
this.dtlsContext = dtlsContext;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkServer build() {
|
|
||||||
if (codec == null) throw new IllegalStateException("codec not set");
|
|
||||||
if (eventManager == null) throw new IllegalStateException("eventManager not set");
|
|
||||||
if (protocolMode == null) throw new IllegalStateException("protocolMode not set");
|
|
||||||
if (clientAuthMode == null) throw new IllegalStateException("clientAuthMode not set");
|
|
||||||
|
|
||||||
SSLServerSocketFactory tcpFactory = tlsServerSocketFactory != null
|
|
||||||
? tlsServerSocketFactory
|
|
||||||
: (SSLServerSocketFactory) ServerSocketFactory.getDefault();
|
|
||||||
|
|
||||||
SSLContext dtls = dtlsContext != null ? dtlsContext : emptyDtls();
|
|
||||||
|
|
||||||
if (timeoutMillis <= 0) throw new IllegalStateException("timeoutMillis must be > 0");
|
|
||||||
if (mtu < 256) throw new IllegalStateException("mtu too small");
|
|
||||||
|
|
||||||
return new NetworkServer(
|
|
||||||
protocolMode,
|
|
||||||
clientAuthMode,
|
|
||||||
codec,
|
|
||||||
eventManager,
|
|
||||||
tcpPort,
|
|
||||||
udpPort,
|
|
||||||
timeoutMillis,
|
|
||||||
mtu,
|
|
||||||
tcpFactory,
|
|
||||||
tlsParameters,
|
|
||||||
dtls
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SSLContext emptyDtls() {
|
|
||||||
try {
|
|
||||||
return SSLContext.getInstance("DTLS");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Failed to create DTLS context", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines which network protocols a server requires for a connection to be considered usable.
|
|
||||||
*
|
|
||||||
* <p><strong>Note:</strong> This model exposes only {@link #required()}.
|
|
||||||
* Therefore, "optional protocols" are not representable here. If you need optional transports,
|
|
||||||
* the model must include a separate supported-set.</p>
|
|
||||||
*/
|
|
||||||
public final class ServerProtocolMode {
|
|
||||||
|
|
||||||
private final EnumSet<NetworkProtocol> required;
|
|
||||||
|
|
||||||
private ServerProtocolMode(EnumSet<NetworkProtocol> required) {
|
|
||||||
this.required = EnumSet.copyOf(Objects.requireNonNull(required, "required"));
|
|
||||||
if (this.required.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("required must not be empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns required protocols.
|
|
||||||
*
|
|
||||||
* @return required protocols
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> required() {
|
|
||||||
return EnumSet.copyOf(required);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server requires TCP only.
|
|
||||||
*
|
|
||||||
* @return mode
|
|
||||||
*/
|
|
||||||
public static ServerProtocolMode tcpOnly() {
|
|
||||||
return new ServerProtocolMode(EnumSet.of(NetworkProtocol.TCP));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server requires UDP only.
|
|
||||||
*
|
|
||||||
* @return mode
|
|
||||||
*/
|
|
||||||
public static ServerProtocolMode udpOnly() {
|
|
||||||
return new ServerProtocolMode(EnumSet.of(NetworkProtocol.UDP));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server requires both TCP and UDP.
|
|
||||||
*
|
|
||||||
* @return mode
|
|
||||||
*/
|
|
||||||
public static ServerProtocolMode bothRequired() {
|
|
||||||
return new ServerProtocolMode(EnumSet.of(NetworkProtocol.TCP, NetworkProtocol.UDP));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet was received on the server on a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class S_PacketReceivedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet received event.
|
|
||||||
*
|
|
||||||
* @param connection connection
|
|
||||||
* @param packet packet
|
|
||||||
* @param protocol protocol used
|
|
||||||
*/
|
|
||||||
public S_PacketReceivedEvent(ClientConnection connection, Packet packet, NetworkProtocol protocol) {
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol the packet was received on.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when receiving or decoding a packet failed on the server for a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class S_PacketReceivedFailedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final Packet packet;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
private final Exception error;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet receive failed event.
|
|
||||||
*
|
|
||||||
* @param connection connection (may be null if not attributable)
|
|
||||||
* @param packet packet that failed to be processed (may be null if undecodable)
|
|
||||||
* @param protocol protocol the failure happened on
|
|
||||||
* @param error root cause
|
|
||||||
*/
|
|
||||||
public S_PacketReceivedFailedEvent(
|
|
||||||
ClientConnection connection,
|
|
||||||
Packet packet,
|
|
||||||
NetworkProtocol protocol,
|
|
||||||
Exception error
|
|
||||||
) {
|
|
||||||
this.connection = connection; // may be null
|
|
||||||
this.packet = packet; // may be null
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
this.error = Objects.requireNonNull(error, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection, if attributable.
|
|
||||||
*
|
|
||||||
* @return connection or null
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet that failed to be processed, if available.
|
|
||||||
*
|
|
||||||
* @return packet or null
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol the failure occurred on.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying error.
|
|
||||||
*
|
|
||||||
* @return error
|
|
||||||
*/
|
|
||||||
public Exception getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.receive;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when an unknown (non-packet) object was received on a specific protocol.
|
|
||||||
*
|
|
||||||
* <p>In v2 the default transport uses {@link dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketCodec}
|
|
||||||
* and usually yields {@link Packet}. This event is kept
|
|
||||||
* for custom/legacy decoding paths.</p>
|
|
||||||
*/
|
|
||||||
public final class S_UnknownObjectReceivedEvent extends Event {
|
|
||||||
|
|
||||||
private final Object received;
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new event.
|
|
||||||
*
|
|
||||||
* @param received received object
|
|
||||||
* @param connection connection (may be null if not attributable)
|
|
||||||
* @param protocol protocol
|
|
||||||
*/
|
|
||||||
public S_UnknownObjectReceivedEvent(Object received, ClientConnection connection, NetworkProtocol protocol) {
|
|
||||||
this.received = Objects.requireNonNull(received, "received");
|
|
||||||
this.connection = connection; // may be null
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection, if attributable.
|
|
||||||
*
|
|
||||||
* @return connection or null
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the received object.
|
|
||||||
*
|
|
||||||
* @return received object
|
|
||||||
*/
|
|
||||||
public Object getReceived() {
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet was successfully sent by the server on a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class S_PacketSendEvent extends Event {
|
|
||||||
|
|
||||||
private final Packet packet;
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet send event.
|
|
||||||
*
|
|
||||||
* @param packet packet
|
|
||||||
* @param connection connection
|
|
||||||
* @param protocol protocol used
|
|
||||||
*/
|
|
||||||
public S_PacketSendEvent(Packet packet, ClientConnection connection, NetworkProtocol protocol) {
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol used.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.send;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a packet send failed on the server on a specific protocol.
|
|
||||||
*/
|
|
||||||
public final class S_PacketSendFailedEvent extends Event {
|
|
||||||
|
|
||||||
private final Packet packet;
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
private final Exception error;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new packet send failed event.
|
|
||||||
*
|
|
||||||
* @param packet packet that failed to be sent
|
|
||||||
* @param connection connection
|
|
||||||
* @param protocol intended protocol
|
|
||||||
* @param error root cause
|
|
||||||
*/
|
|
||||||
public S_PacketSendFailedEvent(
|
|
||||||
Packet packet,
|
|
||||||
ClientConnection connection,
|
|
||||||
NetworkProtocol protocol,
|
|
||||||
Exception error
|
|
||||||
) {
|
|
||||||
this.packet = Objects.requireNonNull(packet, "packet");
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
this.error = Objects.requireNonNull(error, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet.
|
|
||||||
*
|
|
||||||
* @return packet
|
|
||||||
*/
|
|
||||||
public Packet getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the intended protocol.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying error.
|
|
||||||
*
|
|
||||||
* @return error
|
|
||||||
*/
|
|
||||||
public Exception getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a specific protocol becomes connected for a server-side connection.
|
|
||||||
*
|
|
||||||
* <p>Protocol-specific:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link NetworkProtocol#TCP}: connection created after TLS handshake</li>
|
|
||||||
* <li>{@link NetworkProtocol#UDP}: connection received valid UDP bind and attached DTLS session</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClientConnectionConnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new connection connected event.
|
|
||||||
*
|
|
||||||
* @param connection connection
|
|
||||||
* @param protocol connected protocol
|
|
||||||
*/
|
|
||||||
public ClientConnectionConnectedEvent(ClientConnection connection, NetworkProtocol protocol) {
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol that was connected.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.connect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a connection satisfies the server protocol requirements.
|
|
||||||
*
|
|
||||||
* <p>In v2 this typically means:
|
|
||||||
* <ul>
|
|
||||||
* <li>TCP is connected (TLS ok)</li>
|
|
||||||
* <li>and if UDP is enabled: DTLS is established and the bind flow is completed</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClientConnectionFullyConnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final EnumSet<NetworkProtocol> requiredProtocols;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new event.
|
|
||||||
*
|
|
||||||
* @param connection connection
|
|
||||||
* @param requiredProtocols required protocols now established
|
|
||||||
*/
|
|
||||||
public ClientConnectionFullyConnectedEvent(ClientConnection connection, EnumSet<NetworkProtocol> requiredProtocols) {
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.requiredProtocols = EnumSet.copyOf(Objects.requireNonNull(requiredProtocols, "requiredProtocols"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the required protocols that are now established.
|
|
||||||
*
|
|
||||||
* @return required protocols (copy)
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> getRequiredProtocols() {
|
|
||||||
return EnumSet.copyOf(requiredProtocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a specific protocol becomes disconnected for a server-side connection.
|
|
||||||
*/
|
|
||||||
public final class ClientConnectionDisconnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final NetworkProtocol protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new connection disconnected event.
|
|
||||||
*
|
|
||||||
* @param connection connection
|
|
||||||
* @param protocol disconnected protocol
|
|
||||||
*/
|
|
||||||
public ClientConnectionDisconnectedEvent(ClientConnection connection, NetworkProtocol protocol) {
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.protocol = Objects.requireNonNull(protocol, "protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol that was disconnected.
|
|
||||||
*
|
|
||||||
* @return protocol
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.disconnect;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ClientConnection;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when a connection was fully connected and then became fully disconnected
|
|
||||||
* (i.e., it no longer satisfies the required protocols).
|
|
||||||
*/
|
|
||||||
public final class ClientConnectionFullyDisconnectedEvent extends Event {
|
|
||||||
|
|
||||||
private final ClientConnection connection;
|
|
||||||
private final EnumSet<NetworkProtocol> requiredProtocols;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new fully disconnected event.
|
|
||||||
*
|
|
||||||
* @param connection connection
|
|
||||||
* @param requiredProtocols required protocols according to policy
|
|
||||||
*/
|
|
||||||
public ClientConnectionFullyDisconnectedEvent(ClientConnection connection, EnumSet<NetworkProtocol> requiredProtocols) {
|
|
||||||
this.connection = Objects.requireNonNull(connection, "connection");
|
|
||||||
this.requiredProtocols = EnumSet.copyOf(Objects.requireNonNull(requiredProtocols, "requiredProtocols"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the connection.
|
|
||||||
*
|
|
||||||
* @return connection
|
|
||||||
*/
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocols that were required for full connectivity.
|
|
||||||
*
|
|
||||||
* @return required protocols (copy)
|
|
||||||
*/
|
|
||||||
public EnumSet<NetworkProtocol> getRequiredProtocols() {
|
|
||||||
return EnumSet.copyOf(requiredProtocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.CancellableEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when an incoming TCP/TLS connection attempt reaches the server.
|
|
||||||
*
|
|
||||||
* <p>This event is emitted for TCP/TLS accept only. UDP/DTLS is bound later via the bind flow.</p>
|
|
||||||
*/
|
|
||||||
public final class TCPIncomingConnectionEvent extends CancellableEvent {
|
|
||||||
|
|
||||||
private final NetworkServer server;
|
|
||||||
private final SSLSocket socket;
|
|
||||||
private final NetworkProtocol protocol = NetworkProtocol.TCP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new incoming connection event.
|
|
||||||
*
|
|
||||||
* @param server server instance
|
|
||||||
* @param socket accepted SSL socket
|
|
||||||
*/
|
|
||||||
public TCPIncomingConnectionEvent(NetworkServer server, SSLSocket socket) {
|
|
||||||
this.server = Objects.requireNonNull(server, "server");
|
|
||||||
this.socket = Objects.requireNonNull(socket, "socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the accepted socket.
|
|
||||||
*
|
|
||||||
* @return ssl socket
|
|
||||||
*/
|
|
||||||
public SSLSocket getSocket() {
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server instance.
|
|
||||||
*
|
|
||||||
* @return server
|
|
||||||
*/
|
|
||||||
public NetworkServer getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol associated with this incoming connection.
|
|
||||||
*
|
|
||||||
* @return {@link NetworkProtocol#TCP}
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.incoming;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.CancellableEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.NetworkProtocol;
|
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when an incoming UDP datagram is received by the server before it is bound to a connection.
|
|
||||||
*
|
|
||||||
* <p>This event allows inspection or rejection of:
|
|
||||||
* <ul>
|
|
||||||
* <li>DTLS handshake traffic</li>
|
|
||||||
* <li>bind packets</li>
|
|
||||||
* <li>any unbound UDP packet</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>If cancelled, the datagram is silently dropped.</p>
|
|
||||||
*/
|
|
||||||
public final class UDPIncomingConnectionEvent extends CancellableEvent {
|
|
||||||
|
|
||||||
private final NetworkServer server;
|
|
||||||
private final SocketAddress remoteAddress;
|
|
||||||
private final ByteBuffer rawData;
|
|
||||||
private final NetworkProtocol protocol = NetworkProtocol.UDP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new incoming UDP datagram event.
|
|
||||||
*
|
|
||||||
* @param server server instance
|
|
||||||
* @param remoteAddress remote UDP address
|
|
||||||
* @param rawData raw received datagram (a read-only copy will be stored)
|
|
||||||
*/
|
|
||||||
public UDPIncomingConnectionEvent(
|
|
||||||
NetworkServer server,
|
|
||||||
SocketAddress remoteAddress,
|
|
||||||
ByteBuffer rawData
|
|
||||||
) {
|
|
||||||
this.server = Objects.requireNonNull(server, "server");
|
|
||||||
this.remoteAddress = Objects.requireNonNull(remoteAddress, "remoteAddress");
|
|
||||||
this.rawData = Objects.requireNonNull(rawData, "rawData").asReadOnlyBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server instance.
|
|
||||||
*
|
|
||||||
* @return server
|
|
||||||
*/
|
|
||||||
public NetworkServer getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the remote UDP address.
|
|
||||||
*
|
|
||||||
* @return remote address
|
|
||||||
*/
|
|
||||||
public SocketAddress getRemoteAddress() {
|
|
||||||
return remoteAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the raw UDP datagram payload.
|
|
||||||
*
|
|
||||||
* <p>The buffer is read-only.</p>
|
|
||||||
*
|
|
||||||
* @return raw datagram data
|
|
||||||
*/
|
|
||||||
public ByteBuffer getRawData() {
|
|
||||||
return rawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the protocol type of this incoming datagram.
|
|
||||||
*
|
|
||||||
* @return {@link NetworkProtocol#UDP}
|
|
||||||
*/
|
|
||||||
public NetworkProtocol getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* 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.*;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class ConnectedClient {
|
||||||
|
|
||||||
|
private volatile UUID uniqueID;
|
||||||
|
private Socket tcpSocket;
|
||||||
|
|
||||||
|
private DataInputStream inputStream;
|
||||||
|
private DataOutputStream outputStream;
|
||||||
|
|
||||||
|
private DatagramChannel udpChannel; // für serverseitiges Senden
|
||||||
|
private final Thread tcpReceiveThread;
|
||||||
|
private final NetworkServer server;
|
||||||
|
|
||||||
|
private int udpPort = -1;
|
||||||
|
|
||||||
|
public Socket getTcpSocket() {
|
||||||
|
return tcpSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
tcpReceiveThread.start();
|
||||||
|
|
||||||
|
server.getConnectedClients().add(this);
|
||||||
|
|
||||||
|
outputStream.writeUTF(uniqueID.toString());
|
||||||
|
outputStream.writeInt(-1);
|
||||||
|
outputStream.writeInt(udpPort);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUdpChannel(DatagramChannel channel) {
|
||||||
|
this.udpChannel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacket(Packet packet, TransportProtocol protocol) throws IOException {
|
||||||
|
if (protocol == TransportProtocol.UDP) {
|
||||||
|
sendPacketUDP(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocol == TransportProtocol.TCP) {
|
||||||
|
sendPacketTCP(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPacketTCP(Packet packet) throws IOException {
|
||||||
|
if (!isTCPConnected()) return;
|
||||||
|
server.getPacketHandler().sendPacket(outputStream, packet, uniqueID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPacketUDP(Packet packet) throws IOException {
|
||||||
|
if (!isUDPConnected()) return;
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(baos);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
udpChannel.send(buffer, clientAddress); // korrekt an Client senden
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void tcpReceive() {
|
||||||
|
try {
|
||||||
|
while (!Thread.currentThread().isInterrupted() && tcpSocket.isConnected() && tcpSocket.isBound()) {
|
||||||
|
UUID uuid = UUID.fromString(inputStream.readUTF());
|
||||||
|
int packetId = inputStream.readInt();
|
||||||
|
|
||||||
|
if (!server.getPacketHandler().readPacket(inputStream, uuid, packetId)) {
|
||||||
|
// TODO: UnknownPacketReceivedEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
tcpReceiveThread.interrupt();
|
||||||
|
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
tcpSocket = null;
|
||||||
|
inputStream = null;
|
||||||
|
outputStream = null;
|
||||||
|
udpPort = -1;
|
||||||
|
uniqueID = null;
|
||||||
|
|
||||||
|
server.getConnectedClients().remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUniqueID() {
|
||||||
|
return uniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUdpPort() {
|
||||||
|
return udpPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,30 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines whether the server requires client certificates during the TLS handshake.
|
|
||||||
*
|
|
||||||
* <p>{@link #REQUIRED} enforces mutual TLS (mTLS): clients must present a certificate.</p>
|
|
||||||
*
|
|
||||||
* <p>{@link #OPTIONAL} allows clients without a certificate to connect (server will request a certificate,
|
|
||||||
* but does not fail the handshake if none is provided).</p>
|
|
||||||
*/
|
|
||||||
public enum ClientAuthMode {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client certificate is mandatory (mTLS).
|
|
||||||
*/
|
|
||||||
REQUIRED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client certificate is optional.
|
|
||||||
*/
|
|
||||||
OPTIONAL
|
|
||||||
}
|
|
||||||
@@ -1,420 +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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
import javax.net.ssl.SSLEngineResult;
|
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal DTLS endpoint using {@link SSLEngine} over a datagram transport.
|
|
||||||
*
|
|
||||||
* <p>This implementation is designed for "best effort" UDP and focuses on:
|
|
||||||
* <ul>
|
|
||||||
* <li>DTLS handshake via SSLEngine</li>
|
|
||||||
* <li>wrap/unwrap application data into datagrams</li>
|
|
||||||
* <li>per-remote address sessions</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p><strong>Server mode note:</strong> A newly created DTLS session must enter handshake
|
|
||||||
* ({@link SSLEngine#beginHandshake()}) immediately, otherwise initial client handshake datagrams
|
|
||||||
* may not be processed and the client may time out.</p>
|
|
||||||
*
|
|
||||||
* <p><strong>Client certificate policy (server-side):</strong>
|
|
||||||
* For {@link ClientAuthMode#REQUIRED} the presence of a peer certificate is enforced after handshake
|
|
||||||
* completion (via {@link SSLEngine#getSession()} and {@code getPeerCertificates()}), because
|
|
||||||
* {@link SSLParameters#setNeedClientAuth(boolean)} is not reliably enforced for {@link SSLEngine} across providers.</p>
|
|
||||||
*/
|
|
||||||
public final class DtlsEndpoint {
|
|
||||||
|
|
||||||
private static final ByteBuffer EMPTY = ByteBuffer.allocate(0);
|
|
||||||
|
|
||||||
private final java.nio.channels.DatagramChannel channel;
|
|
||||||
private final SSLContext sslContext;
|
|
||||||
private final boolean clientMode;
|
|
||||||
private final int mtu;
|
|
||||||
private final int timeoutMillis;
|
|
||||||
private final ApplicationDataHandler appHandler;
|
|
||||||
private final ClientAuthMode clientAuthMode;
|
|
||||||
|
|
||||||
private final Map<SocketAddress, DtlsSession> sessions = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a DTLS endpoint.
|
|
||||||
*
|
|
||||||
* @param channel underlying datagram channel (bound for server, connected or unconnected for client)
|
|
||||||
* @param sslContext DTLS SSL context (created with "DTLS")
|
|
||||||
* @param clientMode true for client sessions, false for server sessions
|
|
||||||
* @param mtu maximum datagram size
|
|
||||||
* @param timeoutMillis handshake/read timeout for polling
|
|
||||||
* @param clientAuthMode server-side client auth policy (OPTIONAL/REQUIRED). Ignored for clientMode=true.
|
|
||||||
* @param appHandler application data handler
|
|
||||||
*/
|
|
||||||
public DtlsEndpoint(
|
|
||||||
DatagramChannel channel,
|
|
||||||
SSLContext sslContext,
|
|
||||||
boolean clientMode,
|
|
||||||
int mtu,
|
|
||||||
int timeoutMillis,
|
|
||||||
ClientAuthMode clientAuthMode,
|
|
||||||
ApplicationDataHandler appHandler
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.channel = Objects.requireNonNull(channel, "channel");
|
|
||||||
this.sslContext = Objects.requireNonNull(sslContext, "sslContext");
|
|
||||||
this.clientMode = clientMode;
|
|
||||||
this.mtu = mtu;
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
this.clientAuthMode = Objects.requireNonNull(clientAuthMode, "clientAuthMode");
|
|
||||||
this.appHandler = Objects.requireNonNull(appHandler, "appHandler");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a DTLS {@link SSLContext} from an existing key+trust configuration.
|
|
||||||
*
|
|
||||||
* @param keyManagers key managers (nullable)
|
|
||||||
* @param trustManagers trust managers (nullable)
|
|
||||||
* @return DTLS SSL context
|
|
||||||
* @throws Exception on errors
|
|
||||||
*/
|
|
||||||
public static SSLContext createDtlsContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception {
|
|
||||||
SSLContext ctx = SSLContext.getInstance("DTLS");
|
|
||||||
ctx.init(keyManagers, trustManagers, new SecureRandom());
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets or creates a DTLS session for a remote address.
|
|
||||||
*
|
|
||||||
* @param remote remote address
|
|
||||||
* @return session
|
|
||||||
* @throws SSLException if engine creation fails
|
|
||||||
*/
|
|
||||||
public DtlsSession session(SocketAddress remote) throws SSLException {
|
|
||||||
Objects.requireNonNull(remote, "remote");
|
|
||||||
return sessions.computeIfAbsent(remote, r -> {
|
|
||||||
try {
|
|
||||||
SSLEngine engine = createEngine(r);
|
|
||||||
|
|
||||||
// Critical: server-side sessions must enter handshake immediately.
|
|
||||||
if (!clientMode) {
|
|
||||||
engine.beginHandshake();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DtlsSession(engine, r, mtu);
|
|
||||||
} catch (SSLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a DTLS handshake for a remote address.
|
|
||||||
*
|
|
||||||
* @param remote remote address
|
|
||||||
* @throws IOException on I/O error
|
|
||||||
* @throws SSLException on TLS error
|
|
||||||
*/
|
|
||||||
public void handshake(SocketAddress remote) throws IOException, SSLException {
|
|
||||||
DtlsSession s = session(remote);
|
|
||||||
if (s.isHandshakeComplete()) return;
|
|
||||||
|
|
||||||
if (clientMode) {
|
|
||||||
s.engine().beginHandshake();
|
|
||||||
}
|
|
||||||
|
|
||||||
SSLEngineResult.HandshakeStatus hs = s.engine().getHandshakeStatus();
|
|
||||||
|
|
||||||
ByteBuffer netIn = ByteBuffer.allocate(mtu);
|
|
||||||
ByteBuffer netOut = ByteBuffer.allocate(mtu);
|
|
||||||
ByteBuffer app = ByteBuffer.allocate(s.engine().getSession().getApplicationBufferSize());
|
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
while (!s.isHandshakeComplete()) {
|
|
||||||
if (System.currentTimeMillis() - start > timeoutMillis) {
|
|
||||||
throw new SSLException("DTLS handshake timed out");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (hs) {
|
|
||||||
case NEED_WRAP -> {
|
|
||||||
netOut.clear();
|
|
||||||
|
|
||||||
SSLEngineResult r = s.engine().wrap(EMPTY, netOut);
|
|
||||||
hs = r.getHandshakeStatus();
|
|
||||||
|
|
||||||
if (r.getStatus() == SSLEngineResult.Status.CLOSED) {
|
|
||||||
throw new SSLException("DTLS engine closed during handshake (wrap)");
|
|
||||||
}
|
|
||||||
|
|
||||||
netOut.flip();
|
|
||||||
if (netOut.hasRemaining()) {
|
|
||||||
channel.send(netOut, remote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NEED_UNWRAP -> {
|
|
||||||
netIn.clear();
|
|
||||||
SocketAddress from = channel.receive(netIn);
|
|
||||||
if (from == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!from.equals(remote)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
netIn.flip();
|
|
||||||
app.clear();
|
|
||||||
|
|
||||||
SSLEngineResult r = s.engine().unwrap(netIn, app);
|
|
||||||
hs = r.getHandshakeStatus();
|
|
||||||
|
|
||||||
if (r.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (r.getStatus() == SSLEngineResult.Status.CLOSED) {
|
|
||||||
throw new SSLException("DTLS engine closed during handshake (unwrap)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NEED_TASK -> {
|
|
||||||
Runnable task;
|
|
||||||
while ((task = s.engine().getDelegatedTask()) != null) {
|
|
||||||
task.run();
|
|
||||||
}
|
|
||||||
hs = s.engine().getHandshakeStatus();
|
|
||||||
}
|
|
||||||
case FINISHED, NOT_HANDSHAKING -> {
|
|
||||||
s.setHandshakeComplete(true);
|
|
||||||
enforceClientAuthIfRequired(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends application bytes through DTLS to a remote peer.
|
|
||||||
*
|
|
||||||
* @param remote remote address
|
|
||||||
* @param applicationData plaintext app data
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
* @throws SSLException on TLS errors
|
|
||||||
*/
|
|
||||||
public void sendApplication(SocketAddress remote, ByteBuffer applicationData) throws IOException, SSLException {
|
|
||||||
Objects.requireNonNull(remote, "remote");
|
|
||||||
Objects.requireNonNull(applicationData, "applicationData");
|
|
||||||
|
|
||||||
DtlsSession s = session(remote);
|
|
||||||
if (!s.isHandshakeComplete()) {
|
|
||||||
handshake(remote);
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer netOut = ByteBuffer.allocate(mtu);
|
|
||||||
|
|
||||||
while (applicationData.hasRemaining()) {
|
|
||||||
netOut.clear();
|
|
||||||
SSLEngineResult r = s.engine().wrap(applicationData, netOut);
|
|
||||||
if (r.getStatus() == SSLEngineResult.Status.CLOSED) {
|
|
||||||
throw new SSLException("DTLS engine closed");
|
|
||||||
}
|
|
||||||
netOut.flip();
|
|
||||||
if (netOut.hasRemaining()) {
|
|
||||||
channel.send(netOut, remote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls UDP, unwraps DTLS records and dispatches decrypted application data.
|
|
||||||
*
|
|
||||||
* @throws IOException on I/O errors
|
|
||||||
*/
|
|
||||||
public void poll() throws IOException {
|
|
||||||
ByteBuffer netIn = ByteBuffer.allocate(mtu);
|
|
||||||
|
|
||||||
SocketAddress from;
|
|
||||||
while ((from = channel.receive(netIn)) != null) {
|
|
||||||
netIn.flip();
|
|
||||||
|
|
||||||
DtlsSession s;
|
|
||||||
try {
|
|
||||||
s = session(from);
|
|
||||||
} catch (RuntimeException re) {
|
|
||||||
netIn.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer app = ByteBuffer.allocate(s.engine().getSession().getApplicationBufferSize());
|
|
||||||
|
|
||||||
try {
|
|
||||||
app.clear();
|
|
||||||
SSLEngineResult r = s.engine().unwrap(netIn, app);
|
|
||||||
|
|
||||||
if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
|
|
||||||
Runnable task;
|
|
||||||
while ((task = s.engine().getDelegatedTask()) != null) {
|
|
||||||
task.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
|
|
||||||
ByteBuffer netOut = ByteBuffer.allocate(mtu);
|
|
||||||
netOut.clear();
|
|
||||||
|
|
||||||
SSLEngineResult wr = s.engine().wrap(EMPTY, netOut);
|
|
||||||
if (wr.getStatus() != SSLEngineResult.Status.CLOSED) {
|
|
||||||
netOut.flip();
|
|
||||||
if (netOut.hasRemaining()) {
|
|
||||||
channel.send(netOut, from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED
|
|
||||||
|| r.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
|
|
||||||
if (!s.isHandshakeComplete()) {
|
|
||||||
s.setHandshakeComplete(true);
|
|
||||||
enforceClientAuthIfRequired(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.getStatus() == SSLEngineResult.Status.CLOSED) {
|
|
||||||
sessions.remove(from);
|
|
||||||
netIn.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.flip();
|
|
||||||
if (app.hasRemaining() && s.isHandshakeComplete()) {
|
|
||||||
appHandler.onApplicationData(from, app);
|
|
||||||
}
|
|
||||||
} catch (SSLException ignored) {
|
|
||||||
// Best effort: invalid record / handshake mismatch -> drop.
|
|
||||||
} finally {
|
|
||||||
netIn.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enforceClientAuthIfRequired(DtlsSession s) throws SSLException {
|
|
||||||
if (clientMode) return;
|
|
||||||
if (clientAuthMode != ClientAuthMode.REQUIRED) return;
|
|
||||||
|
|
||||||
// If REQUIRED, ensure the peer presented a certificate.
|
|
||||||
try {
|
|
||||||
Certificate[] peer = s.engine().getSession().getPeerCertificates();
|
|
||||||
if (peer == null || peer.length == 0) {
|
|
||||||
sessions.remove(s.remote());
|
|
||||||
throw new SSLException("Client certificate required but not provided");
|
|
||||||
}
|
|
||||||
} catch (javax.net.ssl.SSLPeerUnverifiedException e) {
|
|
||||||
sessions.remove(s.remote());
|
|
||||||
throw new SSLException("Client certificate required but peer unverified", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SSLEngine createEngine(SocketAddress remote) throws SSLException {
|
|
||||||
final SSLEngine engine;
|
|
||||||
|
|
||||||
if (remote instanceof InetSocketAddress isa) {
|
|
||||||
// Host string may be an IP or hostname. Both are acceptable for SSLEngine identity.
|
|
||||||
engine = sslContext.createSSLEngine(isa.getHostString(), isa.getPort());
|
|
||||||
} else {
|
|
||||||
engine = sslContext.createSSLEngine();
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.setUseClientMode(clientMode);
|
|
||||||
|
|
||||||
SSLParameters p = engine.getSSLParameters();
|
|
||||||
p.setProtocols(new String[]{"DTLSv1.2"});
|
|
||||||
|
|
||||||
// Do NOT rely on setNeedClientAuth/setWantClientAuth for SSLEngine enforcement.
|
|
||||||
// Enforce REQUIRED via post-handshake peer certificate check.
|
|
||||||
|
|
||||||
engine.setSSLParameters(p);
|
|
||||||
return engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for decrypted application data.
|
|
||||||
*/
|
|
||||||
public interface ApplicationDataHandler {
|
|
||||||
/**
|
|
||||||
* Called when a DTLS session produced decrypted application bytes.
|
|
||||||
*
|
|
||||||
* @param remote remote socket address
|
|
||||||
* @param data decrypted data buffer (position=0, limit=length)
|
|
||||||
*/
|
|
||||||
void onApplicationData(SocketAddress remote, ByteBuffer data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds a per-remote DTLS state.
|
|
||||||
*/
|
|
||||||
public static final class DtlsSession {
|
|
||||||
private final SSLEngine engine;
|
|
||||||
private final SocketAddress remote;
|
|
||||||
private final int mtu;
|
|
||||||
private volatile boolean handshakeComplete;
|
|
||||||
|
|
||||||
private DtlsSession(SSLEngine engine, SocketAddress remote, int mtu) {
|
|
||||||
this.engine = engine;
|
|
||||||
this.remote = remote;
|
|
||||||
this.mtu = mtu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SSLEngine engine() {
|
|
||||||
return engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress remote() {
|
|
||||||
return remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int mtu() {
|
|
||||||
return mtu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHandshakeComplete() {
|
|
||||||
return handshakeComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHandshakeComplete(boolean complete) {
|
|
||||||
this.handshakeComplete = complete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a network endpoint that may be defined by hostname or IP literal.
|
|
||||||
*
|
|
||||||
* <p>Fully supports IPv4 and IPv6 (including bracketed IPv6 literals).
|
|
||||||
* Resolution is deterministic and configurable via {@link IpPreference}.</p>
|
|
||||||
*/
|
|
||||||
public final class Endpoint {
|
|
||||||
|
|
||||||
private final String host;
|
|
||||||
private final int port;
|
|
||||||
private final IpPreference ipPreference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new endpoint.
|
|
||||||
*
|
|
||||||
* @param host hostname or IP literal
|
|
||||||
* @param port port (1–65535)
|
|
||||||
* @param ipPreference IP selection preference
|
|
||||||
*/
|
|
||||||
public Endpoint(String host, int port, IpPreference ipPreference) {
|
|
||||||
this.host = normalizeHost(host);
|
|
||||||
this.port = validatePort(port);
|
|
||||||
this.ipPreference = Objects.requireNonNull(ipPreference, "ipPreference");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new endpoint with {@link IpPreference#PREFER_IPV6}.
|
|
||||||
*
|
|
||||||
* @param host hostname or IP literal
|
|
||||||
* @param port port
|
|
||||||
*/
|
|
||||||
public Endpoint(String host, int port) {
|
|
||||||
this(host, port, IpPreference.PREFER_IPV6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the host or IP literal.
|
|
||||||
*
|
|
||||||
* @return host
|
|
||||||
*/
|
|
||||||
public String host() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the port.
|
|
||||||
*
|
|
||||||
* @return port
|
|
||||||
*/
|
|
||||||
public int port() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves all addresses for this endpoint.
|
|
||||||
*
|
|
||||||
* @return resolved socket addresses
|
|
||||||
* @throws UnknownHostException if resolution fails
|
|
||||||
*/
|
|
||||||
public List<InetSocketAddress> resolveAll() throws UnknownHostException {
|
|
||||||
InetAddress[] addresses = InetAddress.getAllByName(host);
|
|
||||||
if (addresses.length == 0) {
|
|
||||||
throw new UnknownHostException("No addresses resolved for " + host);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<InetSocketAddress> result = new ArrayList<>(addresses.length);
|
|
||||||
for (InetAddress a : addresses) {
|
|
||||||
result.add(new InetSocketAddress(a, port));
|
|
||||||
}
|
|
||||||
|
|
||||||
result.sort(Comparator.comparingInt(this::score));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the best address according to {@link IpPreference}.
|
|
||||||
*
|
|
||||||
* @return best socket address
|
|
||||||
* @throws UnknownHostException if resolution fails
|
|
||||||
*/
|
|
||||||
public InetSocketAddress resolveBest() throws UnknownHostException {
|
|
||||||
return resolveAll().get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int score(InetSocketAddress addr) {
|
|
||||||
boolean ipv6 = addr.getAddress() instanceof java.net.Inet6Address;
|
|
||||||
boolean ipv4 = addr.getAddress() instanceof java.net.Inet4Address;
|
|
||||||
|
|
||||||
return switch (ipPreference) {
|
|
||||||
case IPV6_ONLY -> ipv6 ? 0 : 100;
|
|
||||||
case IPV4_ONLY -> ipv4 ? 0 : 100;
|
|
||||||
case PREFER_IPV6 -> ipv6 ? 0 : 10;
|
|
||||||
case PREFER_IPV4 -> ipv4 ? 0 : 10;
|
|
||||||
case ANY -> 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalizeHost(String host) {
|
|
||||||
Objects.requireNonNull(host, "host");
|
|
||||||
String h = host.trim();
|
|
||||||
if (h.isEmpty()) throw new IllegalArgumentException("host must not be empty");
|
|
||||||
|
|
||||||
// Remove IPv6 brackets if present
|
|
||||||
if (h.startsWith("[") && h.endsWith("]") && h.length() > 2) {
|
|
||||||
h = h.substring(1, h.length() - 1).trim();
|
|
||||||
}
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int validatePort(int port) {
|
|
||||||
if (port <= 0 || port > 65535) {
|
|
||||||
throw new IllegalArgumentException("port out of range: " + port);
|
|
||||||
}
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls IP family selection when resolving hostnames.
|
|
||||||
*/
|
|
||||||
public enum IpPreference {
|
|
||||||
ANY,
|
|
||||||
PREFER_IPV6,
|
|
||||||
PREFER_IPV4,
|
|
||||||
IPV6_ONLY,
|
|
||||||
IPV4_ONLY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +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://unlegitdqrk.dev/
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.unlegitdqrk.unlegitlibrary.network.system.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ServerProtocolMode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported network protocols for packet transport.
|
|
||||||
*
|
|
||||||
* <p>This enum is used:
|
|
||||||
* <ul>
|
|
||||||
* <li>by client and server to declare supported/enabled protocols</li>
|
|
||||||
* <li>when sending packets to explicitly choose the transport</li>
|
|
||||||
* <li>by {@link ServerProtocolMode} to define server capabilities</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public enum NetworkProtocol {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TCP transport secured via TLS.
|
|
||||||
*/
|
|
||||||
TCP,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UDP transport secured via DTLS.
|
|
||||||
*/
|
|
||||||
UDP
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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.utils;
|
||||||
|
|
||||||
|
public enum TransportProtocol {
|
||||||
|
TCP, UDP
|
||||||
|
}
|
||||||
28
src/test/java/Client.java
Normal file
28
src/test/java/Client.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||||
|
*
|
||||||
|
* You are unauthorized to remove this copyright.
|
||||||
|
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||||
|
* See LICENSE-File if exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.*;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.tcp.*;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
|
||||||
|
public class Client extends EventListener {
|
||||||
|
|
||||||
|
static EventManager eventManager = new EventManager();
|
||||||
|
static NetworkClient client;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
PacketHandler packetHandler = new PacketHandler();
|
||||||
|
packetHandler.registerPacket(() -> new TestTextPacket(""));
|
||||||
|
client = new NetworkClient(packetHandler);
|
||||||
|
client.connect("127.0.0.1", 25565);
|
||||||
|
client.sendPacket(new TestTextPacket("client: tcp"), TransportProtocol.TCP);
|
||||||
|
client.sendPacket(new TestTextPacket("client: udp"), TransportProtocol.UDP);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/test/java/TestServerMain.java
Normal file
42
src/test/java/TestServerMain.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||||
|
*
|
||||||
|
* You are unauthorized to remove this copyright.
|
||||||
|
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||||
|
* See LICENSE-File if exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.*;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.tcp.*;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TestServerMain extends EventListener {
|
||||||
|
|
||||||
|
static NetworkServer server;
|
||||||
|
static EventManager eventManager = new EventManager();
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
PacketHandler packetHandler = new PacketHandler();
|
||||||
|
packetHandler.registerPacket(() -> new TestTextPacket(""));
|
||||||
|
|
||||||
|
eventManager.registerListener(new TestServerMain());
|
||||||
|
|
||||||
|
server = new NetworkServer(packetHandler);
|
||||||
|
server.start(25565, 25566);
|
||||||
|
|
||||||
|
server.events = new NetworkServer.EventsServer() {
|
||||||
|
@Override
|
||||||
|
public void onConnect(ConnectedClient client) {
|
||||||
|
try {
|
||||||
|
client.sendPacket(new TestTextPacket("server: tcp"), TransportProtocol.TCP);
|
||||||
|
client.sendPacket(new TestTextPacket("server: udp"), TransportProtocol.UDP);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/test/java/TestTextPacket.java
Normal file
65
src/test/java/TestTextPacket.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 UnlegitDqrk - All Rights Reserved
|
||||||
|
*
|
||||||
|
* You are unauthorized to remove this copyright.
|
||||||
|
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk
|
||||||
|
* See LICENSE-File if exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple text packet used for round-trip transport tests over TCP and UDP.
|
||||||
|
*/
|
||||||
|
public final class TestTextPacket extends Packet {
|
||||||
|
|
||||||
|
private volatile String message;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPacketID() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a packet with a message.
|
||||||
|
*
|
||||||
|
* @param message message
|
||||||
|
*/
|
||||||
|
public TestTextPacket(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestTextPacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return message
|
||||||
|
*/
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(DataOutputStream stream) {
|
||||||
|
try {
|
||||||
|
stream.writeUTF(message == null ? "" : message);
|
||||||
|
System.out.println("[SEND] " + message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to write TestTextPacket: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(DataInputStream stream, UUID clientID) {
|
||||||
|
try {
|
||||||
|
this.message = stream.readUTF();
|
||||||
|
System.out.println("[RECEIVE] " + this.message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to read TestTextPacket: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user