Java tutorial
/* * __ ______ ______ __ __ __ __ ______ ______ * /\ \ /\ == \ /\ __ \ /\ \/ / /\ "-.\ \ /\ ___\ /\__ _\ * _\_\ \ \ \ __< \ \ __ \ \ \ _"-. \ \ \-. \ \ \ __\ \/_/\ \/ * /\_____\ \ \_\ \_\ \ \_\ \_\ \ \_\ \_\ \ \_\\"\_\ \ \_____\ \ \_\ * \/_____/ \/_/ /_/ \/_/\/_/ \/_/\/_/ \/_/ \/_/ \/_____/ \/_/ * * the MIT License (MIT) * * Copyright (c) 2016-2019 Trent "Whirvis" Summerlin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * the above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.whirvis.jraknet.server; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.whirvis.jraknet.InvalidChannelException; import com.whirvis.jraknet.Packet; import com.whirvis.jraknet.RakNet; import com.whirvis.jraknet.RakNetException; import com.whirvis.jraknet.RakNetPacket; import com.whirvis.jraknet.ThreadedListener; import com.whirvis.jraknet.client.RakNetClient; import com.whirvis.jraknet.identifier.Identifier; import com.whirvis.jraknet.peer.RakNetClientPeer; import com.whirvis.jraknet.protocol.Reliability; import com.whirvis.jraknet.protocol.connection.ConnectionBanned; import com.whirvis.jraknet.protocol.connection.IncompatibleProtocolVersion; import com.whirvis.jraknet.protocol.connection.OpenConnectionRequestOne; import com.whirvis.jraknet.protocol.connection.OpenConnectionRequestTwo; import com.whirvis.jraknet.protocol.connection.OpenConnectionResponseOne; import com.whirvis.jraknet.protocol.connection.OpenConnectionResponseTwo; import com.whirvis.jraknet.protocol.message.EncapsulatedPacket; import com.whirvis.jraknet.protocol.status.UnconnectedPing; import com.whirvis.jraknet.protocol.status.UnconnectedPong; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; /** * Used to create servers using the RakNet protocol. * * @author Trent "Whirvis" Summerlin * @since JRakNet v1.0.0 */ public class RakNetServer implements RakNetServerListener { /** * No globally unique ID for the * {@link #validateSender(InetSocketAddress, long)} method. */ private static final int NO_GUID = -1; /** * Has the maximum transfer unit automatically determined during startup. */ public static final int AUTOMATIC_MTU = -1; /** * Allows for infinite connections to the server. */ public static final int INFINITE_CONNECTIONS = -1; private final InetSocketAddress bindingAddress; private final long guid; private final Logger log; private final long pongId; private final long timestamp; private final int maximumTransferUnit; private int maxConnections; private boolean broadcastingEnabled; private Identifier identifier; private int eventThreadCount; private final ConcurrentLinkedQueue<RakNetServerListener> listeners; private final ConcurrentHashMap<InetSocketAddress, RakNetClientPeer> clients; private final ConcurrentLinkedQueue<InetAddress> banned; private Bootstrap bootstrap; private EventLoopGroup group; private RakNetServerHandler handler; private Channel channel; private InetSocketAddress bindAddress; private Thread peerThread; private volatile boolean running; /** * Creates a RakNet server. * * @param address * the address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(InetSocketAddress address, int maximumTransferUnit, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { if (address == null) { throw new NullPointerException("Address cannot be null"); } else if (maximumTransferUnit < RakNet.MINIMUM_MTU_SIZE && maximumTransferUnit != AUTOMATIC_MTU) { throw new IllegalArgumentException( "Maximum transfer unit must be no smaller than " + RakNet.MINIMUM_MTU_SIZE + " or equal to " + AUTOMATIC_MTU + " for the maximum transfer unit to be determined automatically"); } else if (maxConnections < 0 && maxConnections != INFINITE_CONNECTIONS) { throw new IllegalArgumentException("Maximum connections must be greater than or equal to 0 or " + INFINITE_CONNECTIONS + " for infinite connections"); } UUID uuid = UUID.randomUUID(); this.bindingAddress = address; this.guid = uuid.getMostSignificantBits(); this.log = LogManager .getLogger(RakNetServer.class.getSimpleName() + "-" + Long.toHexString(guid).toUpperCase()); this.pongId = uuid.getLeastSignificantBits(); this.timestamp = System.currentTimeMillis(); this.maxConnections = maxConnections; this.maximumTransferUnit = maximumTransferUnit == AUTOMATIC_MTU ? RakNet.getMaximumTransferUnit(address) : maximumTransferUnit; this.broadcastingEnabled = true; this.identifier = identifier; this.listeners = new ConcurrentLinkedQueue<RakNetServerListener>(); this.clients = new ConcurrentHashMap<InetSocketAddress, RakNetClientPeer>(); this.banned = new ConcurrentLinkedQueue<InetAddress>(); } /** * Creates a RakNet server. * * @param address * the address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(InetSocketAddress address, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { this(address, AUTOMATIC_MTU, maxConnections, identifier); } /** * Creates a RakNet server. * * @param address * the address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(InetSocketAddress address, int maximumTransferUnit, int maxConnections) throws NullPointerException, IllegalArgumentException { this(address, maximumTransferUnit, maxConnections, null); } /** * Creates a RakNet server. * * @param address * the address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(InetSocketAddress address, int maxConnections) throws NullPointerException, IllegalArgumentException { this(address, AUTOMATIC_MTU, maxConnections); } /** * Creates a RakNet server. * * @param address * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(InetAddress address, int port, int maximumTransferUnit, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { this(new InetSocketAddress(address, port), maximumTransferUnit, maxConnections, identifier); } /** * Creates a RakNet server. * * @param address * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(InetAddress address, int port, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { this(address, port, AUTOMATIC_MTU, maxConnections, identifier); } /** * Creates a RakNet server. * * @param address * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(InetAddress address, int port, int maximumTransferUnit, int maxConnections) throws NullPointerException, IllegalArgumentException { this(address, port, maximumTransferUnit, maxConnections, null); } /** * Creates a RakNet server. * * @param address * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(InetAddress address, int port, int maxConnections) throws NullPointerException, IllegalArgumentException { this(address, port, AUTOMATIC_MTU, maxConnections); } /** * Creates a RakNet server. * * @param host * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws UnknownHostException * if no IP address for the <code>bindAddress</code> could be * found, or if a scope_id was specified for a global IPv6 * address. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(String host, int port, int maximumTransferUnit, int maxConnections, Identifier identifier) throws UnknownHostException, NullPointerException, IllegalArgumentException { this(InetAddress.getByName(host), port, maximumTransferUnit, maxConnections, identifier); } /** * Creates a RakNet server. * * @param host * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws UnknownHostException * if no IP address for the <code>bindAddress</code> could be * found, or if a scope_id was specified for a global IPv6 * address. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(String host, int port, int maxConnections, Identifier identifier) throws UnknownHostException, NullPointerException, IllegalArgumentException { this(host, port, AUTOMATIC_MTU, maxConnections, identifier); } /** * Creates a RakNet server. * * @param host * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws UnknownHostException * if no IP address for the <code>bindAddress</code> could be * found, or if a scope_id was specified for a global IPv6 * address. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(String host, int port, int maximumTransferUnit, int maxConnections) throws UnknownHostException, NullPointerException, IllegalArgumentException { this(host, port, maximumTransferUnit, maxConnections, null); } /** * Creates a RakNet server. * * @param host * the IP address the server will bind to during startup. A * <code>null</code> IP address will have the server bind to the * wildcard address. * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws UnknownHostException * if no IP address for the <code>bindAddress</code> could be * found, or if a scope_id was specified for a global IPv6 * address. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(String host, int port, int maxConnections) throws UnknownHostException, NullPointerException, IllegalArgumentException { this(host, port, AUTOMATIC_MTU, maxConnections); } /** * Creates a RakNet server. * * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). A * value of {@value #AUTOMATIC_MTU} will have the maximum * transfer unit be determined automatically via * {@link RakNet#getMaximumTransferUnit(InetAddress)} with the * parameter being the specified bind <code>address</code>. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(int port, int maximumTransferUnit, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { this(new InetSocketAddress((InetAddress) null, port), maximumTransferUnit, maxConnections, identifier); } /** * Creates a RakNet server. * * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @param identifier * the identifier that will be sent in response to server pings * if server broadcasting is enabled. A <code>null</code> * identifier means nothing will be sent in response to server * pings, even if server broadcasting is enabled. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(int port, int maxConnections, Identifier identifier) throws NullPointerException, IllegalArgumentException { this(port, AUTOMATIC_MTU, maxConnections, identifier); } /** * Creates a RakNet server. * * @param port * the port the server will bind to during startup. * @param maximumTransferUnit * the highest maximum transfer unit a client can use. The * maximum transfer unit is the maximum number of bytes that can * be sent in one packet. If a packet exceeds this size, it is * automatically split up so that it can still be sent over the * connection (this is handled automatically by * {@link com.whirvis.jraknet.peer.RakNetPeer RakNetPeer}). * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS} or the maximum * transfer unit size is less than * {@value RakNet#MINIMUM_MTU_SIZE} and is not equal to * {@value #AUTOMATIC_MTU}. */ public RakNetServer(int port, int maximumTransferUnit, int maxConnections) throws NullPointerException, IllegalArgumentException { this(port, maximumTransferUnit, maxConnections, null); } /** * Creates a RakNet server. * * @param port * the port the server will bind to during startup. * @param maxConnections * the maximum number of connections, A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws NullPointerException * if the address is <code>null</code>. * @throws IllegalArgumentException * if the maximum connections is less than <code>0</code> and * not equal to {@value #INFINITE_CONNECTIONS}. */ public RakNetServer(int port, int maxConnections) throws NullPointerException, IllegalArgumentException { this(port, AUTOMATIC_MTU, maxConnections); } /** * Forwards the port that the server is running on. * * @return the result of the port forward attempt. * @see RakNet#forwardPort(int) */ public final RakNet.UPnPResult forwardPort() { return RakNet.forwardPort(this.getPort()); } /** * Closes the port that the server is running on. * * @return the result of the port close attempt. * @see RakNet#closePort(int) */ public final RakNet.UPnPResult closePort() { return RakNet.closePort(this.getPort()); } /** * Returns the server's networking protocol version. * * @return the server's networking protocol version. */ public final int getProtocolVersion() { return RakNet.SERVER_NETWORK_PROTOCOL; } /** * Returns the server's globally unique ID. * * @return the server's globally unique ID. */ public final long getGloballyUniqueId() { return this.guid; } /** * Returns the server's timestamp. * * @return the server's timestamp. */ public final long getTimestamp() { return System.currentTimeMillis() - timestamp; } /** * Returns the address the server is bound to. * * @return the address the server is bound to. */ public final InetSocketAddress getAddress() { return this.bindAddress; } /** * Returns the IP address the server is bound to. * * @return the IP address the server is bound to. */ public final InetAddress getInetAddress() { return bindAddress.getAddress(); } /** * Returns the port the server is bound to. * * @return the port the server is bound to. */ public final int getPort() { return bindAddress.getPort(); } /** * Returns the server's maximum transfer unit. * * @return the server's maximum transfer unit. */ public final int getMaximumTransferUnit() { return this.maximumTransferUnit; } /** * Returns the maximum amount of connections allowed at once. * * @return the maximum amount of connections allowed at once, * {@value #INFINITE_CONNECTIONS} if an infinite amount of * connections are allowed. */ public final int getMaxConnections() { return this.maxConnections; } /** * Sets the maximum amount of connections allowed at once. * * @param maxConnections * the maximum number of connections. A value of * {@value #INFINITE_CONNECTIONS} will allow for an infinite * number of connections. * @throws IllegalArgumentException * if the <code>maxConnections</code> is less than * <code>0</code> and is not equal to * {@value #INFINITE_CONNECTIONS}. */ public final void setMaxConnections(int maxConnections) throws IllegalArgumentException { if (maxConnections < 0 && maxConnections != INFINITE_CONNECTIONS) { throw new IllegalArgumentException("Maximum connections must be greater than or equal to 0 or " + INFINITE_CONNECTIONS + " for infinite connections"); } this.maxConnections = maxConnections; } /** * Enables/disables server broadcasting. * * @param enabled * <code>true</code> to enable broadcasting, <code>false</code> * to disable broadcasting. */ public final void setBroadcastingEnabled(boolean enabled) { boolean wasBroadcasting = this.broadcastingEnabled; this.broadcastingEnabled = enabled; if (wasBroadcasting != enabled) { log.info((enabled ? "Enabled" : "Disabled") + " broadcasting"); } } /** * Returns whether or not broadcasting is enabled. * * @return <code>true</code> if broadcasting is enabled, <code>false</code> * otherwise. */ public final boolean isBroadcastingEnabled() { return this.broadcastingEnabled; } /** * Returns the identifier sent back to clients who ping the server. * * @return the identifier sent back to clients who ping the server. */ public final Identifier getIdentifier() { return this.identifier; } /** * Sets the server's identifier used for discovery. * * @param identifier * the new identifier. */ public final void setIdentifier(Identifier identifier) { this.identifier = identifier; if (identifier != null) { log.info("Set identifier to \"" + identifier.build() + "\""); } else { log.info("Removed identifier"); } } /** * Adds a listener to the server. * <p> * Listeners are used to listen for events that occur relating to the server * such as clients connecting to the server, receiving messages, and more. * * @param listener * the listener to add. * @return the server. * @throws NullPointerException * if the listener is <code>null</code>. * @throws IllegalArgumentException * if the listener is another server that is not the server * itself. */ public final RakNetServer addListener(RakNetServerListener listener) throws NullPointerException, IllegalArgumentException { if (listener == null) { throw new NullPointerException("Listener cannot be null"); } else if (listener instanceof RakNetClient && !this.equals(listener)) { throw new IllegalArgumentException("A server cannot be used as a listener except for itself"); } else if (!listeners.contains(listener)) { listeners.add(listener); if (listener != this) { log.info("Added listener of class " + listener.getClass().getName()); } else { log.info("Added self listener"); } } return this; } /** * Adds the server to its own set of listeners, used when extending the * {@link RakNetServer} directly. * * @return the server. * @see #addListener(RakNetServerListener) */ public final RakNetServer addSelfListener() { return this.addListener(this); } /** * Removes a listener from the server. * * @param listener * the listener to remove. * @return the server. */ public final RakNetServer removeListener(RakNetServerListener listener) { if (listeners.remove(listener)) { log.info("Removed listener of class " + listener.getClass().getName()); } return this; } /** * Removes the server from its own set of listeners, used when extending the * {@link RakNetServer} directly. * * @return the server. * @see #removeListener(RakNetServerListener) */ public final RakNetServer removeSelfListener() { this.removeListener(this); return this; } /** * Calls the event. * * @param event * the event to call. * @throws NullPointerException * if the event is <code>null</code>. */ public final void callEvent(Consumer<? super RakNetServerListener> event) throws NullPointerException { if (event == null) { throw new NullPointerException("Event cannot be null"); } for (RakNetServerListener listener : listeners) { if (listener.getClass().isAnnotationPresent(ThreadedListener.class)) { ThreadedListener threadedListener = listener.getClass().getAnnotation(ThreadedListener.class); new Thread(RakNetServer.class.getSimpleName() + (threadedListener.name().length() > 0 ? "-" : "") + threadedListener.name() + "-Thread-" + ++eventThreadCount) { @Override public void run() { event.accept(listener); } }.start(); } else { event.accept(listener); } } } /** * Returns the clients connected to the server. * * @return the clients connected to the server. */ public final RakNetClientPeer[] getClients() { return clients.values().toArray(new RakNetClientPeer[clients.size()]); } /** * Returns the amount of clients connected to the server. * * @return the amount of clients connected to the server. */ public final int getClientCount() { return clients.size(); } /** * Returns whether or not a client with the specified address is currently * connected to the server. * * @param address * the address. * @return <code>true</code> if a client with the address is connected to * the server, <code>false</code> otherwise. */ public final boolean hasClient(InetSocketAddress address) { if (address != null) { return clients.containsKey(address); } return false; } /** * Returns whether or not a client with the specified address is currently * connected to the server. * * @param address * the IP address. * @param port * the port. * @return <code>true</code> if a client with the address is connected to * the server, <code>false</code> otherwise. */ public final boolean hasClient(InetAddress address, int port) { if (port >= 0x0000 && port <= 0xFFFF) { return this.hasClient(new InetSocketAddress(address, port)); } return false; } /** * Returns whether or not a client with the specified address is currently * connected to the server. * * @param host * the IP address. * @param port * the port. * @return <code>true</code> if a client with the address is connected to * the server, <code>false</code> otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean hasClient(String host, int port) throws UnknownHostException { return this.hasClient(InetAddress.getByName(host), port); } /** * Returns whether or not a client with the specified IP address is * currently connected to the server. * * @param address * the IP address. * @return <code>true</code> if a client with the IP address is connected to * the server, <code>false</code> otherwise. */ public final boolean hasClient(InetAddress address) { if (address != null) { for (InetSocketAddress clientAddress : clients.keySet()) { if (clientAddress.equals(address)) { return true; } } } return false; } /** * Returns whether or not a client with the specified IP address is * currently connected to the server. * * @param host * the IP address. * @return <code>true</code> if a client with the IP address is connected to * the server, <code>false</code> otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean hasClient(String host) throws UnknownHostException { return this.hasClient(InetAddress.getByName(host)); } /** * Returns whether or not a client with the specified port is currently * connected to the server. * * @param port * the port. * @return <code>true</code> if a client with the port is connected to the * server, <code>false</code> otherwise. */ public final boolean hasClient(int port) { if (port >= 0x0000 || port <= 0xFFFF) { for (InetSocketAddress clientAddress : clients.keySet()) { if (clientAddress.getPort() == port) { return true; } } } return false; } /** * Returns whether or not the client with the specified globally unique ID * is currently connected to the server. * * @param guid * the globally unique ID. * @return <code>true</code> if a client with the globally unique ID is * connected to the server, <code>false</code> otherwise. */ public final boolean hasClient(long guid) { for (RakNetClientPeer peer : clients.values()) { if (peer.getGloballyUniqueId() == guid) { return true; } } return false; } /** * Returns the client with the specified address. * * @param address * the address. * @return the client with the address, <code>null</code> if there is none. */ public final RakNetClientPeer getClient(InetSocketAddress address) { return clients.get(address); } /** * Returns the client with the specified address. * * @param address * the IP address. * @param port * the port. * @return the client with the address, <code>null</code> if there is none. */ public final RakNetClientPeer getClient(InetAddress address, int port) { if (address != null && port > 0x0000 && port < 0xFFFF) { return this.getClient(new InetSocketAddress(address, port)); } return null; } /** * Returns the client with the specified address. * * @param host * the IP address. * @param port * the port. * @return the client with the address, <code>null</code> if there is none. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final RakNetClientPeer getClient(String host, int port) throws UnknownHostException { if (host != null) { return this.getClient(InetAddress.getByName(host), port); } return null; } /** * Returns all clients with the specified IP address. * * @param host * the IP address. * @return the clients with the IP address. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final RakNetClientPeer[] getClient(String host) throws UnknownHostException { ArrayList<RakNetClientPeer> peers = new ArrayList<RakNetClientPeer>(); if (host != null) { InetAddress inetAddress = InetAddress.getByName(host); for (RakNetClientPeer peer : clients.values()) { if (peer.getInetAddress().equals(inetAddress)) { peers.add(peer); } } } return peers.toArray(new RakNetClientPeer[peers.size()]); } /** * Returns all clients with the specified port. * * @param port * the port. * @return the clients with the port. */ public final RakNetClientPeer[] getClient(int port) { if (port < 0x0000 || port > 0xFFFF) { return new RakNetClientPeer[0]; // Invalid port range } ArrayList<RakNetClientPeer> peers = new ArrayList<RakNetClientPeer>(); for (RakNetClientPeer peer : clients.values()) { if (peer.getPort() == port) { peers.add(peer); } } return peers.toArray(new RakNetClientPeer[peers.size()]); } /** * Returns the client with the specified globally unique ID. * * @param guid * the globally unique ID of the client. * @return the client with the globally unique ID, <code>null</code> if * there is none. */ public final RakNetClientPeer getClient(long guid) { for (RakNetClientPeer peer : clients.values()) { if (peer.getGloballyUniqueId() == guid) { return peer; } } return null; } /** * Returns the globally unique ID of the specified peer. * * @param peer * the peer. * @return the globally unique ID of the specified peer, <code>-1</code> if * it does not exist. * @throws NullPointerException * if the <code>peer</code> is <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final long getGuid(RakNetClientPeer peer) throws NullPointerException, IllegalArgumentException { if (peer == null) { throw new NullPointerException("Peer cannot be null"); } RakNetClientPeer clientPeer = clients.get(peer.getAddress()); if (clientPeer != null) { if (clientPeer != peer) { throw new NullPointerException("Peer must be of the server"); } return clientPeer.getGloballyUniqueId(); } return -1L; } /** * Sends a message to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packet. * @param channel * the channel to send the packet on. * @param packet * the packet to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packet</code> are * <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, int channel, Packet packet) throws NullPointerException, IllegalArgumentException { if (reliability == null) { throw new NullPointerException("Reliability cannot be null"); } else if (packet == null) { throw new NullPointerException("Packet cannot be null"); } else if (!this.hasClient(guid)) { throw new IllegalArgumentException("No client with the specified GUID exists"); } return this.getClient(guid).sendMessage(reliability, channel, packet); } /** * Sends a message to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packet. * @param channel * the channel to send the packet on. * @param packet * the packet to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packet</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, Packet packet) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, channel, packet); } /** * Sends messages to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packets. * @param channel * the channel to send the packets on. * @param packets * the packets to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packets</code> are * <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, int channel, Packet... packets) throws NullPointerException, InvalidChannelException { if (packets == null) { throw new NullPointerException("Packets cannot be null"); } EncapsulatedPacket[] encapsulated = new EncapsulatedPacket[packets.length]; for (int i = 0; i < encapsulated.length; i++) { encapsulated[i] = this.sendMessage(guid, reliability, channel, packets[i]); } return encapsulated; } /** * Sends messages to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packets. * @param channel * the channel to send the packets on. * @param packets * the packets to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packets</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, Packet... packets) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, channel, packets); } /** * Sends a message to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packet. * @param packet * the packet to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packet</code> are * <code>null</code>. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, Packet packet) throws NullPointerException { return this.sendMessage(guid, reliability, RakNet.DEFAULT_CHANNEL, packet); } /** * Sends a message to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packet. * @param packet * the packet to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packet</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, Packet packet) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, packet); } /** * Sends messages to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packets. * @param packets * the packets to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packets</code> are * <code>null</code>. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, Packet... packets) throws NullPointerException { return this.sendMessage(guid, reliability, RakNet.DEFAULT_CHANNEL, packets); } /** * Sends messages to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packets. * @param packets * the packets to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packets</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, Packet... packets) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, packets); } /** * Sends a message to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packet. * @param channel * the channel to send the packet on. * @param buf * the buffer to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>buf</code> are * <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, int channel, ByteBuf buf) throws NullPointerException, InvalidChannelException { return this.sendMessage(guid, reliability, channel, new Packet(buf)); } /** * Sends a message to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packet. * @param channel * the channel to send the packet on. * @param buf * the buffer to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>buf</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, ByteBuf buf) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, channel, buf); } /** * Sends messages to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packets. * @param channel * the channel to send the packets on. * @param bufs * the buffers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>bufs</code> are * <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, int channel, ByteBuf... bufs) throws NullPointerException, InvalidChannelException { if (bufs == null) { throw new NullPointerException("Buffers cannot be null"); } EncapsulatedPacket[] encapsulated = new EncapsulatedPacket[bufs.length]; for (int i = 0; i < encapsulated.length; i++) { encapsulated[i] = this.sendMessage(guid, reliability, channel, bufs[i]); } return encapsulated; } /** * Sends messages to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packets. * @param channel * the channel to send the packets on. * @param bufs * the buffers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>bufs</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, ByteBuf... bufs) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, bufs); } /** * Sends messages to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packet. * @param buf * the buffer to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the inexistence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>buf</code> are * <code>null</code>. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, ByteBuf buf) throws NullPointerException { return this.sendMessage(guid, reliability, RakNet.DEFAULT_CHANNEL, buf); } /** * Sends messages to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packet. * @param buf * the buffer to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>buf</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, ByteBuf buf) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, buf); } /** * Sends messages to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the packet. * @param bufs * the buffers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>bufs</code> are * <code>null</code>. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, ByteBuf... bufs) throws NullPointerException { return this.sendMessage(guid, reliability, RakNet.DEFAULT_CHANNEL, bufs); } /** * Sends messages to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the packet. * @param bufs * the buffers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>bufs</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, ByteBuf... bufs) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, bufs); } /** * Sends a message identifier to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the message identifier. * @param channel * the channel to send the message identifier on. * @param packetId * the message identifier to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> is <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, int channel, int packetId) throws NullPointerException, InvalidChannelException { return this.sendMessage(guid, reliability, channel, new RakNetPacket(packetId)); } /** * Sends a message identifier to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the message identifier. * @param channel * the channel to send the message identifier on. * @param packetId * the message identifier to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the<code>peer</code> or <code>reliability</code> are * <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, int packetId) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, channel, packetId); } /** * Sends message identifiers to the specified peer. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the message identifiers. * @param channel * the channel to send the message identifiers on. * @param packetIds * the message identifiers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packetIds</code> are * <code>null</code>. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, int channel, int... packetIds) throws NullPointerException, InvalidChannelException { if (packetIds == null) { throw new NullPointerException("Packet IDs cannot be null"); } EncapsulatedPacket[] encapsulated = new EncapsulatedPacket[packetIds.length]; for (int i = 0; i < encapsulated.length; i++) { encapsulated[i] = this.sendMessage(guid, reliability, channel, packetIds[i]); } return encapsulated; } /** * Sends message identifiers to the specified peer. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the message identifiers. * @param channel * the channel to send the message identifiers on. * @param packetIds * the message identifiers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packetIds</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. * @throws InvalidChannelException * if the channel is higher than or equal to * {@value RakNet#CHANNEL_COUNT}. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, int channel, int... packetIds) throws NullPointerException, IllegalArgumentException, InvalidChannelException { return this.sendMessage(this.getGuid(peer), reliability, channel, packetIds); } /** * Sends a message identifier to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the message identifier. * @param packetId * the message identifier to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> is <code>null</code>. */ public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, int packetId) throws NullPointerException { return this.sendMessage(guid, reliability, new RakNetPacket(packetId)); } /** * Sends a message identifier to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the message identifier. * @param packetId * the message identifier to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> is * <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket sendMessage(RakNetClientPeer peer, Reliability reliability, int packetId) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, packetId); } /** * Sends message identifiers to the specified peer on the default channel. * * @param guid * the globally unique ID of the peer to send the packet to. * @param reliability * the reliability of the message identifiers. * @param packetIds * the message identifiers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>reliability</code> or <code>packetIds</code> are * <code>null</code>. */ public final EncapsulatedPacket[] sendMessage(long guid, Reliability reliability, int... packetIds) throws NullPointerException { return this.sendMessage(guid, reliability, RakNet.DEFAULT_CHANNEL, packetIds); } /** * Sends message identifiers to the specified peer on the default channel. * * @param peer * the peer to send the packet to. * @param reliability * the reliability of the message identifiers. * @param packetIds * the message identifiers to send. * @return the generated encapsulated packet, <code>null</code> if no packet * was sent due to the non existence of the peer with the * <code>guid</code>. This is normally not important, however it can * be used for packet acknowledged and not acknowledged events if * the reliability is of the * {@link Reliability#UNRELIABLE_WITH_ACK_RECEIPT WITH_ACK_RECEIPT} * type. * @throws NullPointerException * if the <code>peer</code>, <code>reliability</code> or * <code>packetIds</code> are <code>null</code>. * @throws IllegalArgumentException * if the <code>peer</code> is not of the server. */ public final EncapsulatedPacket[] sendMessage(RakNetClientPeer peer, Reliability reliability, int... packetIds) throws NullPointerException, IllegalArgumentException { return this.sendMessage(this.getGuid(peer), reliability, packetIds); } /** * Returns whether or not the specified client IP address is banned. * * @param address * the IP address. * @return <code>true</code> if the client address is banned, * <code>false</code> otherwise. */ public final boolean isClientBanned(InetAddress address) { return banned.contains(address); } /** * Returns whether or not the specified client IP address is banned. * * @param host * the IP address. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. * @return <code>true</code> if the client address is banned, * <code>false</code> otherwise. */ public final boolean isClientBanned(String host) throws UnknownHostException { return banned.contains(InetAddress.getByName(host)); } /** * Bans the specified client IP address. * * @param address * the IP address to ban. * @throws NullPointerException * if the <code>address</code> is <code>null</code>. */ public final void ban(InetAddress address) throws NullPointerException { if (address == null) { throw new NullPointerException("IP address cannot be null"); } else if (!banned.contains(address)) { banned.add(address); } } /** * Bans the specified client IP address. * * @param host * the IP address to ban. * @throws NullPointerException * if the <code>host</code> is <code>null</code>. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final void ban(String host) throws NullPointerException, UnknownHostException { if (host == null) { throw new NullPointerException("IP address cannot be null"); } this.ban(InetAddress.getByName(host)); } /** * Unbans the specified client IP address. * * @param address * the IP address to unban. */ public final void unban(InetAddress address) { banned.remove(address); } /** * Unbans the specified client IP address. * * @param host * the IP address to unban. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final void unban(String host) throws UnknownHostException { if (host != null) { this.unban(InetAddress.getByName(host)); } } /** * Disconnects a client from the server. * * @param address * the address of the client. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. */ public final boolean disconnect(InetSocketAddress address, String reason) { RakNetClientPeer peer = clients.remove(address); if (peer == null) { return false; // No client to disconnect } peer.disconnect(); log.debug("Disconnected client with address " + address + " for \"" + (reason == null ? "Disconnected" : reason) + "\""); this.callEvent( listener -> listener.onDisconnect(this, address, peer, reason == null ? "Disconnected" : reason)); return true; } /** * Disconnects a client from the server. * * @param address * the address of the client. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. */ public final boolean disconnect(InetSocketAddress address, Throwable reason) { return this.disconnect(address, reason == null ? null : RakNet.getStackTrace(reason)); } /** * Disconnects a client from the server. * * @param address * the address of the client. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. */ public final boolean disconnect(InetSocketAddress address) { return this.disconnect(address, (String) /* Solves ambiguity */ null); } /** * Disconnects a client from the server. * * @param address * the IP address of the client. * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(InetAddress address, int port, String reason) { if (port < 0x0000 || port > 0xFFFF) { return false; // Invalid port range } return this.disconnect(new InetSocketAddress(address, port), reason); } /** * Disconnects a client from the server. * * @param address * the IP address of the client. * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(InetAddress address, int port, Throwable reason) { if (port < 0x0000 || port > 0xFFFF) { return false; // Invalid port range } return this.disconnect(new InetSocketAddress(address, port), reason); } /** * Disconnects a client from the server. * * @param address * the IP address of the client. * @param port * the port. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(InetAddress address, int port) { return this.disconnect(address, port, (String) /* Solves ambiguity */ null); } /** * Disconnects a client from the server. * * @param host * the IP address of the client. * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host, int port, String reason) throws UnknownHostException { if (host == null) { return false; } return this.disconnect(InetAddress.getByName(host), port, reason); } /** * Disconnects a client from the server. * * @param host * the IP address of the client. * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host, int port, Throwable reason) throws UnknownHostException { if (host == null) { return false; } return this.disconnect(InetAddress.getByName(host), port, reason); } /** * Disconnects a client from the server. * * @param host * the IP address of the client. * @param port * the port. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host, int port) throws UnknownHostException { return this.disconnect(host, port, (String) /* Solves ambiguity */ null); } /** * Disconnects all clients from the server with the address. * * @param address * the IP address of the client. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(InetAddress address, String reason) { if (address == null) { return false; } boolean disconnected = false; for (InetSocketAddress peerAddress : clients.keySet()) { if (address.equals(peerAddress)) { this.disconnect(peerAddress, reason); disconnected = true; } } return disconnected; } /** * Disconnects all clients from the server with the IP address. * * @param address * the IP address of the client. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(InetAddress address) { return this.disconnect(address, null); } /** * Disconnects all clients from the server with the IP address. * * @param host * the IP address of the client. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host, String reason) throws UnknownHostException { if (host == null) { return false; } return this.disconnect(InetAddress.getByName(host), reason); } /** * Disconnects all clients from the server with the IP address. * * @param host * the IP address of the client. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host, Throwable reason) throws UnknownHostException { return this.disconnect(host, reason == null ? null : RakNet.getStackTrace(reason)); } /** * Disconnects all clients from the server with the IP address. * * @param host * the IP address of the client. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. * @throws UnknownHostException * if no IP address for the <code>host</code> could be found, or * if a scope_id was specified for a global IPv6 address. */ public final boolean disconnect(String host) throws UnknownHostException { return this.disconnect(host, (String) /* Solves ambiguity */ null); } /** * Disconnects all clients from the server with the port. * * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(int port, String reason) { if (port < 0x0000 || port > 0xFFFF) { return false; // Invalid port range } boolean disconnected = false; for (InetSocketAddress address : clients.keySet()) { if (address.getPort() == port) { this.disconnect(address, reason == null ? "Disconnected" : reason); disconnected = true; } } return disconnected; } /** * Disconnects all clients from the server with the port. * * @param port * the port. * @param reason * the reason for client disconnection. A <code>null</code> * reason will have <code>"Disconnected"</code> be used as the * reason instead. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(int port, Throwable reason) { return this.disconnect(port, reason == null ? null : RakNet.getStackTrace(reason)); } /** * Disconnects all clients from the server with the port. * * @param port * the port. * @return <code>true</code> if a client was disconnect, <code>false</code> * otherwise. */ public final boolean disconnect(int port) { return this.disconnect(port, (String) /* Solves ambiguity */ null); } /** * Disconnects a client from the server. * * @param peer * the peer of the client to disconnect. * @param reason * the reason for client disconnection. A <code>null</code> value * will have <code>"Disconnected"</code> be used as the reason * instead. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. * @throws IllegalArgumentException * if the given peer is fabricated, meaning that the peer is not * one created by the server but rather one created externally. */ public final boolean disconnect(RakNetClientPeer peer, String reason) throws IllegalArgumentException { if (peer != null) { if (peer.getServer() != this) { throw new IllegalArgumentException("Peer must belong to the server"); } return this.disconnect(peer.getAddress(), reason); } return false; } /** * Disconnects a client from the server. * * @param peer * the peer of the client to disconnect. * @param reason * the reason for client disconnection. A <code>null</code> value * will have <code>"Disconnected"</code> be used as the reason * instead. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. * @throws IllegalArgumentException * if the given peer is fabricated, meaning that the peer is not * one created by the server but rather one created externally. */ public final boolean disconnect(RakNetClientPeer peer, Throwable reason) throws IllegalArgumentException { return this.disconnect(peer, reason == null ? null : RakNet.getStackTrace(reason)); } /** * Disconnects a client from the server. * * @param peer * the peer of the client to disconnect. * @return <code>true</code> if a client was disconnected, * <code>false</code> otherwise. * @throws IllegalArgumentException * if the given peer is fabricated, meaning that the peer is not * one created by the server but rather one created externally. */ public final boolean disconnect(RakNetClientPeer peer) { return this.disconnect(peer, (String) /* Solves ambiguity */ null); } /** * Returns whether or not the specified IP address is blocked. * * @param address * the IP address to check. * @return <code>true</code> if the IP address is blocked, * <code>false</code> otherwise. */ public final boolean isAddressBlocked(InetAddress address) { return handler.isAddressBlocked(address); } /** * Blocks the specified IP address. * <p> * All currently connected clients with the IP address (regardless of port) * will be disconnected with the same reason that the IP address was * blocked. * * @param address * the IP address to block. * @param reason * the reason the address was blocked. A <code>null</code> reason * will have <code>"Address blocked"</code> be used as the reason * instead. * @param time * how long the address will blocked in milliseconds. * @throws NullPointerException * if <code>address</code> is <code>null</code>. */ public final void blockAddress(InetAddress address, String reason, long time) throws NullPointerException { handler.blockAddress(address, reason, time); } /** * Blocks the specified IP address. * <p> * All currently connected clients with the IP address (regardless of port) * will be disconnected with the same reason that the IP address was * blocked. * * @param address * the IP address to block. * @param time * how long the address will blocked in milliseconds. * @throws NullPointerException * if <code>address</code> is <code>null</code>. */ public final void blockAddress(InetAddress address, long time) throws NullPointerException { this.blockAddress(address, null, time); } /** * Unblocks the specified IP address. * * @param address * the IP address to unblock. */ public final void unblockAddress(InetAddress address) { handler.unblockAddress(address); } /** * Called by the {@link RakNetServerHandler} when it catches a * <code>Throwable</code> while handling a packet. * * @param address * the address that caused the exception. * @param cause * the <code>Throwable</code> caught by the handler. * @throws NullPointerException * if the cause <code>address</code> or <code>cause</code> are * <code>null</code>. */ protected final void handleHandlerException(InetSocketAddress address, Throwable cause) throws NullPointerException { if (address == null) { throw new NullPointerException("Address cannot be null"); } else if (cause == null) { throw new NullPointerException("Cause cannot be null"); } else if (this.hasClient(address)) { this.disconnect(address, RakNet.getStackTrace(cause)); } log.warn("Handled exception " + cause.getClass().getName() + " caused by address " + address); this.callEvent(listener -> listener.onHandlerException(this, address, cause)); } /** * Handles a packet received by the {@link RakNetServerHandler}. * * @param sender * the address of the sender. * @param packet * the packet to handle. * @throws NullPointerException * if the <code>sender</code> or <code>packet</code> are * <code>null</code>. */ protected final void handleMessage(InetSocketAddress sender, RakNetPacket packet) throws NullPointerException { if (sender == null) { throw new NullPointerException("Sender cannot be null"); } else if (packet == null) { throw new NullPointerException("Packet cannot be null"); } else if (clients.containsKey(sender)) { clients.get(sender).handleInternal(packet); } else if (packet.getId() == RakNetPacket.ID_UNCONNECTED_PING || packet.getId() == RakNetPacket.ID_UNCONNECTED_PING_OPEN_CONNECTIONS) { UnconnectedPing ping = new UnconnectedPing(packet); ping.decode(); if (!ping.failed() && (packet.getId() == RakNetPacket.ID_UNCONNECTED_PING || (clients.size() < maxConnections || maxConnections < 0)) && broadcastingEnabled == true && ping.magic == true) { ServerPing pingEvent = new ServerPing(sender, ping.connectionType, identifier); this.callEvent(listener -> listener.onPing(this, pingEvent)); if (pingEvent.getIdentifier() != null) { UnconnectedPong pong = new UnconnectedPong(); pong.timestamp = ping.timestamp; pong.pongId = this.pongId; pong.identifier = pingEvent.getIdentifier(); pong.encode(); if (!pong.failed()) { this.sendNettyMessage(pong, sender); } else { log.error(pong.getClass().getSimpleName() + " packet failed to encode"); } } } } else if (packet.getId() == RakNetPacket.ID_OPEN_CONNECTION_REQUEST_1) { OpenConnectionRequestOne connectionRequestOne = new OpenConnectionRequestOne(packet); connectionRequestOne.decode(); if (clients.containsKey(sender)) { if (clients.get(sender).isLoggedIn()) { this.disconnect(sender, "Client reinstantiated connection"); } } if (connectionRequestOne.magic == true) { RakNetPacket errorPacket = this.validateSender(sender, NO_GUID); if (errorPacket == null) { if (connectionRequestOne.networkProtocol != this.getProtocolVersion()) { IncompatibleProtocolVersion incompatibleProtocol = new IncompatibleProtocolVersion(); incompatibleProtocol.networkProtocol = this.getProtocolVersion(); incompatibleProtocol.serverGuid = this.guid; incompatibleProtocol.encode(); this.sendNettyMessage(incompatibleProtocol, sender); } else { if (connectionRequestOne.maximumTransferUnit <= maximumTransferUnit) { OpenConnectionResponseOne connectionResponseOne = new OpenConnectionResponseOne(); connectionResponseOne.serverGuid = this.guid; connectionResponseOne.maximumTransferUnit = this.maximumTransferUnit; connectionResponseOne.encode(); this.sendNettyMessage(connectionResponseOne, sender); } } } else { this.sendNettyMessage(errorPacket, sender); } } } else if (packet.getId() == RakNetPacket.ID_OPEN_CONNECTION_REQUEST_2) { OpenConnectionRequestTwo connectionRequestTwo = new OpenConnectionRequestTwo(packet); connectionRequestTwo.decode(); if (!connectionRequestTwo.failed() && connectionRequestTwo.magic == true) { RakNetPacket errorPacket = this.validateSender(sender, connectionRequestTwo.clientGuid); if (errorPacket == null) { if (connectionRequestTwo.maximumTransferUnit <= maximumTransferUnit) { OpenConnectionResponseTwo connectionResponseTwo = new OpenConnectionResponseTwo(); connectionResponseTwo.serverGuid = this.guid; connectionResponseTwo.maximumTransferUnit = connectionRequestTwo.maximumTransferUnit; connectionResponseTwo.encode(); if (!connectionResponseTwo.failed()) { this.callEvent(listener -> listener.onConnect(this, sender, connectionRequestTwo.connectionType)); clients.put(sender, new RakNetClientPeer(this, connectionRequestTwo.connectionType, connectionRequestTwo.clientGuid, connectionRequestTwo.maximumTransferUnit, channel, sender)); this.sendNettyMessage(connectionResponseTwo, sender); } } } else { this.sendNettyMessage(errorPacket, sender); } } } log.debug("Handled " + RakNetPacket.getName(packet.getId()) + " packet"); } /** * Validates the sender of a packet. * <p> * This is called throughout initial client connection to make sure there * are no issues. * * @param sender * the address of the packet sender. * @param guid * the globally unique ID of the sender, {@value #NO_GUID} if * there is none. * @return the packet to respond with if there was an error, * <code>null</code> if there are no issues. * @throws NullPointerException * if the <code>sender</code> is <code>null</code>. */ private final RakNetPacket validateSender(InetSocketAddress sender, long guid) throws NullPointerException { if (sender == null) { throw new NullPointerException("Sender cannot be null"); } else if (this.hasClient(sender) || (this.hasClient(guid) && guid != NO_GUID)) { return new RakNetPacket(RakNetPacket.ID_ALREADY_CONNECTED); } else if (this.getClientCount() >= maxConnections && maxConnections >= 0) { return new RakNetPacket(RakNetPacket.ID_NO_FREE_INCOMING_CONNECTIONS); } else if (this.isClientBanned(sender.getAddress())) { ConnectionBanned connectionBanned = new ConnectionBanned(); connectionBanned.serverGuid = guid; connectionBanned.encode(); return connectionBanned; } return null; } /** * Sends a Netty message over the channel raw. * <p> * This should be used sparingly, as if it is used incorrectly it could * break client peers entirely. In order to send a message to a peer, use * one of the * {@link com.whirvis.jraknet.peer.RakNetPeer#sendMessage(com.whirvis.jraknet.protocol.Reliability, io.netty.buffer.ByteBuf) * sendMessage()} methods. * * @param buf * the buffer to send. * @param address * the address to send the buffer to. * @throws NullPointerException * if the <code>buf</code>, <code>address</code>, or IP address * of the <code>address</code> are <code>null</code>. */ public final void sendNettyMessage(ByteBuf buf, InetSocketAddress address) throws NullPointerException { if (buf == null) { throw new NullPointerException("Buffer cannot be null"); } else if (address == null) { throw new NullPointerException("Address cannot be null"); } else if (address.getAddress() == null) { throw new NullPointerException("IP address cannot be null"); } channel.writeAndFlush(new DatagramPacket(buf, address)); log.debug("Sent netty message with size of " + buf.capacity() + " bytes (" + (buf.capacity() * 8) + " bits) to " + address); } /** * Sends a Netty message over the channel raw. * <p> * This should be used sparingly, as if it is used incorrectly it could * break client peers entirely. In order to send a message to a peer, use * one of the * {@link com.whirvis.jraknet.peer.RakNetPeer#sendMessage(Reliability, Packet) * sendMessage()} methods. * * @param packet * the packet to send. * @param address * the address to send the packet to. * @throws NullPointerException * if the <code>packet</code>, <code>address</code>, or IP * address of the <code>address</code> are <code>null</code>. */ public final void sendNettyMessage(Packet packet, InetSocketAddress address) throws NullPointerException { if (packet == null) { throw new NullPointerException("Packet cannot be null"); } this.sendNettyMessage(packet.buffer(), address); } /** * Sends a Netty message over the channel raw. * <p> * This should be used sparingly, as if it is used incorrectly it could * break client peers entirely. In order to send a message to a peer, use * one of the * {@link com.whirvis.jraknet.peer.RakNetPeer#sendMessage(com.whirvis.jraknet.protocol.Reliability, int) * sendMessage()} methods. * * @param packetId * the packet ID to send. * @param address * the address to send the packet to. * @throws NullPointerException * if the <code>address</code> or IP address of the * <code>address</code> are <code>null</code>. */ public final void sendNettyMessage(int packetId, InetSocketAddress address) throws NullPointerException { this.sendNettyMessage(new RakNetPacket(packetId), address); } /** * Returns whether or not the server is running. * * @return <code>true</code> if the server is running, <code>false</code> * otherwise. */ public final boolean isRunning() { return this.running; } /** * Starts the server. * * @throws IllegalStateException * if the server is already running. * @throws RakNetException * if an error occurs during startup. */ public void start() throws IllegalStateException, RakNetException { if (running == true) { throw new IllegalStateException("Server is already running"); } else if (listeners.isEmpty()) { log.warn("Server has no listeners"); } try { this.bootstrap = new Bootstrap(); this.group = new NioEventLoopGroup(); this.handler = new RakNetServerHandler(this); bootstrap.handler(handler); // Create bootstrap and bind channel bootstrap.channel(NioDatagramChannel.class).group(group); bootstrap.option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_REUSEADDR, false) .option(ChannelOption.SO_SNDBUF, maximumTransferUnit) .option(ChannelOption.SO_RCVBUF, maximumTransferUnit); this.channel = (bindingAddress != null ? bootstrap.bind(bindingAddress) : bootstrap.bind(0)).sync() .channel(); this.bindAddress = (InetSocketAddress) channel.localAddress(); this.running = true; log.debug("Created and bound bootstrap"); // Create and start peer update thread RakNetServer server = this; this.peerThread = new Thread( RakNetServer.class.getSimpleName() + "-Peer-Thread-" + Long.toHexString(guid).toUpperCase()) { @Override public void run() { HashMap<RakNetClientPeer, Throwable> disconnected = new HashMap<RakNetClientPeer, Throwable>(); while (server.running == true && !this.isInterrupted()) { try { Thread.sleep(0, 1); // Lower CPU usage } catch (InterruptedException e) { this.interrupt(); // Interrupted during sleep continue; } for (RakNetClientPeer peer : clients.values()) { if (!peer.isDisconnected()) { try { peer.update(); if (peer.getPacketsReceivedThisSecond() >= RakNet.getMaxPacketsPerSecond()) { server.blockAddress(peer.getInetAddress(), "Too many packets", RakNet.MAX_PACKETS_PER_SECOND_BLOCK); } } catch (Throwable throwable) { server.callEvent(listener -> listener.onPeerException(server, peer, throwable)); disconnected.put(peer, throwable); } } } /* * Disconnect peers. * * This must be done here as simply removing a client * from the clients map would be an incorrect way of * disconnecting a client. This means that calling the * disconnect() method is required. However, calling it * while in the loop would cause a * ConcurrentModifactionException. To get around this, * the clients that need to be disconnected are properly * disconnected after the loop is finished. This is done * simply by having them and their disconnect reason be * put in a disconnection map. */ if (disconnected.size() > 0) { for (RakNetClientPeer peer : disconnected.keySet()) { server.disconnect(peer, disconnected.get(peer)); } disconnected.clear(); } } } }; peerThread.start(); log.debug("Created and started peer update thread"); this.callEvent(listener -> listener.onStart(this)); } catch (InterruptedException e) { this.running = false; throw new RakNetException(e); } log.info("Started server"); } /** * Stops the server. * <p> * All currently connected clients will be disconnected with the same reason * used for shutdown. * * @param reason * the reason for shutdown. A <code>null</code> reason will have * <code>"Server shutdown"</code> be used as the reason instead. * @throws IllegalStateException * if the server is not running. */ public void shutdown(String reason) throws IllegalStateException { if (running == false) { throw new IllegalStateException("Server is not running"); } // Disconnect clients for (RakNetClientPeer client : clients.values()) { this.disconnect(client, reason == null ? "Server shutdown" : reason); } clients.clear(); // Stop server this.running = false; peerThread.interrupt(); log.info("Shutdown server"); // Shutdown networking channel.close(); group.shutdownGracefully(0L, 1000L, TimeUnit.MILLISECONDS); this.channel = null; this.handler = null; this.group = null; this.bootstrap = null; log.debug("Shutdown networking"); this.callEvent(listener -> listener.onShutdown(this)); } /** * Stops the server. * <p> * All currently connected clients will be disconnected with the same reason * used for shutdown. * * @param reason * the reason for shutdown. A <code>null</code> reason will have * <code>"Server shutdown"</code> be used as the reason instead. * @throws IllegalStateException * if the server is not running. */ public final void shutdown(Throwable reason) throws IllegalStateException { this.shutdown(reason != null ? RakNet.getStackTrace(reason) : null); } /** * Stops the server. * <p> * All currently connected clients will be disconnected with the same reason * used for shutdown. * * @throws IllegalStateException * if the server is not running. */ public final void shutdown() throws IllegalStateException { this.shutdown((String) /* Solves ambiguity */ null); } @Override public String toString() { return "RakNetServer [bindingAddress=" + bindingAddress + ", guid=" + guid + ", log=" + log + ", pongId=" + pongId + ", timestamp=" + timestamp + ", maximumTransferUnit=" + maximumTransferUnit + ", maxConnections=" + maxConnections + ", broadcastingEnabled=" + broadcastingEnabled + ", identifier=" + identifier + ", bindAddress=" + bindAddress + ", running=" + running + ", getProtocolVersion()=" + getProtocolVersion() + ", getTimestamp()=" + getTimestamp() + ", getAddress()=" + getAddress() + ", getClientCount()=" + getClientCount() + "]"; } }