/*
* Peer - All public information concerning a peer. 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 java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.texai.network.netty.handler.AbstractBitTorrentHandler;
import org.texai.network.netty.pipeline.BitTorrentClientPipelineFactory;
import org.texai.torrent.message.BitTorrentBitFieldMessage;
import org.texai.torrent.message.BitTorrentCancelMessage;
import org.texai.torrent.message.BitTorrentChokeMessage;
import org.texai.torrent.message.BitTorrentHandshakeMessage;
import org.texai.torrent.message.BitTorrentHaveMessage;
import org.texai.torrent.message.BitTorrentInterestedMessage;
import org.texai.torrent.message.BitTorrentNotInterestedMessage;
import org.texai.torrent.message.BitTorrentPieceMessage;
import org.texai.torrent.message.BitTorrentRequestMessage;
import org.texai.torrent.message.BitTorrentUnchokeMessage;
import org.texai.util.ByteUtils;
import org.texai.util.StringUtils;
import org.texai.util.TexaiException;
/** Provides public information concerning a peer. */
public final class Peer implements Comparable<Peer> {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(Peer.class);
/** the peer connection timeout milliseconds */
private static final int PEER_CONNECTION_TIMEOUT = 20000;
/** the tracked peer information */
private final TrackedPeerInfo trackedPeerInfo;
/** the indicator whether we sent our handshake message to this peer */
private final AtomicBoolean isOurHandshakeSent = new AtomicBoolean(false);
/** the indicator whether this peer has completed its handshake with us */
private final AtomicBoolean hasHandshakeCompleted = new AtomicBoolean(false);
/** the peer coordinator */
private final PeerCoordinator peerCoordinator;
// Interesting and choking describes whether we are interested in or
// are choking the other side.
/** the indicator whether we are interested in the peer */
private final AtomicBoolean isPeerInterestingToUs = new AtomicBoolean(false);
/** the indicator whether we are choking the peer */
private final AtomicBoolean areWeChokingThePeer = new AtomicBoolean(true);
// Interested and choked describes whether the other side is
// interested in us or choked us.
/** the indicator whether the peer is interested in us */
private final AtomicBoolean isPeerInterestedInUs = new AtomicBoolean(false);
/** the indicator that the peer is choking us */
private final AtomicBoolean isPeerChokingUs = new AtomicBoolean(true);
/** the total number of downloaded bytes */
private final AtomicLong nbrBytesDownloaded = new AtomicLong(0L);
/** the total number of uploaded bytes */
private final AtomicLong nbrBytesUploaded = new AtomicLong(0L);
/** the bitfield provided by the remote peer */
private final AtomicReference<BitField> bitField = new AtomicReference<>(null);
/** the outstanding requests */
private final List<Request> outstandingRequests = new ArrayList<>();
/** the last request */
private Request lastRequest = null;
/** the indicator whether we have to resend outstanding requests (true after we got choked) */
private boolean resend = false;
/** the maximum requests in the pipeline */
private static final int MAX_PIPELINE = 5;
/** the 16K size */
private static final int PARTSIZE = 16384; // 16K
/** the indicator to quit processing */
private boolean isQuit;
/** the communication channel between us and the peer */
private Channel channel;
/** Creates a disconnected peer given its TrackedPeerInfo.
*
* @param trackedPeerInfo the tracked peer information
* @param peerCoordinator the peer coordinator
*/
public Peer(
final TrackedPeerInfo trackedPeerInfo,
final PeerCoordinator peerCoordinator) {
//Preconditions
assert trackedPeerInfo != null : "trackedPeerInfo must not be null";
assert peerCoordinator != null : "peerCoordinator must not be null";
this.trackedPeerInfo = trackedPeerInfo;
this.peerCoordinator = peerCoordinator;
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " created disconnected peer: " + this);
}
/** Creates a connected peer.
*
* @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
* @param peerCoordinator the peer coordinator
*/
public Peer(
final InetAddress inetAddress,
final int port,
final Channel channel,
final BitTorrentHandshakeMessage bitTorrentHandshakeMessage,
final PeerCoordinator peerCoordinator) {
//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";
assert peerCoordinator != null : "peerCoordinator must not be null";
this.channel = channel;
this.peerCoordinator = peerCoordinator;
final byte[] peerIdBytes = bitTorrentHandshakeMessage.getPeerIdBytes();
this.trackedPeerInfo = new TrackedPeerInfo(peerIdBytes, inetAddress, port);
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " created connected peer: " + this);
}
/** Receives the handshake message sent by the peer, and replies with our handshake if not already sent.
*
* @param peerBitTorrentHandshakeMessage the remote peer's handshake message
*/
public void receiveHandshake(final BitTorrentHandshakeMessage peerBitTorrentHandshakeMessage) {
//Preconditions
assert peerBitTorrentHandshakeMessage != null : "peerBitTorrentHandshakeMessage must not be null";
assert channel != null : "channel must not be null";
assert ByteUtils.areEqual(peerCoordinator.getMetaInfo().getInfoHash(), peerBitTorrentHandshakeMessage.getInfoHash()) :
"peer info hash must match ours";
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " receiving handshake from " + this);
if (!isOurHandshakeSent.get()) {
sendHandshake();
}
hasHandshakeCompleted.set(true);
final BitTorrentBitFieldMessage bitTorrentBitFieldMessage = new BitTorrentBitFieldMessage(
peerCoordinator.getBitMap(),
peerCoordinator.getPeerIdBytes());
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending " + bitTorrentBitFieldMessage.toDetailedString() + " to " + this);
channel.write(bitTorrentBitFieldMessage);
// unchoke the peer
peerCoordinator.unchokePeer();
}
/** Sends our handshake message to the remote peer. */
@SuppressWarnings("ThrowableResultIgnored")
public void sendHandshake() {
//Preconditions
assert !hasHandshakeCompleted.get() : "handshake must not have completed";
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo() + " sending handshake to " + this);
if (channel == null) {
final ClientBootstrap clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// configure the client pipeline
final AbstractBitTorrentHandler bitTorrentHandler = new BitTorrentHandler(peerCoordinator.getSSLTorrent());
final ChannelPipeline channelPipeline = BitTorrentClientPipelineFactory.getPipeline(
bitTorrentHandler,
peerCoordinator.getX509SecurityInfo());
clientBootstrap.setPipeline(channelPipeline);
// start the connection attempt
final ChannelFuture channelFuture =
clientBootstrap.connect(new InetSocketAddress("localhost", trackedPeerInfo.getPort()));
// wait until the connection attempt succeeds or fails
channel = channelFuture.awaitUninterruptibly().getChannel();
if (!channelFuture.isSuccess()) {
LOGGER.warn(StringUtils.getStackTraceAsString(channelFuture.getCause()));
throw new TexaiException(channelFuture.getCause());
}
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " connected to bit torrent client " + this);
}
final BitTorrentHandshakeMessage ourBitTorrentHandshakeMessage = new BitTorrentHandshakeMessage(
peerCoordinator.getMetaInfo().getInfoHash(),
peerCoordinator.getPeerIdBytes());
channel.write(ourBitTorrentHandshakeMessage);
isOurHandshakeSent.set(true);
}
/** Receives a bitfield message from the peer.
*
* @param bitTorrentBitFieldMessage the bitfield message
*/
public synchronized void bitfieldMessageFromPeer(final BitTorrentBitFieldMessage bitTorrentBitFieldMessage) {
//Preconditions
assert bitTorrentBitFieldMessage != null : "bitTorrentBitFieldMessage must not be null";
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received " + bitTorrentBitFieldMessage.toDetailedString() + " from " + this);
if (bitField.get() != null) {
LOGGER.debug(peerCoordinator.getOurTrackedPeerInfo().toString() + " received unexpected bitfield message from " + this);
return;
}
bitField.set(new BitField(bitTorrentBitFieldMessage.getBitField(), peerCoordinator.getMetaInfo().getNbrPieces()));
setInteresting(peerCoordinator.peerBitFieldEvent(this, bitField.get()));
}
/** Sets whether or not we are interested in pieces from this peer. Defaults
* to false. When interest is true and this peer unchokes us then we start
* downloading from it. Has no effect when not connected.
*
* @param isInterestingOrNot the indicator whether or not we are interested in pieces from this peer
*/
public void setInteresting(final boolean isInterestingOrNot) {
if (hasHandshakeCompleted.get()) {
if (isInterestingOrNot != isPeerInterestingToUs.get()) {
isPeerInterestingToUs.set(isInterestingOrNot);
assert channel != null;
if (isInterestingOrNot) {
final BitTorrentInterestedMessage bitTorrentInterestedMessage =
new BitTorrentInterestedMessage(peerCoordinator.getPeerIdBytes());
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending interested to " + this);
channel.write(bitTorrentInterestedMessage);
} else {
final BitTorrentNotInterestedMessage bitTorrentNotInterestedMessage =
new BitTorrentNotInterestedMessage(peerCoordinator.getPeerIdBytes());
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending not-interested to " + this);
channel.write(bitTorrentNotInterestedMessage);
}
if (isPeerInterestingToUs.get() && !isPeerChokingUs.get()) {
request();
}
}
}
}
/** Receives an interested message from the peer. */
public void interestedMessageFromPeer() {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received interested from " + this);
isPeerInterestedInUs.set(true);
peerCoordinator.peerInterestedOrUninterestedEvent(this, true);
}
/** Receives a not-interested message from the peer. */
public void notInterestedMessageFromPeer() {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received not-interested from " + this);
isPeerInterestedInUs.set(false);
peerCoordinator.peerInterestedOrUninterestedEvent(this, false);
}
/** Sets whether or not we are choking the peer. Defaults to true. When choke
* is false and the peer requests some pieces we upload them, otherwise
* requests of this peer are ignored.
*
* @param choke the indicator whether or not we are choking the peer
*/
public void setChoking(final boolean choke) {
if (hasHandshakeCompleted.get()) {
LOGGER.info(this + " setChoking(" + choke + ")");
if (areWeChokingThePeer.get() != choke) {
areWeChokingThePeer.set(choke);
assert channel != null;
if (choke) {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending choke to " + this);
final BitTorrentChokeMessage bitTorrentChokeMessage = new BitTorrentChokeMessage(peerCoordinator.getPeerIdBytes());
channel.write(bitTorrentChokeMessage);
} else {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending unchoke to " + this);
final BitTorrentUnchokeMessage bitTorrentUnchokeMessage = new BitTorrentUnchokeMessage(peerCoordinator.getPeerIdBytes());
channel.write(bitTorrentUnchokeMessage);
}
}
}
}
/** Receives a choke message from the peer. */
public void chokeMessageFromPeer() {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received choke from " + this);
isPeerChokingUs.set(true);
resend = true;
}
/** Receives an unchoke message from the peer. */
public void unChokeMessageFromPeer() {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received unchoke from " + this);
isPeerChokingUs.set(false);
resend = false;
if (isPeerInterestingToUs.get()) {
request();
}
}
/** Receives a keep-alive message from the peer. */
public void keepAliveMessageFromPeer() {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received keep-alive from " + this);
}
/** Tells the peer we have another piece.
*
* @param pieceIndex the piece index
*/
public synchronized void havePiece(final int pieceIndex) {
//Preconditions
assert pieceIndex >= 0 : "pieceIndex must not be negative";
assert channel != null : "channel must not be null";
if (lastRequest != null && lastRequest.getPieceIndex() == pieceIndex) {
// tell the other side that we are no longer interested in any of the outstanding requests for this piece.
lastRequest = null;
}
final Iterator<Request> outstandingRequests_iter = outstandingRequests.iterator();
while (outstandingRequests_iter.hasNext()) {
final Request outstandingRequest = outstandingRequests_iter.next();
if (outstandingRequest.getPieceIndex() == pieceIndex) {
outstandingRequests_iter.remove();
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending cancel to " + this);
final BitTorrentCancelMessage bitTorrentCancelMessage = new BitTorrentCancelMessage(
outstandingRequest.getPieceIndex(),
outstandingRequest.getOffset(),
outstandingRequest.getLength(),
peerCoordinator.getPeerIdBytes());
channel.write(bitTorrentCancelMessage);
}
}
// Tell the other side that we really have this piece.
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending have piece " + pieceIndex + " to " + this);
final BitTorrentHaveMessage bitTorrentHaveMessage = new BitTorrentHaveMessage(
pieceIndex,
peerCoordinator.getPeerIdBytes());
channel.write(bitTorrentHaveMessage);
// Request something else if necessary.
addRequest();
// Is the peer still interesting?
if (lastRequest == null) {
setInteresting(false);
}
}
/** Receives a have message from the peer.
*
* @param bitTorrentHaveMessage the have message
*/
public synchronized void haveMessageFromPeer(final BitTorrentHaveMessage bitTorrentHaveMessage) {
//Preconditions
assert bitTorrentHaveMessage != null : "bitTorrentHaveMessage must not be null";
final int pieceIndex = bitTorrentHaveMessage.getPieceIndex();
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received have piece " + pieceIndex + " from " + this);
// Sanity check
if (pieceIndex < 0 || pieceIndex >= peerCoordinator.getMetaInfo().getNbrPieces()) {
// XXX disconnect?
LOGGER.log(Level.DEBUG, "Got strange 'have: " + pieceIndex + "' message from " + this);
return;
}
// Can happen if the other side never send a bitfield message.
if (bitField.get() == null) {
bitField.set(new BitField(peerCoordinator.getMetaInfo().getNbrPieces()));
}
bitField.get().set(pieceIndex);
if (peerCoordinator.peerHavePieceEvent(this, pieceIndex)) {
setInteresting(true);
}
}
/** Receives a request message from the peer.
*
* @param bitTorrentRequestMessage the bit torrent request message
*/
public synchronized void requestMessageFromPeer(final BitTorrentRequestMessage bitTorrentRequestMessage) {
//Preconditions
assert bitTorrentRequestMessage != null : "bitTorrentRequestMessage must not be null";
final int pieceIndex = bitTorrentRequestMessage.getPieceIndex();
final int offset = bitTorrentRequestMessage.getOffset();
final int length = bitTorrentRequestMessage.getLength();
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() +
" received request(" + pieceIndex + ", " + offset + ", " + length + ") from " + this);
if (areWeChokingThePeer.get()) {
LOGGER.log(Level.DEBUG, "request received, but choking " + this);
return;
}
// Sanity check
if (pieceIndex < 0 ||
pieceIndex >= peerCoordinator.getMetaInfo().getNbrPieces() ||
offset < 0 ||
offset > peerCoordinator.getMetaInfo().getPieceLength(pieceIndex) ||
length <= 0 ||
length > 4 * PARTSIZE) {
LOGGER.log(Level.DEBUG, "Got strange 'request: " + pieceIndex + ", " + offset + ", " + length + "' message from " + this);
return;
}
final byte[] pieceBytes;
try {
pieceBytes = peerCoordinator.getPieceBytes(this, pieceIndex);
} catch (IOException ex) {
throw new TexaiException(ex);
}
if (pieceBytes == null) {
// XXX - Protocol error-> diconnect?
LOGGER.log(Level.DEBUG, "Got request for unknown piece: " + pieceIndex);
return;
}
// More sanity checks
if (offset >= pieceBytes.length || offset + length > pieceBytes.length) {
// XXX - Protocol error-> disconnect?
LOGGER.log(Level.DEBUG, "Got out of range 'request: " + pieceIndex + ", " + offset + ", " + length + "' message from " + this);
return;
}
final byte[] chunkBytes = new byte[length];
System.arraycopy(
pieceBytes, // source
offset, // source offset
chunkBytes, // destination
0, // destination offset
length); // length
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending piece (" + pieceIndex + ", " + offset + ", " + length + ")" + " to " + this);
final BitTorrentPieceMessage bitTorrentPieceMessage = new BitTorrentPieceMessage(
pieceIndex,
offset,
chunkBytes,
peerCoordinator.getPeerIdBytes());
assert channel != null;
channel.write(bitTorrentPieceMessage);
}
/** Processes a piece message sent by the peer and requeues/sends requests when they must have been lost.
*
* @param bitTorrentPieceMessage the piece message
*/
public synchronized void pieceMessageFromPeer(final BitTorrentPieceMessage bitTorrentPieceMessage) {
//Preconditions
assert bitTorrentPieceMessage != null : "bitTorrentPieceMessage must not be null";
final int pieceIndex = bitTorrentPieceMessage.getPieceIndex();
final int offset = bitTorrentPieceMessage.getOffset();
final int length = bitTorrentPieceMessage.getChunkBytes().length;
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() +
" received piece (" + pieceIndex + "," + offset + "," + length + ") from " + this);
int requestNbr = getFirstOutstandingRequest(pieceIndex);
if (requestNbr == -1) {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() +
" unrequested piece (" + pieceIndex + ", " + offset + ", " + length + ") from " + this);
nbrBytesDownloaded.set(0L);
return;
}
// lookup the correct piece chunk request from the list
Request request;
request = outstandingRequests.get(requestNbr);
while (request.getPieceIndex() == pieceIndex && request.getOffset() != offset && requestNbr < outstandingRequests.size() - 1) {
requestNbr++;
request = outstandingRequests.get(requestNbr);
}
if (request.getPieceIndex() != pieceIndex || request.getOffset() != offset || request.getLength() != length) {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() +
" unrequested or unneeded 'piece (" + pieceIndex + ", " + offset + ", " + length + ") received from " + this);
LOGGER.info(" should match request: " + request);
nbrBytesDownloaded.set(0L);
return;
}
// copy the received chunk into the request's piece buffer
System.arraycopy(
bitTorrentPieceMessage.getChunkBytes(), // source
0, // source offset
request.getPieceBuffer(), // destination
offset, // destination offset
length); // length
// report missing requests
if (requestNbr != 0) {
final StringBuffer errmsg = new StringBuffer("Some requests dropped, got " + request +
", wanted:");
for (int i = 0; i < requestNbr; i++) {
final Request droppedRequest = outstandingRequests.remove(0);
outstandingRequests.add(droppedRequest);
// keep waiting for missing requests as they will be rerequested when we get choked/unchoked again
if (LOGGER.isDebugEnabled()) {
errmsg.append(' ');
errmsg.append(droppedRequest);
}
}
if (LOGGER.isDebugEnabled()) {
errmsg.append(' ');
errmsg.append(this);
LOGGER.log(Level.DEBUG, errmsg);
}
}
outstandingRequests.remove(0);
// request more if necessary to keep the pipeline filled.
addRequest();
final int pieceLength = request.getLength();
nbrBytesDownloaded.addAndGet(pieceLength);
peerCoordinator.downloaded(this, pieceLength);
if (getFirstOutstandingRequest(request.getPieceIndex()) == -1) {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " received all chunks for piece " + pieceIndex);
if (!peerCoordinator.peerCompletePieceEvent(this, request.getPieceIndex(), request.getPieceBuffer())) {
LOGGER.log(Level.DEBUG, "Got BAD " + request.getPieceIndex() + " from " + this);
nbrBytesDownloaded.set(0L);
}
}
}
/** Processes a cancel message from the peer.
*
* @param bitTorrentCancelMessage the cancel message
*/
public void cancelMessageFromPeer(final BitTorrentCancelMessage bitTorrentCancelMessage) {
//Preconditions
assert bitTorrentCancelMessage != null : "bitTorrentCancelMessage must not be null";
final int pieceIndex = bitTorrentCancelMessage.getPieceIndex();
final int offset = bitTorrentCancelMessage.getOffset();
final int length = bitTorrentCancelMessage.getLength();
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() +
" received cancel (" + pieceIndex + ", " + offset + ", " + length + ") from " + this);
}
/** Closes the channel to the peer. */
@SuppressWarnings("ThrowableResultIgnored")
public void disconnect() {
if (isQuit) {
LOGGER.info("peer " + this + " is already disconected");
} else {
if (hasHandshakeCompleted.get()) {
hasHandshakeCompleted.set(false);
}
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " disconnect " + this + " started");
isQuit = true;
final ChannelFuture channelFuture = channel.close();
// wait until the disconnection attempt succeeds or fails
channelFuture.awaitUninterruptibly();
if (!channelFuture.isSuccess()) {
throw new TexaiException(channelFuture.getCause());
}
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " disconnect from " + this + " completed");
}
}
/** Gets the index to the first outstanding request having the given piece sequence number.
*
* @param piece the piece sequence number
* @return the index to the first outstanding request having the given piece sequence number
*/
private int getFirstOutstandingRequest(final int piece) {
//Preconditions
assert piece >= 0 : "piece must not be negative";
for (int i = 0; i < outstandingRequests.size(); i++) {
if ((outstandingRequests.get(i)).getPieceIndex() == piece) {
return i;
}
}
return -1;
}
/** Starts or resumes requesting pieces. */
private void request() {
// Are there outstanding requests that have to be resent?
if (resend) {
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " resending outstanding requests to " + this);
final Iterator<Request> requests_iter = outstandingRequests.iterator();
while (requests_iter.hasNext()) {
sendRequestToPeer(requests_iter.next());
}
resend = false;
}
// Add/Send some more requests if necessary.
addRequest();
}
/** Adds a new request to the outstanding requests list. */
private void addRequest() {
boolean areMorePiecesRemaining = true;
while (areMorePiecesRemaining) {
areMorePiecesRemaining = outstandingRequests.size() < MAX_PIPELINE;
if (areMorePiecesRemaining && lastRequest == null) {
// we want something and we don't have outstanding requests
areMorePiecesRemaining = requestNextPiece();
} else if (areMorePiecesRemaining) {
// we want something
int pieceLength;
boolean isLastChunk;
pieceLength = peerCoordinator.getMetaInfo().getPieceLength(lastRequest.getPieceIndex());
isLastChunk = lastRequest.getOffset() + lastRequest.getLength() == pieceLength;
if (isLastChunk) {
// last chunck of a piece
areMorePiecesRemaining = requestNextPiece();
} else {
final int nextPiece = lastRequest.getPieceIndex();
final int nextBegin = lastRequest.getOffset() + PARTSIZE;
final byte[] pieceBuffer = lastRequest.getPieceBuffer();
final int maxLength = pieceLength - nextBegin;
final int nextLength = maxLength > PARTSIZE ? PARTSIZE : maxLength;
final Request request = new Request(
nextPiece,
pieceBuffer,
nextBegin,
nextLength);
outstandingRequests.add(request);
if (!isPeerChokingUs.get()) {
sendRequestToPeer(request);
}
lastRequest = request;
}
}
}
LOGGER.debug(this + " requests " + outstandingRequests);
}
/** Starts requesting first chunk of next piece. Returns true if something has been added to the requests,
* false otherwise.
*
* @return whether something has been added to the requests
*/
private boolean requestNextPiece() {
// Check that we already know what the other side has.
if (bitField.get() != null) {
final int nextPiece = peerCoordinator.getWantedPiece(this, bitField.get());
LOGGER.log(Level.DEBUG, this + " want piece " + nextPiece);
synchronized (this) {
if (nextPiece != -1 && (lastRequest == null || lastRequest.getPieceIndex() != nextPiece)) {
final int piece_length = peerCoordinator.getMetaInfo().getPieceLength(nextPiece);
final byte[] pieceBuffer = new byte[piece_length];
final int length = Math.min(piece_length, PARTSIZE);
final Request request = new Request(nextPiece, pieceBuffer, 0, length);
outstandingRequests.add(request);
if (!isPeerChokingUs.get()) {
sendRequestToPeer(request);
}
lastRequest = request;
return true;
}
}
}
return false;
}
/** Sends the given request to this peer.
*
* @param request the given request
*/
private void sendRequestToPeer(final Request request) {
//Preconditions
assert request != null : "request must not be null";
assert channel != null : "channel must not be null";
LOGGER.info(peerCoordinator.getOurTrackedPeerInfo().toString() + " sending request " + request + " to " + this);
final BitTorrentRequestMessage bitTorrentRequestMessage = new BitTorrentRequestMessage(
request.getPieceIndex(),
request.getOffset(),
request.getLength(),
peerCoordinator.getPeerIdBytes());
channel.write(bitTorrentRequestMessage);
}
/** Returns the tracked peer information.
*
*
* @return the tracked peer information
*/
public TrackedPeerInfo getTrackedPeerInfo() {
return trackedPeerInfo;
}
/** Returns a string representation of this object.
*
* @return a string representation of this object
*/
@Override
public String toString() {
return trackedPeerInfo.toString();
}
/** Returns a hash code for this object.
*
* @return a hash code for this object
*/
@Override
public int hashCode() {
return trackedPeerInfo.hashCode();
}
/** Returns whether some other object equals this one.
* Two Peers are equal when they have the same TrackedPeerInfo. All other properties
* are ignored.
*
* @param obj the other object
* @return whether some other object equals this one
*/
@Override
public boolean equals(final Object obj) {
if (obj instanceof Peer) {
final Peer peer = (Peer) obj;
return trackedPeerInfo.equals(peer.trackedPeerInfo);
} else {
return false;
}
}
/** Compares the PeerIDs.
*
* @param peer the other peer
* @return -1 if this peer is less than the other peer, 0 if equal, otherwise return +1
*/
@Override
public int compareTo(final Peer peer) {
return trackedPeerInfo.compareTo(peer.trackedPeerInfo);
}
/** Returns whether the peer is connected.
*
* @return whether the peer is connected
*/
public boolean isConnected() {
return hasHandshakeCompleted.get();
}
/** Returns whether or not the peer is interested in pieces we have. Returns false if
* not connected.
*
* @return whether or not the peer is interested in pieces we have, false if
* not connected.
*/
public boolean isInterested() {
return (hasHandshakeCompleted.get()) && isPeerInterestedInUs();
}
/** Returns whether or not the peer has pieces we want from it. Returns false if not
* connected.
*
* @return whether or not the peer has pieces we want from it, false if not
* connected
*/
public boolean isInteresting() {
return hasHandshakeCompleted.get() && isPeerInterestingToUs();
}
/** Returns whether or not we are choking the peer. Returns true when not connected.
*
* @return whether or not we are choking the peer, true when not connected
*/
public boolean isChoking() {
return hasHandshakeCompleted.get() || areWeChokingThePeer();
}
/** Returns whether or not the peer choked us. Returns true when not connected.
*
* @return whether or not the peer choked us, true when not connected
*/
public boolean isChoked() {
return hasHandshakeCompleted.get() || isPeerChokingUs();
}
/** Returns the number of bytes that have been downloaded. Can be reset to
* zero with <code>resetCounters()</code>/
*
* @return the number of bytes that have been downloaded
*/
public long getDownloaded() {
return hasHandshakeCompleted.get() ? 0 : getNbrBytesDownloaded();
}
/** Returns the number of bytes that have been uploaded. Can be reset to zero
* with <code>resetCounters()</code>/
*
* @return the number of bytes that have been uploaded
*/
public long getUploaded() {
return hasHandshakeCompleted.get() ? 0 : getNbrBytesUploaded();
}
/** Resets the downloaded and uploaded counters to zero. */
public void resetCounters() {
if (hasHandshakeCompleted.get()) {
setNbrBytesDownloaded(0);
setNbrBytesUploaded(0);
}
}
/** Records the number of bytes uploaded.
*
* @param size the number of bytes uploaded
*/
public void uploaded(final int size) {
//Preconditions
assert size >= 0 : "size must not be negative";
nbrBytesUploaded.addAndGet(size);
peerCoordinator.uploaded(this, size);
}
/** Gets the indicator whether we are interested in the peer.
*
* @return the indicator whether we are interested in the peer
*/
public boolean isPeerInterestingToUs() {
return isPeerInterestingToUs.get();
}
/** Gets the indicator whether we are choking the peer.
*
* @return the indicator whether we are choking the peer
*/
public boolean areWeChokingThePeer() {
return areWeChokingThePeer.get();
}
/** Gets the indicator that the peer is choking us. Not synchronized in order to avoid deadlock with
* OutgoingPeerMessageHandler.run().
*
* @return the indicator that the peer is choking us
*/
public boolean isPeerChokingUs() {
return isPeerChokingUs.get();
}
/** Gets the total number of downloaded bytes.
*
* @return the total number of downloaded bytes
*/
public long getNbrBytesDownloaded() {
return nbrBytesDownloaded.get();
}
/** Sets the total number of downloaded bytes.
*
* @param nbrBytesDownloaded the total number of downloaded bytes
*/
public void setNbrBytesDownloaded(final long nbrBytesDownloaded) {
this.nbrBytesDownloaded.set(nbrBytesDownloaded);
}
/** Gets the total number of uploaded bytes.
*
* @return the total number of uploaded bytes
*/
public long getNbrBytesUploaded() {
return nbrBytesUploaded.get();
}
/** Sets the total number of uploaded bytes.
*
* @param nbrBytesUploaded the total number of uploaded bytes
*/
public void setNbrBytesUploaded(final long nbrBytesUploaded) {
//Preconditions
assert nbrBytesUploaded >= 0 : "nbrBytesUploaded must not be negative";
this.nbrBytesUploaded.set(nbrBytesUploaded);
}
/** Gets whether the peer is interested in us.
*
* @return whether the peer is interested in us
*/
public boolean isPeerInterestedInUs() {
return isPeerInterestedInUs.get();
}
/** Gets the communication channel between us and the peer.
*
* @return the communication channel between us and the peer
*/
public Channel getChannel() {
return channel;
}
/** Sets the communication channel between us and the peer.
*
* @param channel the communication channel between us and the peer
*/
public void setChannel(final Channel channel) {
//Preconditions
assert channel != null : "channel must not be null";
this.channel = channel;
}
}