net.jenet.Peer.java Source code

Java tutorial

Introduction

Here is the source code for net.jenet.Peer.java

Source

/*
 * Copyright (c) 2005 Dizan Vasquez
 * 
 * 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.jenet;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A class representing a remote host. Elements of this class can not be created
 * directly, a call to {@link net.jenet.Host#connect Host.connect} should be
 * used.
 * <p>
 * Once connected, messages are sent to this peer using the {@link #send send}
 * method.
 * 
 * @author Dizan Vasquez
 */
public class Peer {

    enum STATE {
        DISCONNECTED, CONNECTING, ACKNOWLEDGING_CONNECT, CONNECTED, DISCONNECTING, ACKNOWLEDGING_DISCONNECT, ZOMBIE
    }

    private class Acknowledgement {

        protected Command command;

        protected int sentTime;

        public Command getCommand() {
            return command;
        }

        public int getSentTime() {
            return sentTime;
        }
    }

    private static Log LOG = LogFactory.getLog(Peer.class);

    protected ConcurrentLinkedQueue<Acknowledgement> acknowledgements = new ConcurrentLinkedQueue<Acknowledgement>();

    protected InetSocketAddress address;

    protected int challenge;

    protected int channelCount;

    protected Map<Byte, Channel> channels;

    protected PropertiesConfiguration configuration;

    protected int highestRoundTripTimeVariance;

    protected Host host;

    protected int incomingBandwidth;

    protected int incomingBandwidthThrottleEpoch;

    protected int incomingDataTotal;

    protected short incomingPeerID;

    protected int incomingUnsequencedGroup;

    protected int lastReceiveTime;

    protected int lastRoundTripTime;

    protected int lastRoundTripTimeVariance;

    protected int lastSendTime;

    protected int lowestRoundTripTime;

    protected short mtu;

    protected int nextTimeout;

    protected int outgoingBandwidth;

    protected int outgoingBandwidthThrottleEpoch;

    protected int outgoingDataTotal;

    protected short outgoingPeerID;

    protected ConcurrentLinkedQueue<OutgoingCommand> outgoingReliableCommands = new ConcurrentLinkedQueue<OutgoingCommand>();

    protected int outgoingReliableSequenceNumber;

    protected ConcurrentLinkedQueue<OutgoingCommand> outgoingUnreliableCommands = new ConcurrentLinkedQueue<OutgoingCommand>();

    protected int outgoingUnsequencedGroup;

    protected int packetLoss;

    protected int packetLossEpoch;

    protected int packetLossVariance;

    protected int packetsLost;

    protected int packetsSent;

    protected int packetThrottle;

    protected int packetThrottleAcceleration;

    protected int packetThrottleCounter;

    protected int packetThrottleDeceleration;

    protected int packetThrottleEpoch;

    protected int packetThrottleInterval;

    protected int packetThrottleLimit;

    protected int reliableDataInTransit;

    protected int roundTripTime;

    /**
     * < mean round trip time (RTT), in milliseconds, between sending a reliable
     * packet and receiving its acknowledgement
     */
    protected int roundTripTimeVariance;

    protected ConcurrentLinkedQueue<OutgoingCommand> sentReliableCommands = new ConcurrentLinkedQueue<OutgoingCommand>();

    protected ConcurrentLinkedQueue<OutgoingCommand> sentUnreliableCommands = new ConcurrentLinkedQueue<OutgoingCommand>();

    protected STATE state;

    protected int[] unsequencedWindow;

    protected int windowSize;

    /**
     * @param address
     * @param channelCount
     */
    Peer(Host host, InetSocketAddress address, int channelCount) {
        super();
        init(host);
        this.address = address;
        state = STATE.CONNECTING;
        channels = new HashMap<Byte, Channel>();
        challenge = (int) (Math.random() * Integer.MAX_VALUE);
        unsequencedWindow = new int[configuration.getInt("ENET_PEER_UNSEQUENCED_WINDOW_SIZE") / 32];

        if (host.getOutgoingBandwidth() == 0)
            windowSize = configuration.getInt("ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE");
        else
            windowSize = host.getOutgoingBandwidth() / configuration.getInt("ENET_PEER_WINDOW_SIZE_SCALE")
                    * configuration.getInt("ENET_PROTOCOL_MINIMUM_WINDOW_SIZE");

        if (windowSize < configuration.getInt("ENET_PROTOCOL_MINIMUM_WINDOW_SIZE"))
            windowSize = configuration.getInt("ENET_PROTOCOL_MINIMUM_WINDOW_SIZE");
        else if (windowSize > configuration.getInt("ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE"))
            windowSize = configuration.getInt("ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE");
    }

    Event checkTimeouts() {
        Event result = new Event();
        OutgoingCommand outgoingCommand = null;
        for (Iterator<OutgoingCommand> currentCommand = sentReliableCommands.iterator(); currentCommand
                .hasNext();) {
            outgoingCommand = currentCommand.next();

            if (Time.difference(host.getTimeCurrent(), outgoingCommand.getSentTime()) < outgoingCommand
                    .getRoundTripTimeout())
                continue;

            if (outgoingCommand.getRoundTripTimeout() >= outgoingCommand.getRoundTripTimeoutLimit()) {
                reset();
                result.setType(Event.TYPE.DISCONNECTED);
                result.setPeer(this);
                return result;
            }

            if (outgoingCommand.getPacket() != null)
                reliableDataInTransit -= outgoingCommand.getFragmentLength();

            packetsLost++;

            outgoingCommand.setRoundTripTimeout(outgoingCommand.getRoundTripTimeout() * 2);
            outgoingReliableCommands.add(outgoingCommand);
            currentCommand.remove();

            if (sentReliableCommands.size() == 1)
                nextTimeout = outgoingCommand.getSentTime() + outgoingCommand.getRoundTripTimeout();
        }
        return result;
    }

    /**
     * Requests a disconnection from this peer. When the disconnection is
     * completed, {@link Host#service Host.service} will return an
     * {@link Event Event} object having its type set to
     * <code>Event.TYPE.DISCONNECTED</code>
     */
    public void disconnect() {
        Disconnect command = new Disconnect();

        if (isDisconnected() || isDisconnecting() || isZombie())
            return;

        resetQueues();

        command.getHeader().setChannelID((byte) 0xFF);
        command.getHeader().setFlags(Header.FLAG_UNSEQUENCED);

        if (isConnected())
            command.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);

        queueOutgoingCommand(command, null, 0, (short) 0);

        if (isConnected())
            state = STATE.DISCONNECTING;
        else {
            host.flush();
            reset();
        }
    }

    /**
     * Immediately disconnects this peer. Performs instantaneous disconnection
     * of this peer and sends it a disconnection notification which is not
     * guaranteed to arrive. It will not produce an Event in
     * {@link Host#service Host.service}.
     */
    public void disconnectNow() {
        if (isDisconnected())
            return;

        if (!isZombie() && !isDisconnecting()) {
            Disconnect command = new Disconnect();
            command.getHeader().setChannelID((byte) 0xFF);
            command.getHeader().setFlags(Header.FLAG_UNSEQUENCED);
            queueOutgoingCommand(command, null, 0, (short) 0);
            host.flush();
        }

        reset();
    }

    boolean fitsInPacket(IByteSize object) {
        boolean fits = host.getCommandCount() < configuration.getInt("ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS");
        fits &= host.getBufferCount() < configuration.getInt("ENET_BUFFER_MAXIMUM");
        fits &= mtu - host.getPacketSize() >= object.byteSize();
        return fits;
    }

    /**
     * @return Returns the acknowledgements.
     */
    ConcurrentLinkedQueue<Acknowledgement> getAcknowledgements() {
        return acknowledgements;
    }

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

    /**
     * @return Returns the challenge.
     */
    int getChallenge() {
        return challenge;
    }

    /**
     * Returns the number of channels for this peer.
     * 
     * @return number of channels.
     */
    public int getChannelCount() {
        return channelCount;
    }

    /**
     * @return Returns the channels.
     */
    Map<Byte, Channel> getChannels() {
        return channels;
    }

    /**
     * @return Returns the configuration.
     */
    PropertiesConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * @return Returns the highestRoundTripTimeVariance.
     */
    int getHighestRoundTripTimeVariance() {
        return highestRoundTripTimeVariance;
    }

    /**
     * Returns the host this peer is connected to.
     * 
     * @return The host.
     */
    public Host getHost() {
        return host;
    }

    /**
     * Returns the actual incoming bandwidth used to communicate with this peer.
     * 
     * @return The incomingBandwidth.
     */
    public int getIncomingBandwidth() {
        return incomingBandwidth;
    }

    /**
     * @return Returns the incomingBandwidthThrottleEpoch.
     */
    int getIncomingBandwidthThrottleEpoch() {
        return incomingBandwidthThrottleEpoch;
    }

    /**
     * @return Returns the incomingDataTotal.
     */
    int getIncomingDataTotal() {
        return incomingDataTotal;
    }

    /**
     * @return Returns the incomingPeerID.
     */
    short getIncomingPeerID() {
        return incomingPeerID;
    }

    /**
     * @return Returns the incomingUnsequencedGroup.
     */
    int getIncomingUnsequencedGroup() {
        return incomingUnsequencedGroup;
    }

    /**
     * @return Returns the lastReceiveTime.
     */
    int getLastReceiveTime() {
        return lastReceiveTime;
    }

    /**
     * Returns the last round trip time to communicate with this peer. The round
     * trip time is the time elapsed between sending a reliable packet and the
     * reception of the corresponding acknowledge.
     * 
     * @return The time in milliseconds.
     */
    public int getLastRoundTripTime() {
        return lastRoundTripTime;
    }

    /**
     * @return Returns the lastRoundTripTimeVariance.
     */
    int getLastRoundTripTimeVariance() {
        return lastRoundTripTimeVariance;
    }

    /**
     * @return Returns the lastSendTime.
     */
    int getLastSendTime() {
        return lastSendTime;
    }

    /**
     * @return Returns the lowestRoundTripTime.
     */
    int getLowestRoundTripTime() {
        return lowestRoundTripTime;
    }

    /**
     * @return Returns the mtu.
     */
    short getMtu() {
        return mtu;
    }

    /**
     * @return Returns the nextTimeout.
     */
    int getNextTimeout() {
        return nextTimeout;
    }

    /**
     * Returns the actual outgoing bandwidth used to communicate with this peer.
     * 
     * @return The outgoingBandwidth.
     */
    int getOutgoingBandwidth() {
        return outgoingBandwidth;
    }

    /**
     * @return Returns the outgoingBandwidthThrottleEpoch.
     */
    int getOutgoingBandwidthThrottleEpoch() {
        return outgoingBandwidthThrottleEpoch;
    }

    /**
     * @return Returns the outgoingDataTotal.
     */
    int getOutgoingDataTotal() {
        return outgoingDataTotal;
    }

    /**
     * @return Returns the outgoingPeerID.
     */
    short getOutgoingPeerID() {
        return outgoingPeerID;
    }

    /**
     * @return Returns the outgoingReliableCommands.
     */
    ConcurrentLinkedQueue<OutgoingCommand> getOutgoingReliableCommands() {
        return outgoingReliableCommands;
    }

    /**
     * @return Returns the outgoingReliableSequenceNumber.
     */
    int getOutgoingReliableSequenceNumber() {
        return outgoingReliableSequenceNumber;
    }

    /**
     * @return Returns the outgoingUnreliableCommands.
     */
    ConcurrentLinkedQueue<OutgoingCommand> getOutgoingUnreliableCommands() {
        return outgoingUnreliableCommands;
    }

    /**
     * @return Returns the outgoingUnsequencedGroup.
     */
    int getOutgoingUnsequencedGroup() {
        return outgoingUnsequencedGroup;
    }

    /**
     * @return Returns the packetLoss.
     */
    int getPacketLoss() {
        return packetLoss;
    }

    /**
     * @return Returns the packetLossEpoch.
     */
    int getPacketLossEpoch() {
        return packetLossEpoch;
    }

    /**
     * @return Returns the packetLossVariance.
     */
    int getPacketLossVariance() {
        return packetLossVariance;
    }

    /**
     * The total number of packets lost when commincating with this peer.
     * 
     * @return Returns the packetsLost.
     */
    public int getPacketsLost() {
        return packetsLost;
    }

    /**
     * The total number of sent packets.
     * 
     * @return Returns the packetsSent.
     */
    public int getPacketsSent() {
        return packetsSent;
    }

    /**
     * @return Returns the packetThrottle.
     */
    int getPacketThrottle() {
        return packetThrottle;
    }

    /**
     * @return Returns the packetThrottleAcceleration.
     */
    int getPacketThrottleAcceleration() {
        return packetThrottleAcceleration;
    }

    /**
     * @return Returns the packetThrottleCounter.
     */
    int getPacketThrottleCounter() {
        return packetThrottleCounter;
    }

    /**
     * @return Returns the packetThrottleDeceleration.
     */
    int getPacketThrottleDeceleration() {
        return packetThrottleDeceleration;
    }

    /**
     * @return Returns the packetThrottleEpoch.
     */
    int getPacketThrottleEpoch() {
        return packetThrottleEpoch;
    }

    /**
     * @return Returns the packetThrottleInterval.
     */
    int getPacketThrottleInterval() {
        return packetThrottleInterval;
    }

    /**
     * @return Returns the packetThrottleLimit.
     */
    int getPacketThrottleLimit() {
        return packetThrottleLimit;
    }

    /**
     * @return Returns the reliableDataInTransit.
     */
    int getReliableDataInTransit() {
        return reliableDataInTransit;
    }

    /**
     * Returns mean round trip time for reliable packages.
     * 
     * @see #lastRoundTripTime
     * @return the time in milliseconds.
     */
    public int getRoundTripTime() {
        return roundTripTime;
    }

    /**
     * @return Returns the roundTripTimeVariance.
     */
    int getRoundTripTimeVariance() {
        return roundTripTimeVariance;
    }

    /**
     * @return Returns the sentReliableCommands.
     */
    ConcurrentLinkedQueue<OutgoingCommand> getSentReliableCommands() {
        return sentReliableCommands;
    }

    /**
     * @return Returns the sentUnreliableCommands.
     */
    ConcurrentLinkedQueue<OutgoingCommand> getSentUnreliableCommands() {
        return sentUnreliableCommands;
    }

    /**
     * @return Returns the state.
     */
    STATE getState() {
        return state;
    }

    /**
     * @return Returns the unsequencedWindow.
     */
    int[] getUnsequencedWindow() {
        return unsequencedWindow;
    }

    /**
     * @return Returns the windowSize.
     */
    int getWindowSize() {
        return windowSize;
    }

    void init(Host host) {
        this.host = host;
        this.configuration = host.getConfiguration();
        outgoingPeerID = (short) 0xFFFF;
        challenge = 0;
        address = new InetSocketAddress((InetAddress) null, 0);

        state = STATE.DISCONNECTED;

        incomingBandwidth = 0;
        outgoingBandwidth = 0;
        incomingBandwidthThrottleEpoch = 0;
        outgoingBandwidthThrottleEpoch = 0;
        incomingDataTotal = 0;
        outgoingDataTotal = 0;
        lastSendTime = 0;
        lastReceiveTime = 0;
        nextTimeout = 0;
        packetLossEpoch = 0;
        packetsSent = 0;
        packetsLost = 0;
        packetLoss = 0;
        packetLossVariance = 0;
        packetThrottle = configuration.getInt("ENET_PEER_DEFAULT_PACKET_THROTTLE");
        packetThrottleLimit = configuration.getInt("ENET_PEER_PACKET_THROTTLE_SCALE");
        packetThrottleCounter = 0;
        packetThrottleEpoch = 0;
        packetThrottleAcceleration = configuration.getInt("ENET_PEER_PACKET_THROTTLE_ACCELERATION");
        packetThrottleDeceleration = configuration.getInt("ENET_PEER_PACKET_THROTTLE_DECELERATION");
        packetThrottleInterval = configuration.getInt("ENET_PEER_PACKET_THROTTLE_INTERVAL");
        lastRoundTripTime = configuration.getInt("ENET_PEER_DEFAULT_ROUND_TRIP_TIME");
        lowestRoundTripTime = configuration.getInt("ENET_PEER_DEFAULT_ROUND_TRIP_TIME");
        lastRoundTripTimeVariance = 0;
        highestRoundTripTimeVariance = 0;
        roundTripTime = configuration.getInt("ENET_PEER_DEFAULT_ROUND_TRIP_TIME");
        roundTripTimeVariance = 0;
        mtu = host.getMtu();
        reliableDataInTransit = 0;
        outgoingReliableSequenceNumber = 0;
        windowSize = configuration.getInt("ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE");
        incomingUnsequencedGroup = 0;
        outgoingUnsequencedGroup = 0;
    }

    /**
     * Returns true if this peer is connected.
     * 
     * @return the connection status
     */
    public boolean isConnected() {
        return state == STATE.CONNECTED;
    }

    /**
     * Returns true if a connection request has been sent to this peer.
     * 
     * @return the connection status
     */
    public boolean isConnecting() {
        return state == STATE.CONNECTING;
    }

    /**
     * Returns true if this peer has been disconnected. If the peer is
     * disconnected, it will not get in its connected state again.
     * 
     * @return the connection status
     */
    public boolean isDisconnected() {
        return state == STATE.DISCONNECTED;
    }

    /**
     * Returns true if a disconnection request has been sent to this peer.
     * 
     * @return the connection status
     */
    public boolean isDisconnecting() {
        return state == STATE.DISCONNECTING;
    }

    /**
     * Returns true if this peer is disconnecting due to a problem.
     * 
     * @return the connection state
     */
    public boolean isZombie() {
        return state == STATE.ZOMBIE;
    }

    /**
     * Sends a ping to a a peer. This is automatically done by JeNet, so the
     * only reason to call this method is to increase ping frequency.
     */
    public void ping() {
        Ping command = new Ping();
        if (!isConnected())
            return;
        command.getHeader().setChannelID((byte) 0xFF);
        command.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
        queueOutgoingCommand(command, null, 0, (short) 0);
    }

    /**
     * @param command
     * @param sentTime
     */
    void queueAcknowledgement(Command command, int sentTime) {
        Acknowledgement acknowledgement = new Acknowledgement();
        outgoingDataTotal += command.byteSize();
        acknowledgement.sentTime = sentTime;
        acknowledgement.command = command;
        acknowledgements.add(acknowledgement);
    }

    /**
     * @param hostCommand
     * @param packet
     * @param fragmentCount
     */
    IncomingCommand queueIncomingCommand(Command command, Packet packet, int fragmentCount) {
        Channel channel = selectChannel(command.getHeader().getChannelID());
        LinkedList<IncomingCommand> commandList = null;
        int unreliableSequenceNumber = 0;
        boolean freePacket = false;
        if (command instanceof SendFragment || command instanceof SendReliable) {
            commandList = channel.getIncomingReliableCommands();
            for (IncomingCommand incomingCommand : channel.getIncomingReliableCommands())
                if (incomingCommand.getReliableSequenceNumber() <= command.getHeader()
                        .getReliableSequenceNumber()) {
                    if (incomingCommand.getReliableSequenceNumber() < command.getHeader()
                            .getReliableSequenceNumber())
                        freePacket = false;
                    else
                        freePacket = true;
                    break;
                }
        } else if (command instanceof SendUnreliable) {
            commandList = channel.getIncomingUnreliableCommands();
            unreliableSequenceNumber = ((SendUnreliable) command).getUnreliableSequenceNumber();
            if (command.getHeader().getReliableSequenceNumber() < channel.getIncomingReliableSequenceNumber())
                freePacket = true;
            else if (unreliableSequenceNumber <= channel.getIncomingUnreliableSequenceNumber())
                freePacket = true;
            else
                for (IncomingCommand incomingCommand : channel.getIncomingUnreliableCommands())
                    if (incomingCommand.getUnreliableSequenceNumber() <= unreliableSequenceNumber) {
                        if (incomingCommand.getUnreliableSequenceNumber() < unreliableSequenceNumber)
                            freePacket = false;
                        else
                            freePacket = true;
                        break;
                    }
        } else if (command instanceof SendUnsequenced) {
            commandList = channel.getIncomingUnreliableCommands();
        } else {
            freePacket = true;
        }

        if (!freePacket) {
            IncomingCommand incomingCommand = new IncomingCommand();
            incomingCommand.setReliableSequenceNumber(command.getHeader().getReliableSequenceNumber());
            incomingCommand.setUnreliableSequenceNumber(unreliableSequenceNumber);
            incomingCommand.setCommand(command);
            incomingCommand.setFragmentCount(fragmentCount);
            incomingCommand.setFragmentsRemaining(fragmentCount);
            incomingCommand.setPacket(packet);
            incomingCommand.setFragments(null);

            if (fragmentCount > 0)
                incomingCommand.setFragments(new int[fragmentCount + 31 / 32]);

            commandList.add(incomingCommand);
            return incomingCommand;
        }
        return null;
    }

    OutgoingCommand queueOutgoingCommand(Command command, Packet packet, int offset, short length) {
        Channel channel = selectChannel(command.getHeader().getChannelID());
        OutgoingCommand outgoingCommand = new OutgoingCommand();
        outgoingDataTotal += command.getHeader().getCommandLength() + length;

        if (command.getHeader().getChannelID() == (byte) (0xFF)) {
            ++outgoingReliableSequenceNumber;
            outgoingCommand.setReliableSequenceNumber(outgoingReliableSequenceNumber);
            outgoingCommand.setUnreliableSequenceNumber(0);
        } else if ((command.getHeader().getFlags() & Header.FLAG_ACKNOWLEDGE) != 0) {
            channel.setOutgoingReliableSequenceNumber(channel.getOutgoingReliableSequenceNumber() + 1);
            outgoingCommand.setReliableSequenceNumber(channel.getOutgoingReliableSequenceNumber());
            outgoingCommand.setUnreliableSequenceNumber(0);
        } else if ((command.getHeader().getFlags() & Header.FLAG_UNSEQUENCED) != 0) {
            ++outgoingUnsequencedGroup;
            outgoingCommand.setReliableSequenceNumber(0);
            outgoingCommand.setUnreliableSequenceNumber(0);
        } else {
            channel.setOutgoingUnreliableSequenceNumber(channel.getOutgoingUnreliableSequenceNumber() + 1);
            outgoingCommand.setReliableSequenceNumber(channel.getOutgoingReliableSequenceNumber());
            outgoingCommand.setUnreliableSequenceNumber(channel.getOutgoingUnreliableSequenceNumber());
        }

        outgoingCommand.setSentTime(0);
        outgoingCommand.setRoundTripTimeout(0);
        outgoingCommand.setRoundTripTimeoutLimit(0);
        outgoingCommand.setFragmentOffset(offset);
        outgoingCommand.setFragmentLength(length);
        outgoingCommand.setPacket(packet);
        outgoingCommand.setCommand(command);
        outgoingCommand.getCommand().getHeader()
                .setReliableSequenceNumber(outgoingCommand.getReliableSequenceNumber());

        if (packet != null)
            packet.setReferenceCount(packet.getReferenceCount() + 1);

        LOG.debug("Sent " + outgoingCommand.command);
        if ((command.getHeader().getFlags() & Header.FLAG_ACKNOWLEDGE) != 0)
            outgoingReliableCommands.add(outgoingCommand);
        else
            outgoingUnreliableCommands.add(outgoingCommand);

        return outgoingCommand;
    }

    Packet receive(byte channelID) {
        Channel channel = selectChannel(channelID);
        IncomingCommand incomingCommand = null;
        Packet packet;
        LinkedList<IncomingCommand> listRemove = null;

        if (!channel.getIncomingUnreliableCommands().isEmpty()) {
            listRemove = channel.getIncomingUnreliableCommands();
            incomingCommand = channel.getIncomingUnreliableCommands().getFirst();

            if (incomingCommand.getUnreliableSequenceNumber() > 0)
                if (incomingCommand.getReliableSequenceNumber() > channel.getIncomingReliableSequenceNumber())
                    incomingCommand = null;
                else
                    channel.setIncomingUnreliableSequenceNumber(incomingCommand.getUnreliableSequenceNumber());
        }

        if (incomingCommand == null && !channel.getIncomingReliableCommands().isEmpty()) {
            listRemove = channel.getIncomingReliableCommands();
            do {
                incomingCommand = channel.getIncomingReliableCommands().getFirst();
                if (incomingCommand.getFragmentsRemaining() > 0 || incomingCommand
                        .getReliableSequenceNumber() > channel.getIncomingReliableSequenceNumber() + 1)
                    return null;

                if (incomingCommand.getReliableSequenceNumber() <= channel.getIncomingReliableSequenceNumber()) {
                    channel.getIncomingReliableCommands().remove(incomingCommand);
                    incomingCommand = null;
                }
            } while (incomingCommand == null && !channel.getIncomingReliableCommands().isEmpty());

            if (incomingCommand == null)
                return null;

            channel.setIncomingReliableSequenceNumber(incomingCommand.getReliableSequenceNumber());

            if (incomingCommand.getFragmentCount() > 0)
                channel.setIncomingReliableSequenceNumber(
                        incomingCommand.getReliableSequenceNumber() + incomingCommand.getFragmentCount() - 1);

        }

        if (incomingCommand == null)
            return null;

        listRemove.remove(incomingCommand);

        packet = incomingCommand.getPacket();
        return packet;
    }

    /**
     * @param receivedReliableSequenceNumber
     * @param channelID
     * @return
     */
    Command removeSentReliableCommand(int reliableSequenceNumber, byte channelID) {
        OutgoingCommand outgoingCommand = null;

        for (OutgoingCommand currentCommand : sentReliableCommands)
            if (currentCommand.getReliableSequenceNumber() == reliableSequenceNumber
                    && currentCommand.getCommand().getHeader().getChannelID() == channelID) {
                outgoingCommand = currentCommand;
                break;
            }

        if (outgoingCommand == null)
            return null;

        Command commandNumber = outgoingCommand.getCommand();
        sentReliableCommands.remove(outgoingCommand);
        if (outgoingCommand.getPacket() != null)
            reliableDataInTransit -= outgoingCommand.getFragmentLength();

        if (sentReliableCommands.isEmpty())
            return commandNumber;

        outgoingCommand = sentReliableCommands.element();
        nextTimeout = outgoingCommand.getSentTime() + outgoingCommand.getRoundTripTimeout();

        return commandNumber;
    }

    /**
     * Forcefully disconnects this peer. The foreign host represented by this
     * peer is not notified of the disconnection and its connection with the
     * local host will time out.
     */
    public void reset() {
        Object obj = host.getPeers().remove(getIncomingPeerID());
        state = STATE.DISCONNECTED;
        LOG.debug("removing from peers: " + obj);
    }

    void resetQueues() {
        acknowledgements.clear();
        sentReliableCommands.clear();
        sentUnreliableCommands.clear();
        outgoingReliableCommands.clear();
        outgoingUnreliableCommands.clear();
        channels.clear();
    }

    Channel selectChannel(byte channelID) {
        if (channelID != (byte) 0xFF && channelID >= channelCount)
            return null;
        Channel result = channels.get(channelID);
        if (result == null) {
            result = new Channel();
            channels.put(channelID, result);
        }

        return result;
    }

    /**
     * Queues a packet to be sent to this peer.
     * 
     * @param channelID
     *            the channel on which to send
     * @param packet
     *            the packet to send
     * @throws IOException
     *             on failure
     */
    public void send(byte channelID, Packet packet) throws IOException {
        LOG.debug(host.getAddress() + ": sending packet to " + address + " on channel " + channelID);
        Channel channel = selectChannel(channelID);

        Header header = new Header();
        Command command = new SendFragment();
        int fragmentLength;

        if (!isConnected() || channel == null)
            throw new IOException();

        fragmentLength = mtu - header.byteSize() - command.byteSize();
        if (packet.getDataLength() > fragmentLength) {
            int fragmentCount = (packet.getDataLength() + fragmentLength - 1) / fragmentLength;
            int startSequenceNumber = channel.getOutgoingReliableSequenceNumber() + 1;
            int fragmentNumber;
            int fragmentOffset;

            packet.setFlags(Packet.FLAG_RELIABLE);

            for (fragmentNumber = 0, fragmentOffset = 0; fragmentOffset < packet
                    .getDataLength(); ++fragmentNumber, fragmentOffset += fragmentLength) {
                SendFragment sf = new SendFragment();
                command = sf;
                sf.getHeader().setChannelID(channelID);
                sf.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
                sf.setFragmentNumber(fragmentNumber);
                sf.setStartSequenceNumber(startSequenceNumber);
                sf.setFragmentCount(fragmentCount);
                sf.setTotalLength(packet.getDataLength());
                sf.setFragmentOffset(fragmentOffset);

                if (packet.getDataLength() - fragmentOffset < fragmentLength)
                    fragmentLength = packet.getDataLength() - fragmentOffset;

                queueOutgoingCommand(command, packet, fragmentOffset, (short) fragmentLength);
            }
            return;
        }

        if ((packet.getFlags() & Packet.FLAG_RELIABLE) != 0) {
            command = new SendReliable();
            command.getHeader().setChannelID(channelID);
            command.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
        } else if ((packet.getFlags() & Packet.FLAG_UNSEQUENCED) != 0) {
            SendUnsequenced su = new SendUnsequenced();
            command = su;
            su.getHeader().setChannelID(channelID);
            su.getHeader().setFlags(Header.FLAG_UNSEQUENCED);
            su.setUnsequencedGroup(outgoingUnsequencedGroup + 1);
        } else {
            SendUnreliable su = new SendUnreliable();
            command = su;
            su.getHeader().setChannelID(channelID);
            su.getHeader().setFlags((byte) 0);
            su.setUnreliableSequenceNumber(channel.getOutgoingUnreliableSequenceNumber() + 1);
        }

        queueOutgoingCommand(command, packet, 0, (short) packet.getDataLength());
    }

    void sendAcknowledgements() {
        for (Acknowledgement acknowledgement : acknowledgements) {
            Acknowledge command = new Acknowledge();

            if (!fitsInPacket(command))
                break;

            command.getHeader().setChannelID(acknowledgement.getCommand().getHeader().getChannelID());
            command.getHeader().setFlags((byte) 0);

            command.setReceivedReliableSequenceNumber(
                    acknowledgement.getCommand().getHeader().getReliableSequenceNumber());
            command.setReceivedSentTime(acknowledgement.getSentTime());

            host.getCommands().add(command);

            host.buffer(command);

            if (acknowledgement.getCommand() instanceof Disconnect)
                state = STATE.ZOMBIE;

        }
        acknowledgements.clear();
    }

    void sendReliableOutgoingCommands() {
        for (Iterator<OutgoingCommand> currentCommand = outgoingReliableCommands.iterator(); currentCommand
                .hasNext();) {
            OutgoingCommand outgoingCommand = currentCommand.next();

            if (!fitsInPacket(outgoingCommand))
                break;

            if (outgoingCommand.getPacket() != null
                    && reliableDataInTransit + outgoingCommand.getFragmentLength() > windowSize)
                break;

            if (outgoingCommand.getRoundTripTimeout() == 0) {
                outgoingCommand.setRoundTripTimeout(roundTripTime + 4 * roundTripTimeVariance);
                outgoingCommand.setRoundTripTimeoutLimit(
                        outgoingCommand.getRoundTripTimeout() * configuration.getInt("ENET_PEER_TIMEOUT_LIMIT"));
            }

            if (sentReliableCommands.isEmpty())
                nextTimeout = host.getTimeCurrent() + outgoingCommand.getRoundTripTimeout();

            outgoingCommand.sentTime = host.getTimeCurrent();
            host.getCommands().add(outgoingCommand.getCommand());

            if (outgoingCommand.getPacket() != null) {
                int length = outgoingCommand.getCommand().getHeader().getCommandLength();
                length += outgoingCommand.getFragmentLength();
                outgoingCommand.getCommand().getHeader().setCommandLength(length);
                // TODO:Check updating host packetSize
                reliableDataInTransit += outgoingCommand.getFragmentLength();
            }

            host.buffer(outgoingCommand);
            currentCommand.remove();

            sentReliableCommands.add(outgoingCommand);

            packetsSent++;
        }
    }

    void sendUnreliableOutgoingCommands() {
        // TODO: Verify Unreliable sending
        for (Iterator<OutgoingCommand> currentCommand = outgoingUnreliableCommands.iterator(); currentCommand
                .hasNext();) {
            OutgoingCommand outgoingCommand = currentCommand.next();

            if (!fitsInPacket(outgoingCommand))
                break;

            if (outgoingCommand.getPacket() != null) {
                packetThrottleCounter += configuration.getInt("ENET_PEER_PACKET_THROTTLE_COUNTER");
                packetThrottleCounter %= configuration.getInt("ENET_PEER_PACKET_THROTTLE_SCALE");

                if (packetThrottleCounter > packetThrottle) {
                    currentCommand.remove();
                    continue;
                }
            }

            host.buffer(outgoingCommand);
            currentCommand.remove();
            host.getCommands().add(outgoingCommand.getCommand());

            if (outgoingCommand.getPacket() != null)
                sentUnreliableCommands.add(outgoingCommand);
        }
    }

    /**
     * @param address
     *            The address to set.
     */
    void setAddress(InetSocketAddress address) {
        this.address = address;
    }

    /**
     * @param challenge
     *            The challenge to set.
     */
    void setChallenge(int challenge) {
        this.challenge = challenge;
    }

    void setChannelCount(int channelCount) {
        this.channelCount = channelCount;
    }

    /**
     * @param channels
     *            The channels to set.
     */
    void setChannels(Map<Byte, Channel> channels) {
        this.channels = channels;
    }

    /**
     * @param incomingBandwidth
     *            The incomingBandwidth to set.
     */
    void setIncomingBandwidth(int incomingBandwidth) {
        this.incomingBandwidth = incomingBandwidth;
    }

    /**
     * @param incomingBandwidthThrottleEpoch
     *            The incomingBandwidthThrottleEpoch to set.
     */
    void setIncomingBandwidthThrottleEpoch(int incomingBandwidthThrottleEpoch) {
        this.incomingBandwidthThrottleEpoch = incomingBandwidthThrottleEpoch;
    }

    /**
     * @param incomingDataTotal
     *            The incomingDataTotal to set.
     */
    void setIncomingDataTotal(int incomingDataTotal) {
        this.incomingDataTotal = incomingDataTotal;
    }

    /**
     * @param incomingPeerID
     *            The incomingPeerID to set.
     */
    void setIncomingPeerID(short incomingPeerID) {
        this.incomingPeerID = incomingPeerID;
    }

    /**
     * @param incomingUnsequencedGroup
     *            The incomingUnsequencedGroup to set.
     */
    void setIncomingUnsequencedGroup(int incomingUnsequencedGroup) {
        this.incomingUnsequencedGroup = incomingUnsequencedGroup;
    }

    /**
     * @param lastReceiveTime
     *            The lastReceiveTime to set.
     */
    void setLastReceiveTime(int lastReceiveTime) {
        this.lastReceiveTime = lastReceiveTime;
    }

    /**
     * @param lastSendTime
     *            The lastSendTime to set.
     */
    void setLastSendTime(int lastSendTime) {
        this.lastSendTime = lastSendTime;
    }

    /**
     * @param mtu
     *            The mtu to set.
     */
    void setMtu(short mtu) {
        this.mtu = mtu;
    }

    /**
     * @param outgoingBandwidth
     *            The outgoingBandwidth to set.
     */
    void setOutgoingBandwidth(int outgoingBandwidth) {
        this.outgoingBandwidth = outgoingBandwidth;
    }

    /**
     * @param outgoingBandwidthThrottleEpoch
     *            The outgoingBandwidthThrottleEpoch to set.
     */
    void setOutgoingBandwidthThrottleEpoch(int outgoingBandwidthThrottleEpoch) {
        this.outgoingBandwidthThrottleEpoch = outgoingBandwidthThrottleEpoch;
    }

    /**
     * @param outgoingDataTotal
     *            The outgoingDataTotal to set.
     */
    void setOutgoingDataTotal(int outgoingDataTotal) {
        this.outgoingDataTotal = outgoingDataTotal;
    }

    /**
     * @param outgoingPeerID
     *            The outgoingPeerID to set.
     */
    void setOutgoingPeerID(short outgoingPeerID) {
        this.outgoingPeerID = outgoingPeerID;
    }

    /**
     * @param packetLossEpoch
     *            The packetLossEpoch to set.
     */
    void setPacketLossEpoch(int packetLossEpoch) {
        this.packetLossEpoch = packetLossEpoch;
    }

    /**
     * @param packetThrottle
     *            The packetThrottle to set.
     */
    void setPacketThrottle(int packetThrottle) {
        this.packetThrottle = packetThrottle;
    }

    /**
     * @param packetThrottleAcceleration
     *            The packetThrottleAcceleration to set.
     */
    void setPacketThrottleAcceleration(int packetThrottleAcceleration) {
        this.packetThrottleAcceleration = packetThrottleAcceleration;
    }

    /**
     * @param packetThrottleDeceleration
     *            The packetThrottleDeceleration to set.
     */
    void setPacketThrottleDeceleration(int packetThrottleDeceleration) {
        this.packetThrottleDeceleration = packetThrottleDeceleration;
    }

    /**
     * @param packetThrottleInterval
     *            The packetThrottleInterval to set.
     */
    void setPacketThrottleInterval(int packetThrottleInterval) {
        this.packetThrottleInterval = packetThrottleInterval;
    }

    /**
     * @param packetThrottleLimit
     *            The packetThrottleLimit to set.
     */
    void setPacketThrottleLimit(int packetThrottleLimit) {
        this.packetThrottleLimit = packetThrottleLimit;
    }

    /**
     * @param state
     *            The state to set.
     */
    void setState(STATE state) {
        LOG.debug(host.getAddress() + ": peer " + address + " changed state to " + state);
        this.state = state;
    }

    /**
     * @param windowSize
     *            The windowSize to set.
     */
    void setWindowSize(int windowSize) {
        this.windowSize = windowSize;
    }

    /**
     * @param roundTripTime2
     */
    void throttle(int rtt) {
        if (lastRoundTripTime <= lastRoundTripTimeVariance)
            packetThrottle = packetThrottleLimit;
        else if (rtt < lastRoundTripTime) {
            packetThrottle += packetThrottleAcceleration;
            if (packetThrottle > packetThrottleLimit)
                packetThrottle = packetThrottleLimit;
        } else if (rtt > lastRoundTripTime * 2 * lastRoundTripTimeVariance)
            if (packetThrottle > packetThrottleDeceleration)
                packetThrottle -= packetThrottleDeceleration;
            else
                packetThrottle = 0;
    }

    /**
     * @param interval
     *                      The time in milliseconds to calculate the mean RTT
     * @param acceleration
     *                      The rate to increase the throttle as mean RTT increases
     * @param deceleration
     *                      The rate to decrease the throttle as mean RTT decreases
     */
    public void throttleConfigure(int interval, int acceleration, int deceleration) {
        ThrottleConfigure command = new ThrottleConfigure();
        packetThrottleInterval = interval;
        packetThrottleAcceleration = acceleration;
        packetThrottleDeceleration = deceleration;

        command.setPacketThrottleInterval(interval);
        command.setPacketThrottleAcceleration(acceleration);
        command.setPacketThrottleDeceleration(deceleration);

        command.getHeader().setChannelID((byte) 0xFF);
        command.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
        queueOutgoingCommand(command, null, 0, (short) 0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this).append("address", address).append("state", state).toString();
    }

    void updatePacketLossVariance(int currentTime) {
        int lossDifference = packetsLost * configuration.getInt("ENET_PEER_PACKET_LOSS_SCALE") / packetsSent
                - packetLoss;
        packetLossVariance -= packetLossVariance / 4;

        if (lossDifference >= 0) {
            packetLoss += lossDifference / 8;
            packetLossVariance += lossDifference / 4;
        } else {
            packetLoss += lossDifference / 8;
            packetLossVariance -= lossDifference / 4;
        }

        packetLossEpoch = currentTime;
        packetsSent = 0;
        packetsLost = 0;
    }

    void updateRoundTripTimeVariance(int time) {
        roundTripTimeVariance -= roundTripTimeVariance / 4;
        if (time >= roundTripTime) {
            roundTripTime += (time - roundTripTime) / 8;
            roundTripTimeVariance += (time - roundTripTime) / 4;
        } else {
            roundTripTime += (time - roundTripTime) / 8;
            roundTripTimeVariance -= (time - roundTripTime) / 4;
        }

        if (roundTripTime < lowestRoundTripTime)
            lowestRoundTripTime = roundTripTime;

        if (roundTripTimeVariance > highestRoundTripTimeVariance)
            highestRoundTripTimeVariance = roundTripTimeVariance;

        if (packetThrottleEpoch == 0
                || Time.difference(host.getTimeCurrent(), packetThrottleEpoch) >= packetThrottleInterval) {
            lastRoundTripTime = lowestRoundTripTime;
            lastRoundTripTimeVariance = highestRoundTripTimeVariance;
            lowestRoundTripTime = roundTripTime;
            highestRoundTripTimeVariance = roundTripTimeVariance;
            packetThrottleEpoch = host.getTimeCurrent();
        }
    }

}