org.texai.torrent.PeerCoordinator.java Source code

Java tutorial

Introduction

Here is the source code for org.texai.torrent.PeerCoordinator.java

Source

/*
 * PeerCoordinator - Coordinates which peers do what (up and downloading).
 * Copyright (C) 2003 Mark J. Wielaard
 *
 * This file is part of Snark.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Revised by Stephen L. Reed, Dec 22, 2009.
 * Reformatted, fixed Checkstyle, Findbugs and PMD violations, and substituted Log4J logger
 * for consistency with the Texai project.
 */
package org.texai.torrent;

import org.texai.torrent.domainEntity.MetaInfo;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.SortedMap;
import java.util.Timer;
import java.util.TreeMap;
import org.apache.commons.codec.net.URLCodec;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.Channel;
import org.texai.torrent.message.BitTorrentHandshakeMessage;
import org.texai.util.ByteUtils;
import org.texai.util.NetworkUtils;
import org.texai.util.TexaiException;
import org.texai.x509.X509SecurityInfo;

/** Coordinates which peers perform uploading or downloading actions. */
public final class PeerCoordinator {

    /** the logger */
    private static final Logger LOGGER = Logger.getLogger(PeerCoordinator.class);
    /** delay in milliseconds before the peer checker task is to be executed */
    public static final long CHECK_PERIOD = 20 * 1000; // 20 seconds
    /** the maximum number of connections */
    public static final int MAX_CONNECTIONS = 24;
    /** the maximum number of uploaders */
    public static final int MAX_UPLOADERS = 4;
    /** the torrent metainfo */
    private final MetaInfo metaInfo;
    /** the storage */
    private final Storage storage;
    /** an approximation of the number of current uploaders that is re-synced by PeerChecker once in a while */
    private int uploaders = 0;
    /** the total number of uploaded bytes of all peers */
    private long nbrBytesUploaded;
    /** the total number of downloaded bytes of all peers */
    private long nbrBytesDownloaded;
    /** the peer dictionary, peer identification bytes hex --> peer */
    private final Map<String, Peer> peerDictionary = new HashMap<>();
    /** the peer checker timer */
    private final Timer peerCheckerTimer = new Timer("peer checker timer", true);
    /** our BitTorrent client id bytes, randomly assigned */
    private final byte[] ourIdBytes = new byte[20];
    /** randomized wanted piece sequence numbers */
    private final List<Integer> wantedPieces;
    /** the indicator whether this peer is halted */
    private boolean isQuit = false;
    /** the tracker client */
    private TrackerClient trackerClient;
    /** the download statistics dictionary, tracked peer info --> nbr downloaded bytes */
    private final Map<TrackedPeerInfo, Long> downloadStatisticsDictionary = new HashMap<>();
    /** the upload statistics dictionary, tracked peer info --> nbr uploaded bytes */
    private final Map<TrackedPeerInfo, Long> uploadStatisticsDictionary = new HashMap<>();
    /** our tracked peer information */
    private final TrackedPeerInfo ourTrackedPeerInfo;
    /** the download listener, or null if our peer is a seed */
    private final DownloadListener downloadListener;
    /** the X.509 security information */
    private final X509SecurityInfo x509SecurityInfo;
    /** the SSL torrent */
    private final SSLTorrent sslTorrent;
    /** the client id candidate symbols */
    private static final byte[] CANDIDATE_ID_SYMBOLS = new byte[62];

    static {
        for (int i = 0; i < 10; ++i) {
            CANDIDATE_ID_SYMBOLS[i] = (byte) ('0' + i);
        }
        for (int i = 10; i < 36; ++i) {
            CANDIDATE_ID_SYMBOLS[i] = (byte) ('a' + i - 10);
        }
        for (int i = 36; i < 62; ++i) {
            CANDIDATE_ID_SYMBOLS[i] = (byte) ('A' + i - 36);
        }
    }

    /** Constructs a new PeerCoordinator instance.
     *
     * @param metaInfo the torrent metainfo
     * @param storage the torrent file/directory storage
     * @param port the port for accepting peer connections
     * @param x509SecurityInfo the X.509 security information
     * @param downloadListener the download listener, or null if our peer is a seed
     * @param sslTorrent the SSL torrent
     */
    public PeerCoordinator(final MetaInfo metaInfo, final Storage storage, final int port,
            final X509SecurityInfo x509SecurityInfo, final DownloadListener downloadListener,
            final SSLTorrent sslTorrent) {
        //Preconditions
        assert metaInfo != null : "metaInfo must not be null";
        assert storage != null : "storage must not be null";
        assert port > 0 : "port must be positive";
        assert x509SecurityInfo != null : "x509SecurityInfo must not be null";
        assert sslTorrent != null : "sslTorrent must not be null";

        this.metaInfo = metaInfo;
        this.storage = storage;
        this.x509SecurityInfo = x509SecurityInfo;
        this.downloadListener = downloadListener;
        this.sslTorrent = sslTorrent;

        // create our peer id bytes
        int index = 0;
        ourIdBytes[index++] = '-';
        ourIdBytes[index++] = 'S';
        ourIdBytes[index++] = 'N';
        ourIdBytes[index++] = '1';
        ourIdBytes[index++] = '0';
        ourIdBytes[index++] = '0';
        ourIdBytes[index++] = '0';
        ourIdBytes[index++] = '-';
        final Random random = new Random();
        while (index < 20) {
            ourIdBytes[index++] = CANDIDATE_ID_SYMBOLS[random.nextInt(CANDIDATE_ID_SYMBOLS.length)];
        }
        ourTrackedPeerInfo = new TrackedPeerInfo(ourIdBytes, NetworkUtils.getLocalHostAddress(), port);
        try {
            LOGGER.info(this + " our peer id: " + new String(ourIdBytes, "US-ASCII"));
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }
        LOGGER.log(Level.DEBUG, "our peer tracked peer info hex: " + TrackedPeerInfo.hexEncode(ourIdBytes));

        // Make a random list of piece numbers.
        wantedPieces = new ArrayList<>();
        final BitField bitfield = storage.getBitField();
        for (int i = 0; i < metaInfo.getNbrPieces(); i++) {
            if (!bitfield.get(i)) {
                wantedPieces.add(i);
            }
        }
        //Collections.shuffle(wantedPieces);
        LOGGER.info(this + " indices of wanted pieces " + wantedPieces);

        // Install a timer to check the uploaders.
        peerCheckerTimer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
        sslTorrent.addPeerCoordinator(metaInfo.getInfoHash(), this);
        LOGGER.info("created peer coordinator " + this);
    }

    /** Adds a peer that we contact.
     *
     * @param trackedPeerInfo the given peer tracked information
     * @return whether the peer was added
     */
    public boolean addPeerThatWeContact(final TrackedPeerInfo trackedPeerInfo) {
        //Preconditions
        assert trackedPeerInfo != null : "trackedPeerInfo must not be null";

        if (isQuit) {
            return false;
        }
        if (trackedPeerInfo.equals(ourTrackedPeerInfo)) {
            LOGGER.info("not adding our own peer listed at the tracker");
            return false;
        }

        for (final Peer peer : peerDictionary.values()) {
            if (trackedPeerInfo.equals(peer.getTrackedPeerInfo()) && peer.isConnected()) {
                LOGGER.info("already connected to " + peer);
                return false;
            }
        }
        final boolean areMorePeersNeeded;
        synchronized (peerDictionary) {
            areMorePeersNeeded = peerDictionary.size() < MAX_CONNECTIONS;
        }

        if (areMorePeersNeeded) {
            final Peer peer = new Peer(trackedPeerInfo, this);
            final String peerIdBytesHex = ByteUtils.toHex(peer.getTrackedPeerInfo().getPeerIdBytes());
            synchronized (peerDictionary) {
                peerDictionary.put(peerIdBytesHex, peer);
            }
            LOGGER.info(
                    "=========================================================================================================");
            try {
                LOGGER.info(this + " added peer to contact: " + peer + " "
                        + new String(peer.getTrackedPeerInfo().getPeerIdBytes(), "US-ASCII"));
            } catch (UnsupportedEncodingException ex) {
                throw new TexaiException(ex);
            }
            LOGGER.info(
                    "=========================================================================================================");
            peer.sendHandshake();

            return true;
        } else {
            LOGGER.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS + " not accepting extra peer: " + trackedPeerInfo);
            return false;
        }
    }

    /** Adds a peer that contacted us.
     *
     * @param inetAddress the peer's IP address
     * @param port the peer's port
     * @param channel the communication channel between us and the peer
     * @param bitTorrentHandshakeMessage the remote peer's handshake message
     * @return whether the peer was added
     */
    public boolean addPeerThatContactedUs(final InetAddress inetAddress, final int port, final Channel channel,
            final BitTorrentHandshakeMessage bitTorrentHandshakeMessage) {
        //Preconditions
        assert inetAddress != null : "inetAddress must not be null";
        assert port > 0 : "port must be positive";
        assert channel != null : "channel must not be null";
        assert bitTorrentHandshakeMessage != null : "bitTorrentHandshakeMessage must not be null";

        if (isQuit) {
            return false;
        }

        final TrackedPeerInfo trackedPeerInfo = new TrackedPeerInfo(bitTorrentHandshakeMessage.getPeerIdBytes(),
                inetAddress, port);
        if (trackedPeerInfo.equals(ourTrackedPeerInfo)) {
            LOGGER.info("not adding our own peer listed at the tracker");
            return false;
        }

        for (final Peer peer : peerDictionary.values()) {
            if (trackedPeerInfo.equals(peer.getTrackedPeerInfo()) && peer.isConnected()) {
                LOGGER.info("already connected to " + peer);
                return false;
            }
        }
        final boolean areMorePeersNeeded;
        synchronized (peerDictionary) {
            areMorePeersNeeded = peerDictionary.size() < MAX_CONNECTIONS;
        }

        if (areMorePeersNeeded) {
            final Peer peer = new Peer(inetAddress, port, channel, bitTorrentHandshakeMessage, this);
            final String peerIdBytesHex = ByteUtils.toHex(peer.getTrackedPeerInfo().getPeerIdBytes());
            synchronized (peerDictionary) {
                peerDictionary.put(peerIdBytesHex, peer);
            }
            LOGGER.info(
                    "=========================================================================================================");
            try {
                LOGGER.info(this + " added peer that contacted us: " + peer + " "
                        + new String(peer.getTrackedPeerInfo().getPeerIdBytes(), "US-ASCII"));
            } catch (UnsupportedEncodingException ex) {
                throw new TexaiException(ex);
            }
            LOGGER.info(
                    "=========================================================================================================");
            peer.receiveHandshake(bitTorrentHandshakeMessage);
            return true;
        } else {
            LOGGER.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS + " not accepting extra peer: " + trackedPeerInfo);
            return false;
        }
    }

    /** Processes the event in which the connection to the peer was terminated or the connection
     * handshake failed.
     *
     * @param peer the peer that just got disconnected.
     */
    public void peerDisconnectedEvent(final Peer peer) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";

        LOGGER.log(Level.DEBUG, "Disconnected " + peer);

        LOGGER.info("peerDisconnectedEvent started");
        synchronized (peerDictionary) {
            // Make sure it is no longer in our lists
            if (peerDictionary.remove(ByteUtils.toHex(peer.getTrackedPeerInfo().getPeerIdBytes())) != null) {
                // Unchoke some random other peer
                unchokePeer();
            }
        }
        LOGGER.info("peerDisconnectedEvent completed");
    }

    /** Processes the event in which we received a interested or unintrested message from a peer.
     *
     * @param peer the peer that sent the message
     * @param interest true when the peer sent a interested message, false when the peer sent an uninterested message
     */
    public void peerInterestedOrUninterestedEvent(final Peer peer, final boolean interest) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";

        if (interest) {
            LOGGER.info("peerInterestedOrUninterestedEvent started");
            synchronized (peerDictionary) {
                if (uploaders < MAX_UPLOADERS && peer.isChoking()) {
                    uploaders++;
                    peer.setChoking(false);
                    LOGGER.log(Level.DEBUG, "Unchoke: " + peer);
                }
            }
            LOGGER.info("peerInterestedOrUninterestedEvent completed");
        }
    }

    /** Processes the event in which a complete piece is received from a peer. The piece must be
     * requested by Peer.request() first. If this method returns false that
     * means the Peer provided a corrupted piece and the connection will be
     * closed.
     *
     * @param peer the peer that sent the piece.
     * @param pieceIndex the piece index received.
     * @param pieceBuffer the byte array containing the piece.
     *
     * @return true when the bytes represent the piece, false otherwise.
     */
    public boolean peerCompletePieceEvent(final Peer peer, final int pieceIndex, final byte[] pieceBuffer) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert pieceIndex >= 0 : "pieceIndex must not be negative";
        assert pieceBuffer != null : "pieceBuffer must not be equal to null";

        if (isQuit) {
            return true;
        }
        synchronized (wantedPieces) {
            if (!wantedPieces.contains(pieceIndex)) {
                LOGGER.info(peer + " piece " + pieceIndex + " is no longer needed");

                // No need to announce have piece to peers.
                // Assume we got a good piece, we don't really care anymore.
                return true;
            }
            try {
                if (storage.putPiece(pieceIndex, pieceBuffer)) {
                    synchronized (downloadStatisticsDictionary) {
                        Long nbrBytesDownloaded1 = downloadStatisticsDictionary.get(peer.getTrackedPeerInfo());
                        if (nbrBytesDownloaded1 == null) {
                            nbrBytesDownloaded1 = (long) pieceBuffer.length;
                            downloadStatisticsDictionary.put(peer.getTrackedPeerInfo(), nbrBytesDownloaded1);
                        } else {
                            downloadStatisticsDictionary.put(peer.getTrackedPeerInfo(),
                                    nbrBytesDownloaded1 + (long) pieceBuffer.length);
                        }
                    }
                    LOGGER.info(this + " received piece " + pieceIndex + " from " + peer);
                } else {
                    nbrBytesDownloaded -= metaInfo.getPieceLength(pieceIndex);
                    LOGGER.info("got BAD piece " + pieceIndex + " from " + peer);
                    return false; // No need to announce BAD piece to peers.
                }
            } catch (final IOException ioe) {
                throw new TexaiException(ioe);
            }
            wantedPieces.remove(Integer.valueOf(pieceIndex));
        }

        // announce that we have it to our peers
        final List<Peer> myPeers = new ArrayList<>();
        LOGGER.debug("peerPieceEvent critical section started");
        synchronized (peerDictionary) {
            myPeers.addAll(peerDictionary.values());
        }
        LOGGER.debug("peerPieceEvent critical section completed");
        final Iterator<Peer> peers_iter = myPeers.iterator();
        while (!isQuit && peers_iter.hasNext()) {
            final Peer peer1 = peers_iter.next();
            if (peer1.isConnected()) {
                peer1.havePiece(pieceIndex);
            }
        }

        if (!isQuit && isCompleted()) {
            trackerClient.quit();
        }
        return true;
    }

    /** Gets the peer having the the given peer identification bytes.
     *
     * @param peerIdBytes the given peer identification bytes
     * @return the peer, or null if not found
     */
    public Peer getPeer(final byte[] peerIdBytes) {
        //Preconditions
        assert peerIdBytes != null : "peerIdBytes must not be null";

        return peerDictionary.get(ByteUtils.toHex(peerIdBytes));
    }

    /** Sets the tracker client.
     *
     * @param trackerClient the tracker client
     */
    public void setTrackerClient(final TrackerClient trackerClient) {
        //Preconditions
        assert trackerClient != null : "trackerClient must not be null";

        this.trackerClient = trackerClient;
    }

    /** Gets our peer identification bytes
     *
     * @return our peer identification bytes
     */
    public byte[] getPeerIdBytes() {
        return ourIdBytes;
    }

    /** Gets our URL encoded id bytes
     *
     * @return our URL encoded id bytes
     */
    public String getURLEncodedID() {
        return new String((new URLCodec()).encode(ourIdBytes));
    }

    /** Returns whether this storage contains all pieces in the MetaInfo.
     *
     * @return whether this storage contains all pieces in the MetaInfo
     */
    public boolean isCompleted() {
        return storage.isComplete();
    }

    /** Returns the number of peers.
     *
     * @return  the number of peers
     */
    public int getNbrPeers() {
        synchronized (peerDictionary) {
            return peerDictionary.size();
        }
    }

    /** Returns how many pieces are still missing.
     *
     * @return how many pieces are still missing
     */
    public int getNbrNeededPieces() {
        return storage.getNbrNeededPieces();
    }

    /** Returns approximately how many bytes are still needed to get the complete file.
     *
     * @return approximately how many bytes are still needed to get the complete file
     */
    public long getApproximateNbrBytesRemaining() {
        return storage.getNbrNeededPieces() * metaInfo.getPieceLength(0);
    }

    /** Returns the total number of uploaded bytes of all peers.
     *
     * @return the total number of uploaded bytes of all peers
     */
    public long getUploaded() {
        return nbrBytesUploaded;
    }

    /** Returns the total number of downloaded bytes of all peers.
     *
     * @return the total number of downloaded bytes of all peers
     */
    public long getDownloaded() {
        return nbrBytesDownloaded;
    }

    /** Returns whether more peers are needed.
     *
     * @return whether more peers are needed
     */
    public boolean areMorePeersNeeded() {
        synchronized (peerDictionary) {
            return !isQuit && peerDictionary.size() < MAX_CONNECTIONS;
        }
    }

    /** Quits the tracker client and peers. */
    public void quit() {
        isQuit = true;
        LOGGER.info("halting tracker client");
        trackerClient.quit();
        LOGGER.info("disconnecting the peers  ********");
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        synchronized (peerDictionary) {
            LOGGER.info("obtained lock on the peers");

            // Stop peer checker task.
            peerCheckerTimer.cancel();

            // Stop peers.
            final Iterator<Peer> peers_iter = peerDictionary.values().iterator();
            while (peers_iter.hasNext()) {
                final Peer peer = peers_iter.next();
                LOGGER.info("disconnecting " + peer);
                peer.disconnect();
                peers_iter.remove();
            }
            LOGGER.info("peers disconnected  ********");
        }
    }

    /** Get the peers.
     *
     * @return the peers
     */
    public Collection<Peer> getPeers() {
        return peerDictionary.values();
    }

    /** Gets an approximation of the number of current uploaders that is re-synced by PeerChecker once in a while.
     *
     * @return an approximation of the number of current uploaders
     */
    public int getUploaders() {
        return uploaders;
    }

    /** Sets the uploaders.
     *
     * @param uploaders the uploaders to set
     */
    public void setUploaders(final int uploaders) {
        //Preconditions
        assert uploaders >= 0 : "uploaders must not be negative";

        this.uploaders = uploaders;
    }

    /** Reports the download statistics. */
    public void reportDownloadStatistics() {
        final SortedMap<Long, TrackedPeerInfo> sortedDownloadStatisticsDictionary = new TreeMap<>();
        synchronized (downloadStatisticsDictionary) {
            for (final Entry<TrackedPeerInfo, Long> entry : downloadStatisticsDictionary.entrySet()) {
                sortedDownloadStatisticsDictionary.put(entry.getValue(), entry.getKey());
            }
        }
        LOGGER.info("number bytes downloaded from peers ...");
        if (sortedDownloadStatisticsDictionary.isEmpty()) {
            LOGGER.info("  none");
        } else {
            for (final Entry<Long, TrackedPeerInfo> entry : sortedDownloadStatisticsDictionary.entrySet()) {
                LOGGER.info("  " + entry.getValue() + "  " + entry.getKey());
            }
        }
    }

    /** Reports the upload statistics. */
    public void reportUploadStatistics() {
        final SortedMap<Long, TrackedPeerInfo> sortedUploadStatisticsDictionary = new TreeMap<>();
        synchronized (uploadStatisticsDictionary) {
            for (final Entry<TrackedPeerInfo, Long> entry : uploadStatisticsDictionary.entrySet()) {
                sortedUploadStatisticsDictionary.put(entry.getValue(), entry.getKey());
            }
        }
        LOGGER.info("number bytes uploaded to peers ...");
        if (sortedUploadStatisticsDictionary.isEmpty()) {
            LOGGER.info("  none");
        } else {
            for (final Entry<Long, TrackedPeerInfo> entry : sortedUploadStatisticsDictionary.entrySet()) {
                LOGGER.info("  " + entry.getValue() + "  " + entry.getKey());
            }
        }
    }

    /** Optimistically unchokes the peers. */
    public synchronized void unchokePeer() {
        // linked list will contain all interested peers that we choke.
        // At the start are the peers that have us unchoked and at the end the
        // other peers that are interested, but are choking us.
        final List<Peer> interested = new LinkedList<>();
        final Iterator<Peer> peers_iter = peerDictionary.values().iterator();
        while (peers_iter.hasNext()) {
            final Peer peer = peers_iter.next();
            if (uploaders < MAX_UPLOADERS && peer.isChoking() && peer.isInterested()) {
                if (peer.isChoked()) {
                    interested.add(peer);
                } else {
                    interested.add(0, peer);
                }
            }
        }

        while (uploaders < MAX_UPLOADERS && !interested.isEmpty()) {
            final Peer peer = interested.remove(0);
            peer.setChoking(false);
            uploaders++;
        }
    }

    /** Gets the bit map.
     *
     * @return the bit map
     */
    public byte[] getBitMap() {
        return storage.getBitField().getFieldBytes();
    }

    /** Processes the event in which a peer sent a have piece message. If this method returns true
     * and the peer has not yet received a interested message or we indicated
     * earlier to be not interested, then an interested message will be sent.
     *
     * @param peer the peer that sent the message
     * @param piece the piece number that the peer just got
     *
     * @return true when it is a piece that we want, false if the piece is already here.
     */
    public boolean peerHavePieceEvent(final Peer peer, final int piece) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert piece >= 0 : "piece must not be negative";

        synchronized (wantedPieces) {
            return wantedPieces.contains(piece);
        }
    }

    /** Processes the event in which we received a bitfield message from a peer. If this method returns true an
     * interested message will be send back to the peer.
     *
     * @param peer the peer that sent the message
     * @param bitField a BitField containing the pieces that the other side has
     *
     * @return true when the BitField contains pieces we want, false if the piece is already known
     */
    public boolean peerBitFieldEvent(final Peer peer, final BitField bitField) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert bitField != null : "bitField must not be equal to null";

        synchronized (wantedPieces) {
            final Iterator<Integer> wantedPieces_iter = wantedPieces.iterator();
            while (wantedPieces_iter.hasNext()) {
                final int wantedPiece = wantedPieces_iter.next();
                if (bitField.get(wantedPiece)) {
                    LOGGER.info(this + " we want piece: " + wantedPiece + " from " + peer);
                    return true;
                }
            }
        }
        LOGGER.info(this + " no wanted pieces from " + peer);
        return false;
    }

    /** Processes the event in which we are downloading from the peer and need to ask for a new
     * piece. Might be called multiple times before <code>gotPiece()</code> is
     * called.
     *
     * @param peer the Peer that will be asked to provide the piece
     * @param bitfield a BitField containing the pieces that the other side has
     *
     * @return one of the pieces from the bitfield that we want or -1 if we are no longer interested in the peer
     */
    public int getWantedPiece(final Peer peer, final BitField bitfield) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert bitfield != null : "bitfield must not be equal to null";

        if (isQuit) {
            return -1;
        }

        synchronized (wantedPieces) {
            Integer piece = null;
            final Iterator<Integer> wantedPieces_iter = wantedPieces.iterator();
            while (piece == null && wantedPieces_iter.hasNext()) {
                final Integer wantedPiece = wantedPieces_iter.next();
                if (bitfield.get(wantedPiece)) {
                    wantedPieces_iter.remove();
                    piece = wantedPiece;
                }
            }

            if (piece == null) {
                return -1;
            }

            // We add it back at the back of the list. It will be removed
            // if gotPiece is called later. This means that the last
            // couple of pieces might very well be asked from multiple
            // peers but that is OK.
            wantedPieces.add(piece);

            return piece;
        }
    }

    /** Processes the event in which the peer wants (part of) a piece from us. Only called when
     * the peer is not choked by us (<code>peer.choke(false)</code> was
     * called).
     *
     * @param peer the peer that wants the piece.
     * @param piece the piece number requested.
     * @return a byte array containing the piece or null when the piece is not available (which is a protocol error).
     * @throws IOException when an input/output error occurs
     */
    public byte[] getPieceBytes(final Peer peer, final int piece) throws IOException {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert piece >= 0 : "piece must not be negative";

        if (isQuit) {
            return null;
        }

        try {
            return storage.getPiece(piece);
        } catch (final IOException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Processes the event in which a (partial) piece has been uploaded to the peer.
     *
     * @param peer the peer to which size bytes were uploaded
     * @param size the number of bytes that were uploaded
     */
    public void uploaded(final Peer peer, final int size) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert size >= 0 : "size must not be negative";

        nbrBytesUploaded += size;
        synchronized (uploadStatisticsDictionary) {
            Long nbrBytesUploaded1 = uploadStatisticsDictionary.get(peer.getTrackedPeerInfo());
            if (nbrBytesUploaded1 == null) {
                nbrBytesUploaded1 = (long) size;
                uploadStatisticsDictionary.put(peer.getTrackedPeerInfo(), nbrBytesUploaded1);
            } else {
                uploadStatisticsDictionary.put(peer.getTrackedPeerInfo(), nbrBytesUploaded1 + (long) size);
            }
        }
    }

    /** Processes the event in which a (partial) piece has been downloaded from the peer.
     *
     * @param peer the Peer from which size bytes were downloaded.
     * @param size the number of bytes that were downloaded.
     */
    public void downloaded(final Peer peer, final int size) {
        //Preconditions
        assert peer != null : "peer must not be equal to null";
        assert size >= 0 : "size must not be negative";

        nbrBytesDownloaded += size;
    }

    /** Gets the torrent metainfo.
     *
     * @return the torrent metainfo
     */
    public MetaInfo getMetaInfo() {
        return metaInfo;
    }

    /** Gets the storage.
     *
     * @return the storage
     */
    public Storage getStorage() {
        return storage;
    }

    /** Gets our tracked peer info.
     *
     * @return our tracked peer info
     */
    public TrackedPeerInfo getOurTrackedPeerInfo() {
        return ourTrackedPeerInfo;
    }

    /** Gets the download listener.
     *
     * @return the download listener
     */
    public DownloadListener getDownloadListener() {
        return downloadListener;
    }

    /** Gets the X.509 security information.
     *
     * @return the X.509 security information
     */
    protected X509SecurityInfo getX509SecurityInfo() {
        return x509SecurityInfo;
    }

    /** Gets the SSL torrent.
     *
     * @return the sslTorrent
     */
    public SSLTorrent getSSLTorrent() {
        return sslTorrent;
    }

    /** Returns a string representation of this object.
     *
     * @return a string representation of this object
     */
    @Override
    public String toString() {
        return "[PeerCoordinator " + ourTrackedPeerInfo.toString() + "]";
    }

}