net.marfgamer.jraknet.session.RakNetSession.java Source code

Java tutorial

Introduction

Here is the source code for net.marfgamer.jraknet.session.RakNetSession.java

Source

/*
 *       _   _____            _      _   _          _   
 *      | | |  __ \          | |    | \ | |        | |  
 *      | | | |__) |   __ _  | | __ |  \| |   ___  | |_ 
 *  _   | | |  _  /   / _` | | |/ / | . ` |  / _ \ | __|
 * | |__| | | | \ \  | (_| | |   <  | |\  | |  __/ | |_ 
 *  \____/  |_|  \_\  \__,_| |_|\_\ |_| \_|  \___|  \__|
 *                                                  
 * The MIT License (MIT)
 *
 * Copyright (c) 2016, 2017 Trent "MarfGamer" 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 net.marfgamer.jraknet.session;

import static net.marfgamer.jraknet.protocol.MessageIdentifier.*;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.socket.DatagramPacket;
import net.marfgamer.jraknet.Packet;
import net.marfgamer.jraknet.RakNet;
import net.marfgamer.jraknet.RakNetLogger;
import net.marfgamer.jraknet.RakNetPacket;
import net.marfgamer.jraknet.protocol.ConnectionType;
import net.marfgamer.jraknet.protocol.MessageIdentifier;
import net.marfgamer.jraknet.protocol.Reliability;
import net.marfgamer.jraknet.protocol.message.CustomPacket;
import net.marfgamer.jraknet.protocol.message.EncapsulatedPacket;
import net.marfgamer.jraknet.protocol.message.acknowledge.Acknowledge;
import net.marfgamer.jraknet.protocol.message.acknowledge.AcknowledgeType;
import net.marfgamer.jraknet.protocol.message.acknowledge.Record;
import net.marfgamer.jraknet.protocol.status.ConnectedPing;
import net.marfgamer.jraknet.protocol.status.ConnectedPong;
import net.marfgamer.jraknet.util.map.IntMap;

/**
 * This class is used to easily manage connections in RakNet.
 *
 * @author Trent "MarfGamer" Summerlin
 */
public abstract class RakNetSession implements UnumRakNetPeer, GeminusRakNetPeer {

    // Session data
    private String loggerName;
    private final ConnectionType connectionType;
    private final long guid;
    private final int maximumTransferUnit;
    private final Channel channel;
    private final InetSocketAddress address;
    private RakNetState state;

    // Timing
    private int packetsSentThisSecond;
    private int packetsReceivedThisSecond;
    private long lastPacketCounterResetTime;
    private long lastPacketSendTime;
    private long lastPacketReceiveTime;
    private long lastRecoverySendTime;
    private long lastKeepAliveSendTime;
    private long lastPingSendTime;

    // Packet data
    private int messageIndex;
    private int splitId;
    private final ArrayList<Integer> reliablePackets;
    private final IntMap<SplitPacket> splitQueue;
    /**
     * Cleared by <code>RakNetServerSession</code> to help make sure the
     * <code>ID_DISCONNECTION_NOTIFICATION</code> packet is sent out
     */
    protected final ArrayList<EncapsulatedPacket> sendQueue;
    private final IntMap<EncapsulatedPacket[]> recoveryQueue;
    private final HashMap<EncapsulatedPacket, Integer> ackReceiptPackets;

    // Ordering and sequencing
    private int sendSequenceNumber;
    private int receiveSequenceNumber;
    private final int[] orderSendIndex;
    private final int[] orderReceiveIndex;
    private final int[] sequenceSendIndex;
    private final int[] sequenceReceiveIndex;
    private final IntMap<IntMap<EncapsulatedPacket>> handleQueue;

    // Latency detection
    private boolean latencyEnabled;
    private int pongsReceived;
    private long latencyIdentifier;
    private long totalLatency;
    private long latency;
    private long lastLatency;
    private long lowestLatency;
    private long highestLatency;

    /**
     * Constructs a <code>RakNetSession</code> with the specified globally
     * unique ID, maximum transfer unit, <code>Channel</code>, and address.
     * 
     * @param connectionType
     *            the connection type of the session.
     * @param guid
     *            the globally unique ID.
     * @param maximumTransferUnit
     *            the maximum transfer unit.
     * @param channel
     *            the <code>Channel</code>.
     * @param address
     *            the address.
     */
    public RakNetSession(ConnectionType connectionType, long guid, int maximumTransferUnit, Channel channel,
            InetSocketAddress address) {
        // Session data
        this.loggerName = "session #" + guid;
        this.connectionType = connectionType;
        this.guid = guid;
        this.maximumTransferUnit = maximumTransferUnit;
        this.channel = channel;
        this.address = address;
        this.state = RakNetState.DISCONNECTED;

        // Timing
        this.lastPacketReceiveTime = System.currentTimeMillis();

        // Packet data
        this.reliablePackets = new ArrayList<Integer>();
        this.splitQueue = new IntMap<SplitPacket>();
        this.sendQueue = new ArrayList<EncapsulatedPacket>();
        this.recoveryQueue = new IntMap<EncapsulatedPacket[]>();
        this.ackReceiptPackets = new HashMap<EncapsulatedPacket, Integer>();

        // Ordering and sequencing
        this.orderSendIndex = new int[RakNet.MAX_CHANNELS];
        this.orderReceiveIndex = new int[RakNet.MAX_CHANNELS];
        this.sequenceSendIndex = new int[RakNet.MAX_CHANNELS];
        this.sequenceReceiveIndex = new int[RakNet.MAX_CHANNELS];
        this.handleQueue = new IntMap<IntMap<EncapsulatedPacket>>();
        for (int i = 0; i < RakNet.MAX_CHANNELS; i++) {
            sequenceReceiveIndex[i] = -1;
            handleQueue.put(i, new IntMap<EncapsulatedPacket>());
        }

        // Latency detection
        this.latencyEnabled = true;
        this.latency = -1; // We can't predict a player's latency
        this.lastLatency = -1;
        this.lowestLatency = -1;
        this.highestLatency = -1;
    }

    /**
     * @return the connection type of the session.
     */
    public final ConnectionType getConnectionType() {
        return this.connectionType;
    }

    /**
     * @return the session's globally unique ID.
     */
    public final long getGloballyUniqueId() {
        return this.guid;
    }

    /**
     * @return the session's address.
     */
    public final InetSocketAddress getAddress() {
        return this.address;
    }

    /**
     * @return the session's <code>InetAddress</code>.
     */
    public final InetAddress getInetAddress() {
        return address.getAddress();
    }

    /**
     * @return the session's port.
     */
    public final int getInetPort() {
        return address.getPort();
    }

    /**
     * @return the session's maximum transfer unit.
     */
    public int getMaximumTransferUnit() {
        return this.maximumTransferUnit;
    }

    /**
     * @return the session's current state.
     */
    public RakNetState getState() {
        return this.state;
    }

    /**
     * Sets the session's current state.
     * 
     * @param state
     *            the new state.
     */
    public void setState(RakNetState state) {
        this.state = state;
        RakNetLogger.debug(loggerName, "set state to " + state);
    }

    /**
     * @return the amount of packets sent this second.
     */
    public int getPacketsSentThisSecond() {
        return this.packetsSentThisSecond;
    }

    /**
     * @return the amount of packets received this second.
     */
    public int getPacketsReceivedThisSecond() {
        return this.packetsReceivedThisSecond;
    }

    /**
     * @return the last time a packet was sent by the session.
     */
    public long getLastPacketSendTime() {
        return this.lastPacketSendTime;
    }

    /**
     * @return the last time a packet was received from the session.
     */
    public long getLastPacketReceiveTime() {
        return this.lastPacketReceiveTime;
    }

    /**
     * Bumps the message index and returns the new one, this should only be
     * called by the <code>SplitPacket</code> and <code>RakNetSession</code>
     * classes.
     * 
     * @return the new message index.
     */
    protected int bumpMessageIndex() {
        RakNetLogger.debug(loggerName, "Bumped message index from " + messageIndex + " to " + (messageIndex + 1));
        return this.messageIndex++;
    }

    /**
     * Enables/disables latency detection, when disabled the latency will always
     * return -1. If the session is not yet in the keep alive state then the
     * packets needed to detect the latency will not be sent until then.
     * 
     * @param enabled
     *            whether or not latency detection is enabled
     */
    public void enableLatencyDetection(boolean enabled) {
        this.latencyEnabled = enabled;
        this.latency = (enabled ? this.latency : -1);
        this.pongsReceived = (enabled ? this.pongsReceived : 0);
        RakNetLogger.info(loggerName, (enabled ? "Enabled" : "Disabled") + " latency detection.");
    }

    /**
     * @return whether or not latency detection is enabled.
     */
    public boolean latencyDetectionEnabled() {
        return this.latencyEnabled;
    }

    /**
     * @return the average latency for the session.
     */
    public long getLatency() {
        return this.latency;
    }

    /**
     * @return the last latency for the session.
     */
    public long getLastLatency() {
        return this.lastLatency;
    }

    /**
     * @return the lowest recorded latency for the session.
     */
    public long getLowestLatency() {
        return this.lowestLatency;
    }

    /**
     * @return the highest recorded latency for the session.
     */
    public long getHighestLatency() {
        return this.highestLatency;
    }

    @Override
    public final EncapsulatedPacket sendMessage(Reliability reliability, int channel, Packet packet)
            throws InvalidChannelException {
        // Make sure channel doesn't exceed RakNet limit
        if (channel >= RakNet.MAX_CHANNELS) {
            throw new InvalidChannelException();
        }

        // Set packet properties
        EncapsulatedPacket encapsulated = new EncapsulatedPacket();
        encapsulated.reliability = reliability;
        encapsulated.orderChannel = (byte) channel;
        encapsulated.payload = packet;
        if (reliability.isReliable()) {
            encapsulated.messageIndex = this.bumpMessageIndex();
        }
        if (reliability.isOrdered() || reliability.isSequenced()) {
            encapsulated.orderIndex = (reliability.isOrdered() ? this.orderSendIndex[channel]++
                    : this.sequenceSendIndex[channel]++);
            RakNetLogger.debug(loggerName, "Bumped " + (reliability.isOrdered() ? "order" : "sequence")
                    + " index from "
                    + ((reliability.isOrdered() ? this.orderSendIndex[channel] : this.sequenceSendIndex[channel])
                            - 1)
                    + " to "
                    + (reliability.isOrdered() ? this.orderSendIndex[channel] : this.sequenceSendIndex[channel]));
        }

        // Do we need to split the packet?
        synchronized (sendQueue) {
            if (SplitPacket.needsSplit(reliability, packet, this.maximumTransferUnit)) {
                encapsulated.splitId = ++this.splitId % 65536;
                for (EncapsulatedPacket split : SplitPacket.splitPacket(this, encapsulated)) {
                    sendQueue.add(split);
                }
                RakNetLogger.debug(loggerName,
                        "Split encapsulated packet " + encapsulated.splitId + " and added it to the send queue");
            } else {
                sendQueue.add(encapsulated);
                RakNetLogger.debug(loggerName, "Added encapsulated packet to the send queue");
            }
        }
        RakNetLogger.debug(loggerName, "Sent packet with size of " + packet.size() + " bytes ("
                + (packet.size() * 8) + " bits) with reliability " + reliability + " on channel " + channel);

        /*
         * We return a copy of the encapsulated packet because if a single
         * variable is modified in the encapsulated packet before it is sent,
         * the whole API could break.
         */
        return encapsulated.clone();
    }

    @Override
    public final EncapsulatedPacket sendMessage(long guid, Reliability reliability, int channel, Packet packet)
            throws InvalidChannelException {
        if (this.guid == guid) {
            return this.sendMessage(reliability, channel, packet);
        } else {
            throw new IllegalArgumentException("Invalid GUID");
        }
    }

    /**
     * Used to tell the session to assign the given packets to an ACK receipt to
     * be used for when an ACK or NACK arrives.
     * 
     * @param packets
     *            the packets.
     */
    public final void setAckReceiptPackets(EncapsulatedPacket[] packets) {
        for (EncapsulatedPacket packet : packets) {
            EncapsulatedPacket clone = packet.getClone();
            if (!clone.reliability.requiresAck()) {
                throw new IllegalArgumentException("Invalid reliability " + packet.reliability);
            }
            clone.ackRecord = packet.ackRecord;
            ackReceiptPackets.put(clone, clone.ackRecord.getIndex());
        }
    }

    /**
     * Sends a raw message.
     * 
     * @param packet
     *            The packet to send.
     */
    public final void sendRawMessage(Packet packet) {
        channel.writeAndFlush(new DatagramPacket(packet.buffer(), this.address));
        RakNetLogger.debug(loggerName,
                "Sent raw packet with size of " + packet.size() + " bytes (" + (packet.size() * 8) + " bits)");
    }

    /**
     * Sends a raw message
     * 
     * @param buf
     *            the buffer to send.
     */
    public final void sendRawMessage(ByteBuf buf) {
        channel.writeAndFlush(new DatagramPacket(buf, this.address));
        RakNetLogger.debug(loggerName,
                "Sent raw buffer with size of " + buf.capacity() + " bytes (" + (buf.capacity() * 8) + " bits)");
    }

    /**
     * Sends a <code>CustomPacket</code> with the specified
     * <code>EncapsulatedPacket</code>'s.
     * 
     * @param encapsulated
     *            the encapsulated packets to send.
     * @param updateRecoveryQueue
     *            whether or not to store the encapsulated packets in the
     *            recovery queue for later, only set this to <code>true</code>
     *            if you are sending new data and not resending old data.
     * @return the sequence number of the <code>CustomPacket</code>.
     */
    private final int sendCustomPacket(ArrayList<EncapsulatedPacket> encapsulated, boolean updateRecoveryQueue) {
        // Create CustomPacket
        CustomPacket custom = new CustomPacket();
        custom.sequenceNumber = this.sendSequenceNumber++;
        custom.messages = encapsulated;
        custom.session = this;
        custom.encode();

        // Send packet
        this.sendRawMessage(custom);

        // Do we need to store it for recovery?
        synchronized (recoveryQueue) {
            if (updateRecoveryQueue == true) {
                // Make sure unreliable data is discarded
                custom.removeUnreliables();
                if (custom.messages.size() > 0) {
                    recoveryQueue.put(custom.sequenceNumber,
                            custom.messages.toArray(new EncapsulatedPacket[custom.messages.size()]));
                }
            }
        }

        // Update packet data
        this.packetsSentThisSecond++;
        this.lastPacketSendTime = System.currentTimeMillis();
        RakNetLogger.debug(loggerName, "Sent custom packet with sequence number " + custom.sequenceNumber);
        return custom.sequenceNumber;
    }

    /**
     * Sends a <code>CustomPacket</code> with the specified
     * <code>EncapsulatedPacket</code>'s
     * 
     * @param encapsulated
     *            The encapsulated packets to send
     * @param updateRecoveryQueue
     *            Whether or not to store the encapsulated packets in the
     *            recovery queue for later, only set this to <code>true</code>
     *            if you are sending new data and not resending old data
     * @return The sequence number of the <code>CustomPacket</code>
     */
    private final int sendCustomPacket(EncapsulatedPacket[] encapsulated, boolean updateRecoveryQueue) {
        ArrayList<EncapsulatedPacket> encapsulatedArray = new ArrayList<EncapsulatedPacket>();
        for (EncapsulatedPacket message : encapsulated) {
            encapsulatedArray.add(message);
        }
        return this.sendCustomPacket(encapsulatedArray, updateRecoveryQueue);
    }

    /**
     * Sends an <code>Acknowledge</code> packet with the specified type and
     * <code>Record</code>s.
     * 
     * @param type
     *            the type of the <code>Acknowledge</code> packet.
     * @param records
     *            the <code>Record</code>s to send.
     */
    private final void sendAcknowledge(AcknowledgeType type, Record... records) {
        // Create Acknowledge packet
        Acknowledge acknowledge = new Acknowledge(type);
        for (Record record : records) {
            acknowledge.records.add(record);
        }
        acknowledge.encode();
        this.sendRawMessage(acknowledge);

        // Update packet data
        this.lastPacketSendTime = System.currentTimeMillis();
        RakNetLogger.debug(loggerName, "Sent " + acknowledge.records.size() + " records in "
                + (type == AcknowledgeType.ACKNOWLEDGED ? "ACK" : "NACK") + " packet");
    }

    /**
     * Handles a <code>CustomPacket</code>.
     * 
     * @param custom
     *            the <code>CustomPacket</code> to handle.
     */
    public final void handleCustom(CustomPacket custom) {
        // Update packet data
        this.packetsReceivedThisSecond++;

        /*
         * There are three important things to note here:
         */

        /*
         * 1. The reason we subtract one from the difference is because the last
         * sequence number we received should always be one less than the next
         * one
         */

        /*
         * 2. The reason we add one to the last sequence number to the record
         * when the difference is bigger than one is because we have already
         * received that record, this is also the same reason we subtract one
         * from the CustomPacket's sequence number even when the difference is
         * not greater than one
         */

        /*
         * 3. We always generate the NACK response first because the previous
         * sequence number data would be destroyed, making it impossible to
         * generate it
         */

        // Generate NACK queue if needed
        int difference = custom.sequenceNumber - this.receiveSequenceNumber - 1;
        if (difference > 0) {
            if (difference > 1) {
                this.sendAcknowledge(AcknowledgeType.NOT_ACKNOWLEDGED,
                        new Record(this.receiveSequenceNumber + 1, custom.sequenceNumber - 1));
            } else {
                this.sendAcknowledge(AcknowledgeType.NOT_ACKNOWLEDGED, new Record(custom.sequenceNumber - 1));
            }
        }

        // Only handle if it is a newer packet
        if (custom.sequenceNumber > this.receiveSequenceNumber - 1) {
            this.receiveSequenceNumber = custom.sequenceNumber;
            for (EncapsulatedPacket encapsulated : custom.messages) {
                this.handleEncapsulated(encapsulated);
            }

            // Update packet data
            this.lastPacketReceiveTime = System.currentTimeMillis();
        }

        // Send ACK
        this.sendAcknowledge(AcknowledgeType.ACKNOWLEDGED, new Record(custom.sequenceNumber));
        RakNetLogger.debug(loggerName, "Handled custom packet with sequence number " + custom.sequenceNumber);
    }

    /**
     * Handles an <code>Acknowledge</code> packet and responds accordingly.
     * 
     * @param acknowledge
     *            the <code>Acknowledge</code> packet to handle.
     */
    public final void handleAcknowledge(Acknowledge acknowledge) {
        synchronized (recoveryQueue) {
            if (acknowledge.getType().equals(AcknowledgeType.ACKNOWLEDGED)) {
                for (Record record : acknowledge.records) {
                    // Get record data
                    int recordIndex = record.getIndex();

                    // Are any packets associated with an ACK receipt tied to
                    // this record?
                    for (EncapsulatedPacket packet : ackReceiptPackets.keySet()) {
                        int packetRecordIndex = ackReceiptPackets.get(packet).intValue();
                        if (recordIndex == packetRecordIndex) {
                            this.onAcknowledge(record, packet);
                            packet.ackRecord = null;
                            ackReceiptPackets.remove(packet);
                        }
                    }

                    // Remove acknowledged packet from the recovery queue
                    recoveryQueue.remove(recordIndex);
                }
            } else if (acknowledge.getType().equals(AcknowledgeType.NOT_ACKNOWLEDGED)) {
                // Track old sequence numbers so they can be properly renamed
                int[] oldSequenceNumbers = new int[acknowledge.records.size()];
                int[] newSequenceNumbers = new int[oldSequenceNumbers.length];

                for (int i = 0; i < acknowledge.records.size(); i++) {
                    // Get record data
                    Record record = acknowledge.records.get(i);
                    int recordIndex = record.getIndex();

                    // Are any packets associated with an ACK receipt tied to
                    // this record?
                    for (EncapsulatedPacket packet : ackReceiptPackets.keySet()) {
                        int packetRecordIndex = ackReceiptPackets.get(packet).intValue();

                        /*
                         * We only call onNotAcknowledge() for unreliable
                         * packets, as they can be lost. However, reliable
                         * packets will always eventually be received.
                         */
                        if (recordIndex == packetRecordIndex && !packet.reliability.isReliable()) {
                            this.onNotAcknowledge(record, packet);
                            packet.ackRecord = null;
                            ackReceiptPackets.remove(packet);
                        }
                    }

                    // Update records and resend lost packets
                    if (recoveryQueue.containsKey(recordIndex)) {
                        oldSequenceNumbers[i] = recordIndex;
                        newSequenceNumbers[i] = this.sendCustomPacket(recoveryQueue.get(oldSequenceNumbers[i]),
                                false);
                    } else {
                        oldSequenceNumbers[i] = -1;
                        newSequenceNumbers[i] = -1;
                    }
                }

                // Rename lost packets
                for (int i = 0; i < oldSequenceNumbers.length; i++) {
                    if (oldSequenceNumbers[i] != -1) {
                        recoveryQueue.renameKey(oldSequenceNumbers[i], newSequenceNumbers[i]);
                    }
                }
            }
        }

        // Update packet data
        this.lastPacketReceiveTime = System.currentTimeMillis();
        RakNetLogger.debug(loggerName,
                "Handled " + (acknowledge.getType() == AcknowledgeType.ACKNOWLEDGED ? "ACK" : "NACK")
                        + " packet with " + acknowledge.records.size() + " records");
    }

    /**
     * Handles an <code>EncapsulatedPacket</code> and makes sure all the data is
     * handled correctly.
     * 
     * @param encapsulated
     *            the <code>EncapsualtedPacket</code> to handle.
     */
    private final void handleEncapsulated(EncapsulatedPacket encapsulated) {
        Reliability reliability = encapsulated.reliability;

        // Put together split packet
        if (encapsulated.split == true) {
            if (!splitQueue.containsKey(encapsulated.splitId)) {
                // Prevent queue from overflowing
                if (splitQueue.size() + 1 > RakNet.MAX_SPLITS_PER_QUEUE) {
                    // Remove unreliable packets from the queue
                    Iterator<SplitPacket> splitPackets = splitQueue.values().iterator();
                    while (splitPackets.hasNext()) {
                        SplitPacket splitPacket = splitPackets.next();
                        if (!splitPacket.getReliability().isReliable()) {
                            splitPackets.remove();
                        }
                    }

                    // The queue is filled with reliable packets
                    if (splitQueue.size() + 1 > RakNet.MAX_SPLITS_PER_QUEUE) {
                        throw new SplitQueueOverloadException();
                    }
                }
                splitQueue.put(encapsulated.splitId,
                        new SplitPacket(encapsulated.splitId, encapsulated.splitCount, encapsulated.reliability));
            }

            SplitPacket splitPacket = splitQueue.get(encapsulated.splitId);
            Packet finalPayload = splitPacket.update(encapsulated);
            if (finalPayload == null) {
                return; // Do not handle, the split packet is not complete
            }

            /*
             * It is safe to set the payload here because the old payload is no
             * longer needed and split EncapsulatedPackets share the exact same
             * data except for split data and payload.
             */
            encapsulated.payload = finalPayload;
            splitQueue.remove(encapsulated.splitId);
        }

        // Make sure we are not handling a duplicate
        if (reliability.isReliable()) {
            if (reliablePackets.contains(encapsulated.messageIndex)) {
                return; // Do not handle, it is a duplicate
            }
            reliablePackets.add(encapsulated.messageIndex);
        }

        // Make sure we are handling everything in an ordered/sequenced fashion
        int orderIndex = encapsulated.orderIndex;
        int orderChannel = encapsulated.orderChannel;
        if (orderChannel >= RakNet.MAX_CHANNELS) {
            throw new InvalidChannelException();
        } else {
            // Channel is valid, it is safe to handle
            if (reliability.isOrdered()) {
                handleQueue.get(orderChannel).put(orderIndex, encapsulated);
                while (handleQueue.get(orderChannel).containsKey(orderReceiveIndex[orderChannel])) {
                    EncapsulatedPacket orderedEncapsulated = handleQueue.get(orderChannel)
                            .get(orderReceiveIndex[orderChannel]++);
                    handleQueue.get(orderChannel).remove(orderReceiveIndex[orderChannel] - 1);
                    this.handleMessage0(encapsulated.orderChannel, new RakNetPacket(orderedEncapsulated.payload));
                }
            } else if (reliability.isSequenced()) {
                if (orderIndex > sequenceReceiveIndex[orderChannel]) {
                    sequenceReceiveIndex[orderChannel] = orderIndex;
                    this.handleMessage0(encapsulated.orderChannel, new RakNetPacket(encapsulated.payload));
                }
            } else {
                this.handleMessage0(encapsulated.orderChannel, new RakNetPacket(encapsulated.payload));
            }
        }
        RakNetLogger.debug(loggerName,
                "Handled encapsulated packet with " + encapsulated.reliability + " reliability");
    }

    /**
     * Handles an internal packet related to RakNet, if the ID is unrecognized
     * it is passed on to the underlying session class.
     * 
     * @param channel
     *            the channel the packet was sent on.
     * @param packet
     *            the packet.
     */
    private final void handleMessage0(int channel, RakNetPacket packet) {
        short packetId = packet.getId();

        if (packetId == ID_CONNECTED_PING) {
            ConnectedPing ping = new ConnectedPing(packet);
            ping.decode();

            ConnectedPong pong = new ConnectedPong();
            pong.identifier = ping.identifier;
            pong.encode();
            this.sendMessage(Reliability.UNRELIABLE, pong);
        } else if (packetId == ID_CONNECTED_PONG) {
            ConnectedPong pong = new ConnectedPong(packet);
            pong.decode();

            if (latencyEnabled == true) {
                if (latencyIdentifier - pong.identifier == 1) {
                    long latencyRaw = (this.lastPacketReceiveTime - this.lastPingSendTime);

                    // Get last latency result
                    this.lastLatency = latencyRaw;

                    // Get lowest and highest latency
                    if (this.pongsReceived == 0) {
                        this.lowestLatency = latencyRaw;
                        this.highestLatency = latencyRaw;
                    } else {
                        if (latencyRaw < lowestLatency) {
                            this.lowestLatency = latencyRaw;
                        } else if (latencyRaw > highestLatency) {
                            this.highestLatency = latencyRaw;
                        }
                    }

                    // Get average latency
                    this.pongsReceived++;
                    this.totalLatency += latencyRaw;
                    this.latency = (totalLatency / pongsReceived);
                }
            }

            this.latencyIdentifier = (pong.identifier + 1);
        } else {
            this.handleMessage(packet, channel);
        }

        if (MessageIdentifier.hasPacket(packet.getId())) {
            RakNetLogger.debug(loggerName, "Handled internal packet with ID "
                    + MessageIdentifier.getName(packet.getId()) + " (" + packet.getId() + ")");
        } else {
            RakNetLogger.debug(loggerName, "Sent packet with ID " + packet.getId() + " to session handler");
        }
    }

    /**
     * Updates the session.
     */
    public final void update() {
        long currentTime = System.currentTimeMillis();

        // Send packets in the send queue
        synchronized (sendQueue) {
            if (!sendQueue.isEmpty() && this.packetsSentThisSecond < RakNet.getMaxPacketsPerSecond()) {
                ArrayList<EncapsulatedPacket> send = new ArrayList<EncapsulatedPacket>();
                int sendLength = CustomPacket.calculateDummy();

                // Add packets
                Iterator<EncapsulatedPacket> sendQueueI = sendQueue.iterator();
                while (sendQueueI.hasNext()) {
                    // Make sure the packet will not cause an overflow
                    EncapsulatedPacket encapsulated = sendQueueI.next();
                    if (encapsulated == null) {
                        sendQueueI.remove();
                        continue;
                    }
                    sendLength += encapsulated.calculateSize();
                    if (sendLength > this.maximumTransferUnit) {
                        break;
                    }

                    // Add the packet and remove it from the queue
                    send.add(encapsulated);
                    sendQueueI.remove();
                }

                // Send packet
                if (send.size() > 0) {
                    this.sendCustomPacket(send, true);
                }
            }
        }

        // Resend lost packets
        synchronized (recoveryQueue) {
            Iterator<EncapsulatedPacket[]> recovering = recoveryQueue.values().iterator();
            if (currentTime - this.lastRecoverySendTime >= RakNet.RECOVERY_SEND_INTERVAL && recovering.hasNext()) {
                this.sendCustomPacket(recovering.next(), false);
                this.lastRecoverySendTime = currentTime;
            }
        }

        // Send ping to detect latency if it is enabled
        if (this.latencyEnabled == true && currentTime - this.lastPingSendTime >= RakNet.PING_SEND_INTERVAL
                && state.equals(RakNetState.CONNECTED)) {
            ConnectedPing ping = new ConnectedPing();
            ping.identifier = this.latencyIdentifier++;
            ping.encode();

            this.sendMessage(Reliability.UNRELIABLE, ping);
            this.lastPingSendTime = currentTime;
            RakNetLogger.debug(loggerName,
                    "Sent ping to session with latency identifier " + (latencyIdentifier - 1));
        }

        // Make sure the client is still connected
        if (currentTime - this.lastPacketReceiveTime >= RakNet.DETECTION_SEND_INTERVAL
                && currentTime - this.lastKeepAliveSendTime >= RakNet.DETECTION_SEND_INTERVAL
                && state.equals(RakNetState.CONNECTED)) {
            this.sendMessage(Reliability.UNRELIABLE, ID_DETECT_LOST_CONNECTIONS);
            this.lastKeepAliveSendTime = currentTime;
            RakNetLogger.debug(loggerName,
                    "Sent " + MessageIdentifier.getName(ID_DETECT_LOST_CONNECTIONS) + " packet to session");
        }

        // Client timed out
        if (currentTime - this.lastPacketReceiveTime >= RakNet.SESSION_TIMEOUT) {
            throw new TimeoutException();
        }

        // Reset packet data
        if (currentTime - this.lastPacketCounterResetTime >= 1000L) {
            this.packetsSentThisSecond = 0;
            this.packetsReceivedThisSecond = 0;
            this.lastPacketCounterResetTime = currentTime;
        }
    }

    /**
     * This function is called when a acknowledge receipt is received for the
     * packet.
     * 
     * @param record
     *            the received record.
     * @param packet
     *            the received packet.
     */
    public abstract void onAcknowledge(Record record, EncapsulatedPacket packet);

    /**
     * This function is called when a not acknowledged receipt is received for
     * the packet.
     * 
     * @param record
     *            the lost record.
     * @param packet
     *            the lost packet.
     */
    public abstract void onNotAcknowledge(Record record, EncapsulatedPacket packet);

    /**
     * This function is called when a packet is received by the session.
     * 
     * @param packet
     *            the packet to handle.
     * @param channel
     *            the packet the channel was sent on.
     */
    public abstract void handleMessage(RakNetPacket packet, int channel);

}