com.whirvis.jraknet.RakNet.java Source code

Java tutorial

Introduction

Here is the source code for com.whirvis.jraknet.RakNet.java

Source

/*
 *    __     ______     ______     __  __     __   __     ______     ______  
 *   /\ \   /\  == \   /\  __ \   /\ \/ /    /\ "-.\ \   /\  ___\   /\__  _\
 *  _\_\ \  \ \  __<   \ \  __ \  \ \  _"-.  \ \ \-.  \  \ \  __\   \/_/\ \/  
 * /\_____\  \ \_\ \_\  \ \_\ \_\  \ \_\ \_\  \ \_\\"\_\  \ \_____\    \ \_\ 
 * \/_____/   \/_/ /_/   \/_/\/_/   \/_/\/_/   \/_/ \/_/   \/_____/     \/_/                                                                          
 *
 * 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;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.UUID;

import com.dosse.upnp.UPnP;
import com.whirvis.jraknet.identifier.Identifier;
import com.whirvis.jraknet.protocol.connection.IncompatibleProtocolVersion;
import com.whirvis.jraknet.protocol.connection.OpenConnectionRequestOne;
import com.whirvis.jraknet.protocol.connection.OpenConnectionResponseOne;
import com.whirvis.jraknet.protocol.status.UnconnectedPing;
import com.whirvis.jraknet.protocol.status.UnconnectedPong;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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;

/**
 * The main RakNet component class, containing protocol information and utility
 * methods.
 *
 * @author Trent "Whirvis" Summerlin
 * @since JRakNet v1.0.0
 */
public final class RakNet {

    /**
     * A <code>Thread</code> which runs in the background to allow for code
     * relating to UPnP to be executed through the WaifUPnP without locking up
     * the main thread.
     * 
     * @author Trent "Whirvis" Summerlin
     * @since JRakNet v2.11.0
     * @see #onFinish(Runnable)
     * @see #wasSuccessful()
     */
    public static class UPnPResult extends Thread {

        protected boolean finished;
        protected Runnable runnable;
        protected boolean success;

        /**
         * Sets the callback for when the task has finished executing.
         * <p>
         * This callback method will be run on the same thread as the original
         * task.
         * 
         * @param runnable
         *            the callback.
         */
        public void onFinish(Runnable runnable) {
            this.runnable = runnable;
        }

        /**
         * Returns whether or not the UPnP task was successful.
         * 
         * @return <code>true</code> if the UPnP task was successful,
         *         <code>false</code> otherwise.
         * @throws IllegalStateException
         *             if the UPnP code is still being executed.
         */
        public boolean wasSuccessful() throws IllegalStateException {
            if (finished == false) {
                throw new IllegalStateException("UPnP code is still being executed");
            }
            return this.success;
        }

    }

    /**
     * Used by the
     * {@link RakNet#createBootstrapAndSend(InetSocketAddress, Packet, long, int)}
     * method to wait for a packet.
     *
     * @author Trent "Whirvis" Summerlin
     * @since JRakNet v1.0.0
     */
    private static final class BootstrapHandler extends ChannelInboundHandlerAdapter {

        public volatile RakNetPacket packet;

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof DatagramPacket) {
                this.packet = new RakNetPacket(((DatagramPacket) msg).content().retain());
            }
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.flush();
        }

    }

    /**
     * The amount of times the {@link #isServerOnline(InetSocketAddress)} and
     * {@link #isServerCompatible(InetSocketAddress)} methods will attempt to
     * ping the server before giving up.
     */
    public static final int PING_RETRIES = 5;

    /**
     * The timestamp the {@link #isServerOnline(InetSocketAddress)} and
     * {@link #isServerCompatible(InetSocketAddress)} methods will use as the
     * ping timestamp.
     */
    public static final long PING_TIMESTAMP = System.currentTimeMillis();

    /**
     * The ping ID that the {@link #isServerOnline(InetSocketAddress)} and
     * {@link #isServerCompatible(InetSocketAddress)} methods will use as the
     * ping ID.
     */
    public static final long PING_ID = UUID.randomUUID().getLeastSignificantBits();

    /**
     * The amount of times the {@link #getServerIdentifier(InetSocketAddress)}
     * method will attempt to retrieve the server identifier before giving up.
     */
    public static final int IDENTIFIER_RETRIES = 3;

    /**
     * The current supported server network protocol.
     */
    public static final int SERVER_NETWORK_PROTOCOL = 9;

    /**
     * The current supported client network protocol.
     */
    public static final int CLIENT_NETWORK_PROTOCOL = 9;

    /**
     * The minimum maximum transfer unit size.
     */
    public static final int MINIMUM_MTU_SIZE = 400;

    /**
     * The amount of available channels there are to send packets on.
     */
    public static final int CHANNEL_COUNT = 32;

    /**
     * The default channel packets are sent on.
     */
    public static final byte DEFAULT_CHANNEL = 0;

    /**
     * The amount of time in milliseconds an address will be blocked if it sends
     * too many packets in one second.
     */
    public static final long MAX_PACKETS_PER_SECOND_BLOCK = 300000L;

    private static final HashMap<InetAddress, Integer> MAXIMUM_TRANSFER_UNIT_SIZES = new HashMap<InetAddress, Integer>();
    private static int _lowestMaximumTransferUnitSize = -1;
    private static long _maxPacketsPerSecond = 500;

    private RakNet() {
        // Static class
    }

    /**
     * Sleeps the current thread for the specified amount of time in
     * milliseconds.
     * <p>
     * If an <code>InterruptedException</code> is caught during the sleep,
     * <code>Thread.currentThread().interrupt()</code> will automatically be
     * called.
     * 
     * @param time
     *            the amount of time the thread should sleep in milliseconds.
     * @return <code>true</code> if the current thread was interrupted during
     *         the sleep, <code>false</code> otherwise.
     */
    public static boolean sleep(long time) {
        try {
            Thread.sleep(time);
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return true;
        }
    }

    /**
     * Returns whether or not the specified IP address is a local address.
     * 
     * @param address
     *            the IP address.
     * @return <code>true</code> if the address is a local address,
     *         <code>false</code> otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> is <code>null</code>.
     */
    public static boolean isLocalAddress(InetAddress address) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        byte[] ab = address.getAddress();
        if (address.isSiteLocalAddress()) {
            return true; // Local site address
        } else if (ab.length >= 4) {
            if (ab[0] == 127 && ab[1] == 0 && ab[2] == 0 && ab[3] >= 1 && ab[3] <= 8) {
                return true; // Address is in range of 127.0.0.1-127.0.0.8
            }
        } else if (ab.length >= 16) {
            if (ab[0] == 0 && ab[1] == 0 && ab[2] == 0 && ab[3] == 0 && ab[4] == 0 && ab[5] == 0 && ab[6] == 0
                    && ab[7] == 0 && ab[8] == 0 && ab[9] == 0 && ab[10] == 0 && ab[11] == 0 && ab[12] == 0
                    && ab[13] == 0 && ab[14] == 0 && ab[15] == 1) {
                return true; // Address is equal to 0:0:0:0:0:0:0:1
            }
        }
        return false;
    }

    /**
     * Returns whether or not the specified IP address is a local address.
     * 
     * @param host
     *            the IP address.
     * @return <code>true</code> if the address is a local address,
     *         <code>false</code> otherwise.
     * @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 static boolean isLocalAddress(String host) throws NullPointerException, UnknownHostException {
        if (host == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isLocalAddress(InetAddress.getByName(host));
    }

    /**
     * Returns whether or not the address is a local address.
     * 
     * @param address
     *            the address.
     * @return <code>true</code> if the address is a local address,
     *         <code>false</code> otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> or the IP address of the
     *             <code>address</code> are <code>null</code>.
     */
    public static boolean isLocalAddress(InetSocketAddress address) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("Address cannot be null");
        } else if (address.getAddress() == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isLocalAddress(address.getAddress());
    }

    /**
     * Converts the stack trace of the specified <code>Throwable</code> to a
     * string.
     * 
     * @param throwable
     *            the <code>Throwable</code> to get the stack trace from.
     * @return the stack trace as a string.
     * @throws NullPointerException
     *             if the <code>throwable</code> is <code>null</code>.
     */
    public static String getStackTrace(Throwable throwable) throws NullPointerException {
        if (throwable == null) {
            throw new NullPointerException("Throwable cannot be null");
        }
        ByteArrayOutputStream stackTraceOut = new ByteArrayOutputStream();
        PrintStream stackTracePrint = new PrintStream(stackTraceOut);
        throwable.printStackTrace(stackTracePrint);
        String printedStackTrace = new String(stackTraceOut.toByteArray());
        if (printedStackTrace.endsWith("\n")) {
            printedStackTrace = printedStackTrace.substring(0, printedStackTrace.length() - 2);
        }
        return printedStackTrace;
    }

    /**
     * Forwards the specified UDP port via
     * <a href="https://en.wikipedia.org/wiki/Universal_Plug_and_Play">UPnP</a>.
     * <p>
     * In order for this method to work,
     * <a href="https://en.wikipedia.org/wiki/Universal_Plug_and_Play">UPnP</a>
     * for the router must be enabled. The way to enable this varies depending
     * on the router. There is no guarantee this method will successfully
     * forward the specified UDP port; as it is completely dependent on the
     * gateway (the router in this case) to do so.
     * <p>
     * This is not a blocking method. However, the code required to accomplish
     * the task can up to three seconds to execute. As a result, it is
     * encapsulated within another thread so as to prevent unnecessary blocking.
     * If one wishes to get the result of the code, use the
     * {@link UPnPResult#wasSuccessful()} method found inside of
     * {@link UPnPResult}. A callback for when the task finishes can also be set
     * using the {@link UPnPResult#onFinish(Runnable)} method.
     * 
     * @param port
     *            the port to forward.
     * @return the result of the execution.
     * @throws IllegalArgumentException
     *             if the port is not within the range of <code>0-65535</code>.
     */
    public static synchronized UPnPResult forwardPort(int port) throws IllegalArgumentException {
        if (port < 0x0000 || port > 0xFFFF) {
            throw new IllegalArgumentException("Port must be in between 0-65535");
        }
        UPnPResult result = new UPnPResult() {
            @Override
            public void run() {
                this.setName("jraknet-port-forwarder-" + port);
                this.success = UPnP.openPortUDP(port);
                this.finished = true;
                if (runnable != null) {
                    runnable.run();
                }
            }
        };
        result.start();
        return result;
    }

    /**
     * Closes the specified UDP port via
     * <a href="https://en.wikipedia.org/wiki/Universal_Plug_and_Play">UPnP</a>.
     * <p>
     * In order for this method to work,
     * <a href="https://en.wikipedia.org/wiki/Universal_Plug_and_Play">UPnP</a>
     * for the router must be enabled. The way to enable this varies depending
     * on the router. There is no guarantee this method will successfully close
     * the specified UDP port; as it is completely dependent on the gateway (the
     * router in this case) to do so.
     * <p>
     * This is not a blocking method. However the code required to accomplish
     * the task can up to three seconds to execute. As a result, it is
     * encapsulated within another thread so as to prevent unnecessary blocking.
     * If one wishes to get the result of the code, use the
     * {@link UPnPResult#wasSuccessful()} method found inside of
     * {@link UPnPResult}. A callback for when the task finishes can also be set
     * using the {@link UPnPResult#onFinish(Runnable)} method.
     * 
     * @param port
     *            the port to close.
     * @return the result of the execution.
     * @throws IllegalArgumentException
     *             if the port is not within the range of <code>0-65535</code>.
     */
    public static synchronized UPnPResult closePort(int port) throws IllegalArgumentException {
        if (port < 0x0000 || port > 0xFFFF) {
            throw new IllegalArgumentException("Port must be in between 0-65535");
        }
        UPnPResult result = new UPnPResult() {
            @Override
            public void run() {
                this.setName("jraknet-port-closer-" + port);
                this.success = UPnP.closePortUDP(port);
                this.finished = true;
                if (runnable != null) {
                    runnable.run();
                }
            }
        };
        result.start();
        return result;
    }

    /**
     * Sends a packet to the specified address.
     * 
     * @param address
     *            the address to send the packet to.
     * @param packet
     *            the packet to send.
     * @param timeout
     *            how long to wait until resending the packet.
     * @param retries
     *            how many times the packet will be sent before giving up.
     * @return the packet received in response, <code>null</code> if no response
     *         was received or the thread was interrupted.
     * @throws NullPointerException
     *             if the <code>address</code>, IP address of the
     *             <code>address</code>, or <code>packet</code> are
     *             <code>null</code>.
     * @throws IllegalArgumentException
     *             if the <code>timeout</code> or <code>retries</code> are less
     *             than or equal to <code>0</code>.
     */
    private static RakNetPacket createBootstrapAndSend(InetSocketAddress address, Packet packet, long timeout,
            int retries) throws NullPointerException, IllegalArgumentException {
        if (address == null) {
            throw new NullPointerException("Address cannot be null");
        } else if (address.getAddress() == null) {
            throw new NullPointerException("IP address cannot be null");
        } else if (packet == null) {
            throw new NullPointerException("Packet cannot be null");
        } else if (timeout <= 0) {
            throw new IllegalArgumentException("Timeout must be greater than 0");
        } else if (retries <= 0) {
            throw new IllegalArgumentException("Retriest must be greater than 0");
        }

        // Prepare bootstrap
        RakNetPacket received = null;
        EventLoopGroup group = new NioEventLoopGroup();
        int maximumTransferUnit = getMaximumTransferUnit();
        if (maximumTransferUnit < MINIMUM_MTU_SIZE) {
            return null;
        }
        try {
            // Create bootstrap
            Bootstrap bootstrap = new Bootstrap();
            BootstrapHandler handler = new BootstrapHandler();
            bootstrap.group(group).channel(NioDatagramChannel.class).option(ChannelOption.SO_BROADCAST, true)
                    .option(ChannelOption.SO_RCVBUF, maximumTransferUnit)
                    .option(ChannelOption.SO_SNDBUF, maximumTransferUnit).handler(handler);
            Channel channel = bootstrap.bind(0).sync().channel();

            // Wait for response
            while (retries > 0 && received == null && !Thread.currentThread().isInterrupted()) {
                long sendTime = System.currentTimeMillis();
                channel.writeAndFlush(new DatagramPacket(packet.buffer(), address));
                while (System.currentTimeMillis() - sendTime < timeout && handler.packet == null)
                    ; // Wait for either a timeout or a response
                received = handler.packet;
                retries--;
            }
        } catch (InterruptedException e) {
            return null;
        }
        group.shutdownGracefully();
        return received;
    }

    /**
     * Returns whether or not the server with the specified address is online.
     * 
     * @param address
     *            the address of the server.
     * @return <code>true</code> if the server is online, <code>false</code>
     *         otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> or the IP address of the
     *             <code>address</code> are <code>null</code>.
     */
    public static boolean isServerOnline(InetSocketAddress address) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("Address cannot be null");
        } else if (address.getAddress() == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        OpenConnectionRequestOne connectionRequestOne = new OpenConnectionRequestOne();
        connectionRequestOne.maximumTransferUnit = MINIMUM_MTU_SIZE;
        connectionRequestOne.networkProtocol = CLIENT_NETWORK_PROTOCOL;
        connectionRequestOne.encode();
        RakNetPacket packet = createBootstrapAndSend(address, connectionRequestOne, 1000, PING_RETRIES);
        if (packet != null) {
            if (packet.getId() == RakNetPacket.ID_OPEN_CONNECTION_REPLY_1) {
                OpenConnectionResponseOne connectionResponseOne = new OpenConnectionResponseOne(packet);
                connectionResponseOne.decode();
                if (connectionResponseOne.magic == true) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns whether or not the server with the specified address is online.
     * 
     * @param address
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return <code>true</code> if the server is online, <code>false</code>
     *         otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> is <code>null</code>.
     */
    public static boolean isServerOnline(InetAddress address, int port) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isServerOnline(new InetSocketAddress(address, port));
    }

    /**
     * Returns whether or not the server with the specified address is online.
     * 
     * @param host
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return <code>true</code> if the server is online, <code>false</code>
     *         otherwise.
     * @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 static boolean isServerOnline(String host, int port) throws NullPointerException, UnknownHostException {
        if (host == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isServerOnline(InetAddress.getByName(host), port);
    }

    /**
     * Returns whether or not the server with the specified address is
     * compatible with the current client protocol.
     * 
     * @param address
     *            the address of the server.
     * @return <code>true</code> if the server is compatible with the current
     *         client protocol, <code>false</code> otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> or the IP address of the
     *             <code>address</code> are <code>null</code>.
     */
    public static boolean isServerCompatible(InetSocketAddress address) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("Address cannot be null");
        } else if (address.getAddress() == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        OpenConnectionRequestOne connectionRequestOne = new OpenConnectionRequestOne();
        connectionRequestOne.maximumTransferUnit = MINIMUM_MTU_SIZE;
        connectionRequestOne.networkProtocol = CLIENT_NETWORK_PROTOCOL;
        connectionRequestOne.encode();
        RakNetPacket packet = createBootstrapAndSend(address, connectionRequestOne, 1000L, PING_RETRIES);
        if (packet != null) {
            if (packet.getId() == RakNetPacket.ID_OPEN_CONNECTION_REPLY_1) {
                OpenConnectionResponseOne connectionResponseOne = new OpenConnectionResponseOne(packet);
                connectionResponseOne.decode();
                if (connectionResponseOne.magic == true) {
                    return true;
                }
            } else if (packet.getId() == RakNetPacket.ID_INCOMPATIBLE_PROTOCOL_VERSION) {
                IncompatibleProtocolVersion incompatibleProtocol = new IncompatibleProtocolVersion(packet);
                incompatibleProtocol.decode();
                return false;
            }
        }
        return false;
    }

    /**
     * Returns whether or not the server with the specified address is
     * compatible with the current client protocol.
     * 
     * @param address
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return <code>true</code> if the server is compatible with the current
     *         client protocol, <code>false</code> otherwise.
     * @throws NullPointerException
     *             if the <code>address</code> is <code>null</code>.
     */
    public static boolean isServerCompatible(InetAddress address, int port) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isServerCompatible(new InetSocketAddress(address, port));
    }

    /**
     * Returns whether or not the server with the specified address is
     * compatible with the current client protocol.
     * 
     * @param host
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return <code>true</code> if the server is compatible with the current
     *         client protocol, <code>false</code> otherwise.
     * @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 static boolean isServerCompatible(String host, int port)
            throws NullPointerException, UnknownHostException {
        if (host == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return isServerCompatible(InetAddress.getByName(host), port);
    }

    /**
     * Returns the identifier of the server with the specified address.
     * 
     * @param address
     *            the address of the server.
     * @return the identifier of the server, <code>null</code> if it could not
     *         be retrieved.
     * @throws NullPointerException
     *             if the <code>address</code> or the IP address of the
     *             <code>address</code> are <code>null</code>.
     */
    public static Identifier getServerIdentifier(InetSocketAddress address) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("Address cannot be null");
        } else if (address.getAddress() == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        UnconnectedPing ping = new UnconnectedPing();
        ping.timestamp = System.currentTimeMillis() - PING_TIMESTAMP;
        ping.pingId = PING_ID;
        ping.encode();
        if (ping.failed()) {
            throw new RuntimeException(UnconnectedPing.class.getSimpleName() + " failed to encode");
        }
        RakNetPacket packet = createBootstrapAndSend(address, ping, 1000, IDENTIFIER_RETRIES);
        if (packet != null) {
            if (packet.getId() == RakNetPacket.ID_UNCONNECTED_PONG) {
                UnconnectedPong pong = new UnconnectedPong(packet);
                pong.decode();
                if (!pong.failed() && pong.magic == true) {
                    return pong.identifier;
                }
            }
        }
        return null;
    }

    /**
     * Returns the identifier of the server with the specified address.
     * 
     * @param address
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return the identifier of the server, <code>null</code> if it could not
     *         be retrieved.
     * @throws NullPointerException
     *             if the <code>address</code> is <code>null</code>.
     */
    public static Identifier getServerIdentifier(InetAddress address, int port) throws NullPointerException {
        if (address == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return getServerIdentifier(new InetSocketAddress(address, port));
    }

    /**
     * Returns the identifier of the server with the specified address.
     * 
     * @param host
     *            the IP address of the server.
     * @param port
     *            the port of the server.
     * @return the identifier of the server, <code>null</code> if it could not
     *         be retrieved.
     * @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 static Identifier getServerIdentifier(String host, int port)
            throws NullPointerException, UnknownHostException {
        if (host == null) {
            throw new NullPointerException("IP address cannot be null");
        }
        return getServerIdentifier(InetAddress.getByName(host), port);
    }

    /**
     * Returns the maximum transfer unit of the network card with the specified
     * address.
     * 
     * @param address
     *            the IP address. A <code>null</code> value will have the lowest
     *            valid maximum transfer unit be returned instead.
     * @return the maximum transfer unit of the network card with the specified
     *         address, <code>-1</code> if it could not be determined.
     * @throws RuntimeException
     *             if an exception is caught when determining the lowest valid
     *             maximum transfer unit size despite the safe checks put in
     *             place.
     */
    public static int getMaximumTransferUnit(InetAddress address) throws RuntimeException {
        // Calculate lowest valid maximum transfer unit
        if (_lowestMaximumTransferUnitSize < 0) {
            try {
                int lowestMaximumTransferUnitSize = Integer.MAX_VALUE;
                Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
                while (networkInterfaces.hasMoreElements()) {
                    NetworkInterface networkInterface = networkInterfaces.nextElement();
                    if (lowestMaximumTransferUnitSize > networkInterface.getMTU()
                            && networkInterface.getMTU() >= 0) {
                        lowestMaximumTransferUnitSize = networkInterface.getMTU();
                    }
                }
                _lowestMaximumTransferUnitSize = lowestMaximumTransferUnitSize;
            } catch (SocketException | NullPointerException e) {
                throw new RuntimeException(e);
            }
        }

        // Get maximum transfer unit for address
        if (address == null) {
            return _lowestMaximumTransferUnitSize;
        } else if (!MAXIMUM_TRANSFER_UNIT_SIZES.containsKey(address)) {
            try {
                int maximumTransferUnit = NetworkInterface.getByInetAddress(address).getMTU();
                if (maximumTransferUnit < 0) {
                    throw new SocketException("Invalid maximum transfer unit with size " + maximumTransferUnit);
                }
                MAXIMUM_TRANSFER_UNIT_SIZES.put(address, maximumTransferUnit);
            } catch (SocketException | NullPointerException e) {
                MAXIMUM_TRANSFER_UNIT_SIZES.put(address, _lowestMaximumTransferUnitSize);
            }
        }
        return MAXIMUM_TRANSFER_UNIT_SIZES.get(address).intValue();
    }

    /**
     * Returns the maximum transfer unit of the network card with the specified
     * address.
     * <p>
     * This method is simply a shorthand for
     * {@link #getMaximumTransferUnit(InetAddress)}, with the port of the
     * <code>address</code> not being used. Instead, the
     * {@link InetSocketAddress#getAddress()} method is called to retrieve the
     * original {@link InetAddress}. If the <code>address</code> is
     * <code>null</code>, no <code>NullPointerException</code> will be thrown as
     * the possibility of a <code>null</code> value is accounted for.
     * 
     * @param address
     *            the address. A <code>null</code> value will have the lowest
     *            valid maximum transfer unit be returned instead.
     * @return the maximum transfer unit of the network card with the specified
     *         address, <code>-1</code> if it could not be determined.
     * @throws RuntimeException
     *             if an exception is caught when determining the lowest valid
     *             maximum transfer unit size despite the safe checks put in
     *             place.
     */
    public static int getMaximumTransferUnit(InetSocketAddress address) {
        return getMaximumTransferUnit(address == null ? null : address.getAddress());
    }

    /**
     * Returns the lowest valid maximum transfer unit among all of the network
     * cards installed on the machine.
     * 
     * @return the lowest valid maximum transfer unit among all of the network
     *         cards installed on the machine.
     */
    public static int getMaximumTransferUnit() {
        return getMaximumTransferUnit((InetAddress) /* Solves ambiguity */ null);
    }

    /**
     * Returns how many packets can be received in the span of a single second
     * before an address is automatically blocked.
     * 
     * @return how many packets can be received in the span of a single second
     *         before an address is automatically blocked.
     */
    public static long getMaxPacketsPerSecond() {
        return _maxPacketsPerSecond;
    }

    /**
     * Sets how many packets can be received in the span of a single second
     * before an address is blocked.
     * <p>
     * One must take caution when setting this value, as setting it to low can
     * cause communication to become impossible.
     * 
     * @param maxPacketsPerSecond
     *            how many packets can be received in the span of a single
     *            second before a peer is blocked.
     * @throws IllegalArgumentException
     *             if <code>maxPacketsPerSecond</code> is negative.
     */
    public static void setMaxPacketsPerSecond(long maxPacketsPerSecond) throws IllegalArgumentException {
        if (maxPacketsPerSecond < 0) {
            throw new IllegalArgumentException("Max packets per second cannot be negative");
        }
        _maxPacketsPerSecond = maxPacketsPerSecond;
    }

    /**
     * Converts the specified ID to a hex string.
     * 
     * @param id
     *            the ID to convert to a hex string.
     * @return the generated hex string.
     */
    public static String toHexStringId(int id) {
        String hexString = Integer.toHexString(id);
        if (hexString.length() % 2 != 0) {
            hexString = "0" + hexString;
        }
        return "0x" + hexString.toUpperCase();
    }

    /**
     * Converts the ID of the specified packet to a hex string.
     * 
     * @param packet
     *            the packet to get the ID from.
     * @return the generated hex string.
     * @throws NullPointerException
     *             if the <code>packet</code> is <code>null</code>.
     */
    public static String toHexStringId(RakNetPacket packet) throws NullPointerException {
        if (packet == null) {
            throw new NullPointerException("Packet cannot be null");
        }
        return toHexStringId(packet.getId());
    }

    /**
     * Parses the specified string to a <code>long</code>.
     * 
     * @param longStr
     *            the string to parse.
     * @return the string as a <code>long</code>, <code>-1</code> if it fails to
     *         parse.
     */
    public static long parseLongPassive(String longStr) {
        try {
            return Long.parseLong(longStr);
        } catch (NumberFormatException e) {
            return -1L; // Failed to parse
        }
    }

    /**
     * Parses the specified string to an <code>int</code>.
     * 
     * @param intStr
     *            the string to parse.
     * @return the string as an <code>int</code>, <code>-1</code> if it fails to
     *         parse.
     */
    public static int parseIntPassive(String intStr) {
        return (int) parseLongPassive(intStr);
    }

    /**
     * Parses the specified string to a <code>short</code>.
     * 
     * @param shortStr
     *            the string to parse.
     * @return the string as a <code>short</code>, <code>-1</code> if fails to
     *         parse.
     */
    public static short parseShortPassive(String shortStr) {
        return (short) parseLongPassive(shortStr);
    }

    /**
     * Parses the specified string to a <code>byte</code>.
     * 
     * @param byteStr
     *            the string to parse.
     * @return the string as a <code>byte</code>, <code>-1</code> if it fails to
     *         parse.
     */
    public static byte parseBytePassive(String byteStr) {
        return (byte) parseLongPassive(byteStr);
    }

    /**
     * Parses a single String as an address and port and converts it to an
     * <code>InetSocketAddress</code>.
     * 
     * @param address
     *            the address to convert.
     * @param defaultPort
     *            the default port to use if one is not.
     * @return the parsed <code>InetSocketAddress</code>.
     * @throws UnknownHostException
     *             if the address is in an invalid format or if the host cannot
     *             be found.
     */
    public static InetSocketAddress parseAddress(String address, int defaultPort) throws UnknownHostException {
        String[] addressSplit = address.split(":");
        if (addressSplit.length == 1 || addressSplit.length == 2) {
            InetAddress inetAddress = InetAddress.getByName(!addressSplit[0].startsWith("/") ? addressSplit[0]
                    : addressSplit[0].substring(1, addressSplit[0].length()));
            int port = (addressSplit.length == 2 ? parseIntPassive(addressSplit[1]) : defaultPort);
            if (port >= 0x0000 && port <= 0xFFFF) {
                return new InetSocketAddress(inetAddress, port);
            } else {
                throw new UnknownHostException("Port number must be between 0-65535");
            }
        } else {
            throw new UnknownHostException("Format must follow address:port");
        }
    }

    /**
     * Parses a single String as an address and port and converts it to an
     * <code>InetSocketAddress</code>.
     * 
     * @param address
     *            the address to convert.
     * @return the parsed <code>InetSocketAddress</code>.
     * @throws UnknownHostException
     *             if the address is in an invalid format, the host cannot be
     *             found, or no port was specifed in the <code>address</code>.
     */
    public static InetSocketAddress parseAddress(String address) throws UnknownHostException {
        try {
            return parseAddress(address, -1 /* If no port specified */);
        } catch (IllegalArgumentException e) {
            throw new UnknownHostException("No port specified in address");
        }
    }

    /**
     * Parses a single String as an address and port and converts it to an
     * <code>InetSocketAddress</code>.
     * 
     * @param address
     *            the address to convert.
     * @param defaultPort
     *            the default port to use if one is not.
     * @return the parsed <code>InetSocketAddress</code>, <code>null</code> if
     *         it fails to parse.
     */
    public static InetSocketAddress parseAddressPassive(String address, int defaultPort) {
        try {
            return parseAddress(address, defaultPort);
        } catch (UnknownHostException e) {
            return null; // Unknown host
        }
    }

    /**
     * Parses a single String as an address and port and converts it to an
     * <code>InetSocketAddress</code>.
     * 
     * @param address
     *            the address to convert.
     * @return the parsed <code>InetSocketAddress</code>, <code>null</code> if
     *         it fails to parse.
     */
    public static InetSocketAddress parseAddressPassive(String address) {
        return parseAddressPassive(address, -1);
    }

}