diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java deleted file mode 100644 index 7c47b6d..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java +++ /dev/null @@ -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). - * - *
Connectivity rules: - *
If UDP is enabled and TCP is disabled, the client sends {@link UdpHelloPacket} to obtain - * a {@link ConnectionIdPacket} + {@link ProtocolRequirementsPacket} over UDP.
- * - * @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 EnumSetNote: UDP packets can be sent before UDP is "bound" because the bind/hello flow itself - * is transported via UDP.
- * - * @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 EnumSetNote: 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.
- */ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java deleted file mode 100644 index 7867627..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/events/packets/send/C_PacketSendEvent.java +++ /dev/null @@ -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. - * - *v2 mapping: - *
Protocol-specific: - *
In v2 this typically means: - *
Protocol-specific: - *
Packets are encoded as: - *
- * [int packetId][payload bytes...] - *- * Payload is written/read by the packet itself through Java serialization streams. - */ public abstract class Packet { - private final int packetId; + public Packet() {} - /** - * Creates a new packet. - * - * @param packetId unique packet id - */ - protected Packet(int packetId) { - this.packetId = packetId; - } + public abstract int getPacketID(); + public abstract void read(DataInputStream stream, UUID clientID) throws IOException; + public abstract void write(DataOutputStream stream) throws IOException; - /** - * 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; } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketCodec.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketCodec.java deleted file mode 100644 index 830a29b..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketCodec.java +++ /dev/null @@ -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). - * - *
Wire format (transport-agnostic): - *
- * [int packetId][ObjectStream payload...] - *- */ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java new file mode 100644 index 0000000..9a19f77 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java @@ -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
This avoids reusing the same packet instance across threads/connections.
- */ -public final class PacketRegistry { - - private final MapThis id is used to bind the DTLS/UDP endpoint to the already authenticated TCP/TLS connection - * via {@link UdpBindPacket}.
- */ -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); - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ProtocolRequirementsPacket.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ProtocolRequirementsPacket.java deleted file mode 100644 index 7d6a0d9..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/ProtocolRequirementsPacket.java +++ /dev/null @@ -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. - * - *This packet allows the client to evaluate "fully connected" based on the server policy.
- */ -public final class ProtocolRequirementsPacket extends Packet { - - private EnumSetThe client must only consider UDP "bound/ready" after receiving this ACK with the matching connection id.
- * - *IMPORTANT: Ensure {@link #PACKET_ID} does not conflict with your other packet ids and - * register it in {@code PacketRegistry}.
- */ -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); - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java deleted file mode 100644 index 6bea518..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/impl/UdpBindPacket.java +++ /dev/null @@ -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. - * - *Flow: - *
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.
- */ -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. - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ClientConnection.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ClientConnection.java deleted file mode 100644 index b692fb7..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ClientConnection.java +++ /dev/null @@ -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. - * - *Connection usability is defined by {@link ServerProtocolMode#required()}.
- */ -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 EnumSetEmits: - *
Listeners are started based on {@link ServerProtocolMode#required()}.
- *Fully-connected is evaluated based on {@link ServerProtocolMode#required()}.
- */ -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 MapNote: 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.
- */ -public final class ServerProtocolMode { - - private final EnumSetIn 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.
- */ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java deleted file mode 100644 index 3799aef..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendEvent.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java deleted file mode 100644 index e85bdb4..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/packets/send/S_PacketSendFailedEvent.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ClientConnectionConnectedEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ClientConnectionConnectedEvent.java deleted file mode 100644 index 8e97c9b..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/connect/ClientConnectionConnectedEvent.java +++ /dev/null @@ -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. - * - *Protocol-specific: - *
In v2 this typically means: - *
This event is emitted for TCP/TLS accept only. UDP/DTLS is bound later via the bind flow.
- */ -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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java deleted file mode 100644 index a828eb5..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/events/state/incoming/UDPIncomingConnectionEvent.java +++ /dev/null @@ -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. - * - *This event allows inspection or rejection of: - *
If cancelled, the datagram is silently dropped.
- */ -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. - * - *The buffer is read-only.
- * - * @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; - } -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/ConnectedClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/ConnectedClient.java new file mode 100644 index 0000000..f9d83d0 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/ConnectedClient.java @@ -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; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkClient.java new file mode 100644 index 0000000..de3e846 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkClient.java @@ -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; + } +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkServer.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkServer.java new file mode 100644 index 0000000..f5b75d7 --- /dev/null +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/tcp/NetworkServer.java @@ -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{@link #REQUIRED} enforces mutual TLS (mTLS): clients must present a certificate.
- * - *{@link #OPTIONAL} allows clients without a certificate to connect (server will request a certificate, - * but does not fail the handshake if none is provided).
- */ -public enum ClientAuthMode { - - /** - * Client certificate is mandatory (mTLS). - */ - REQUIRED, - - /** - * Client certificate is optional. - */ - OPTIONAL -} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/DtlsEndpoint.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/DtlsEndpoint.java deleted file mode 100644 index 8742bc6..0000000 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/DtlsEndpoint.java +++ /dev/null @@ -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. - * - *This implementation is designed for "best effort" UDP and focuses on: - *
Server mode note: 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.
- * - *Client certificate policy (server-side): - * 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.
- */ -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 MapFully supports IPv4 and IPv6 (including bracketed IPv6 literals). - * Resolution is deterministic and configurable via {@link IpPreference}.
- */ -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 ListThis enum is used: - *