/* PeerState - Keeps track of the Peer state through connection callbacks.
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.
*/
package org.klomp.snark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.Log;
class PeerState implements DataLoader
{
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class);
private final Peer peer;
/** Fixme, used by Peer.disconnect() to get to the coordinator */
final PeerListener listener;
/** Null before we have it. locking: this */
private MetaInfo metainfo;
/** Null unless needed. Contains -1 for all. locking: this */
private List<Integer> havesBeforeMetaInfo;
// Interesting and choking describes whether we are interested in or
// are choking the other side.
volatile boolean interesting;
volatile boolean choking = true;
// Interested and choked describes whether the other side is
// interested in us or choked us.
volatile boolean interested;
volatile boolean choked = true;
/** the pieces the peer has. locking: this */
BitField bitfield;
// Package local for use by Peer.
final PeerConnectionIn in;
final PeerConnectionOut out;
// Outstanding request
private final List<Request> outstandingRequests = new ArrayList<Request>();
/** the tail (NOT the head) of the request queue */
private Request lastRequest = null;
// FIXME if piece size < PARTSIZE, pipeline could be bigger
private final static int MAX_PIPELINE = 5; // this is for outbound requests
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
public final static int PARTSIZE = 16*1024; // outbound request
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
private static final Integer PIECE_ALL = Integer.valueOf(-1);
/**
* @param metainfo null if in magnet mode
*/
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
PeerConnectionIn in, PeerConnectionOut out)
{
this.peer = peer;
this.listener = listener;
this.metainfo = metainfo;
this.in = in;
this.out = out;
}
// NOTE Methods that inspect or change the state synchronize (on this).
void keepAliveMessage()
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv alive");
/* XXX - ignored */
}
void chokeMessage(boolean choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
boolean resend = choked && !choke;
choked = choke;
listener.gotChoke(peer, choke);
if (interesting && !choked)
request(resend);
if (choked) {
out.cancelRequestMessages();
// old Roberts thrash us here, choke+unchoke right together
// The only problem with returning the partials to the coordinator
// is that chunks above a missing request are lost.
// Future enhancements to PartialPiece could keep track of the holes.
List<Request> pcs = returnPartialPieces();
if (!pcs.isEmpty()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " got choked, returning partial pieces to the PeerCoordinator: " + pcs);
listener.savePartialPieces(this.peer, pcs);
}
}
}
void interestedMessage(boolean interest)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (interest ? "" : "un")
+ "interested");
interested = interest;
listener.gotInterest(peer, interest);
}
void haveMessage(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv have(" + piece + ")");
// Sanity check
if (piece < 0) {
if (_log.shouldWarn())
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
return;
}
synchronized(this) {
if (metainfo == null) {
if (_log.shouldWarn())
_log.warn("Got HAVE " + piece + " before metainfo from " + peer);
if (bitfield != null) {
if (piece < bitfield.size())
bitfield.set(piece);
} else {
// note reception for later
if (havesBeforeMetaInfo == null) {
havesBeforeMetaInfo = new ArrayList<Integer>(8);
} else if (havesBeforeMetaInfo.size() > 1000) {
// don't blow up
if (_log.shouldWarn())
_log.warn("Got too many haves before metainfo from " + peer);
return;
}
havesBeforeMetaInfo.add(Integer.valueOf(piece));
}
return;
}
// Sanity check
if (piece >= metainfo.getPieces()) {
// XXX disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
return;
}
// Can happen if the other side never send a bitfield message.
if (bitfield == null)
bitfield = new BitField(metainfo.getPieces());
bitfield.set(piece);
}
if (listener.gotHave(peer, piece))
setInteresting(true);
}
void bitfieldMessage(byte[] bitmap) {
bitfieldMessage(bitmap, false);
}
/**
* @param bitmap null to use the isAll param
* @param isAll only if bitmap == null: true for have_all, false for have_none
* @since 0.9.21
*/
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
if (_log.shouldLog(Log.DEBUG)) {
if (bitmap != null)
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
else if (isAll)
_log.debug(peer + " rcv bitfield HAVE_ALL");
else
_log.debug(peer + " rcv bitfield HAVE_NONE");
}
synchronized(this) {
if (bitfield != null)
{
// XXX - Be liberal in what you accept?
if (_log.shouldLog(Log.WARN))
_log.warn("Got unexpected bitfield message from " + peer);
return;
}
// XXX - Check for weird bitfield and disconnect?
// Will have to regenerate the bitfield after we know exactly
// how many pieces there are, as we don't know how many spare bits there are.
// This happens in setMetaInfo() below.
if (metainfo == null) {
if (bitmap != null) {
bitfield = new BitField(bitmap, bitmap.length * 8);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("have_x w/o metainfo: " + isAll);
if (isAll) {
// note reception for later
if (havesBeforeMetaInfo == null)
havesBeforeMetaInfo = new ArrayList<Integer>(1);
else
havesBeforeMetaInfo.clear();
havesBeforeMetaInfo.add(PIECE_ALL);
} // else HAVE_NONE, ignore
}
return;
} else {
if (bitmap != null) {
bitfield = new BitField(bitmap, metainfo.getPieces());
} else {
bitfield = new BitField(metainfo.getPieces());
if (isAll)
bitfield.setAll();
}
}
} // synch
boolean interest = listener.gotBitField(peer, bitfield);
setInteresting(interest);
if (bitfield.complete() && !interest) {
// They are seeding and we are seeding,
// why did they contact us? (robert)
// Dump them quick before we send our whole bitmap
if (_log.shouldLog(Log.WARN))
_log.warn("Disconnecting seed that connects to seeds: " + peer);
peer.disconnect(true);
}
}
void requestMessage(int piece, int begin, int length)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv request("
+ piece + ", " + begin + ", " + length + ") ");
if (metainfo == null)
return;
if (choking) {
if (peer.supportsFast()) {
if (_log.shouldInfo())
_log.info("Request received, sending reject to choked " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldInfo())
_log.info("Request received, but choking " + peer);
}
return;
}
// Sanity check
// There is no check here that we actually have the piece;
// this will be caught in loadData() below
if (piece < 0
|| piece >= metainfo.getPieces()
|| begin < 0
|| begin > metainfo.getPieceLength(piece)
|| length <= 0
|| length > MAX_PARTSIZE)
{
// XXX - Protocol error -> disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return;
}
// Limit total pipelined requests to MAX_PIPELINE bytes
// to conserve memory and prevent DOS
// Todo: limit number of requests also? (robert 64 x 4KB)
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
{
if (peer.supportsFast()) {
if (_log.shouldWarn())
_log.warn("Rejecting request over pipeline limit from " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldWarn())
_log.warn("Discarding request over pipeline limit from " + peer);
}
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Queueing (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
// don't load the data into mem now, let PeerConnectionOut do it
out.sendPiece(piece, begin, length, this);
}
/**
* This is the callback that PeerConnectionOut calls
*
* @return bytes or null for errors such as not having the piece yet
* @throws RuntimeException on IOE getting the data
* @since 0.8.2
*/
public ByteArray loadData(int piece, int begin, int length) {
ByteArray pieceBytes = listener.gotRequest(peer, piece, begin, length);
if (pieceBytes == null)
{
// XXX - Protocol error-> diconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got request for unknown piece: " + piece);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
// More sanity checks
if (length != pieceBytes.getData().length)
{
// XXX - Protocol error-> disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got out of range 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
return pieceBytes;
}
/**
* Called when some bytes have left the outgoing connection.
* XXX - Should indicate whether it was a real piece or overhead.
*/
void uploaded(int size)
{
peer.uploaded(size);
listener.uploaded(peer, size);
}
// This is used to flag that we have to back up from the firstOutstandingRequest
// when calculating how far we've gotten
private Request pendingRequest;
/**
* Called when a full chunk (i.e. a piece message) has been received by
* PeerConnectionIn.
*
* This may block quite a while if it is the last chunk for a piece,
* as it calls the listener, who stores the piece and then calls
* havePiece for every peer on the torrent (including us).
*
*/
void pieceMessage(Request req)
{
int size = req.len;
peer.downloaded(size);
listener.downloaded(peer, size);
if (_log.shouldLog(Log.DEBUG))
_log.debug("got end of Chunk("
+ req.getPiece() + "," + req.off + "," + req.len + ") from "
+ peer);
// Last chunk needed for this piece?
// FIXME if priority changed to skip, we will think we're done when we aren't
if (getFirstOutstandingRequest(req.getPiece()) == -1)
{
// warning - may block here for a while
if (listener.gotPiece(peer, req.getPartialPiece()))
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got " + req.getPiece() + ": " + peer);
}
else
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
synchronized(this) {
// so we don't ask again
if (bitfield != null)
bitfield.clear(req.getPiece());
}
}
}
// ok done with this one
synchronized(this) {
pendingRequest = null;
}
}
/**
* @return index in outstandingRequests or -1
*/
synchronized private int getFirstOutstandingRequest(int piece)
{
for (int i = 0; i < outstandingRequests.size(); i++)
if (outstandingRequests.get(i).getPiece() == piece)
return i;
return -1;
}
/**
* Called when a piece message is being processed by the incoming
* connection. That is, when the header of the piece message was received.
* Returns null when there was no such request. It also
* requeues/sends requests when it thinks that they must have been
* lost.
*/
Request getOutstandingRequest(int piece, int begin, int length)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("got start of Chunk("
+ piece + "," + begin + "," + length + ") from "
+ peer);
// Lookup the correct piece chunk request from the list.
Request req;
synchronized(this)
{
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1) {
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer);
return null;
}
req = outstandingRequests.get(r);
while (req.getPiece() == piece && req.off != begin
&& r < outstandingRequests.size() - 1)
{
r++;
req = outstandingRequests.get(r);
}
// Something wrong?
if (req.getPiece() != piece || req.off != begin || req.len != length)
{
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested or unneeded 'piece: "
+ piece + ", "
+ begin + ", "
+ length + "' received from "
+ peer);
return null;
}
// note that this request is being read
pendingRequest = req;
// Report missing requests.
if (r != 0)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Some requests dropped, got " + req
+ ", wanted for peer: " + peer);
for (int i = 0; i < r; i++)
{
Request dropReq = outstandingRequests.remove(0);
outstandingRequests.add(dropReq);
if (!choked)
out.sendRequest(dropReq);
if (_log.shouldLog(Log.WARN))
_log.warn("dropped " + dropReq + " with peer " + peer);
}
}
outstandingRequests.remove(0);
}
// Request more if necessary to keep the pipeline filled.
addRequest();
return req;
}
/**
* @return lowest offset of any request for the piece
* @since 0.8.2
*/
synchronized private Request getLowestOutstandingRequest(int piece) {
Request rv = null;
int lowest = Integer.MAX_VALUE;
for (Request r : outstandingRequests) {
if (r.getPiece() == piece && r.off < lowest) {
lowest = r.off;
rv = r;
}
}
if (pendingRequest != null &&
pendingRequest.getPiece() == piece && pendingRequest.off < lowest)
rv = pendingRequest;
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " lowest for " + piece + " is " + rv + " out of " + pendingRequest + " and " + outstandingRequests);
return rv;
}
/**
* Get partial pieces, give them back to PeerCoordinator.
* Clears the request queue.
* @return List of PartialPieces, even those with an offset == 0, or empty list
* @since 0.8.2
*/
synchronized List<Request> returnPartialPieces()
{
Set<Integer> pcs = getRequestedPieces();
List<Request> rv = new ArrayList<Request>(pcs.size());
for (Integer p : pcs) {
Request req = getLowestOutstandingRequest(p.intValue());
if (req != null) {
PartialPiece pp = req.getPartialPiece();
synchronized(pp) {
int dl = pp.getDownloaded();
if (req.off != dl)
req = new Request(pp, dl, 1);
}
rv.add(req);
}
}
outstandingRequests.clear();
pendingRequest = null;
lastRequest = null;
return rv;
}
/**
* @return all pieces we are currently requesting, or empty Set
*/
synchronized private Set<Integer> getRequestedPieces() {
Set<Integer> rv = new HashSet<Integer>(outstandingRequests.size() + 1);
for (Request req : outstandingRequests) {
rv.add(Integer.valueOf(req.getPiece()));
if (pendingRequest != null)
rv.add(Integer.valueOf(pendingRequest.getPiece()));
}
return rv;
}
void cancelMessage(int piece, int begin, int length)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got cancel message ("
+ piece + ", " + begin + ", " + length + ")");
out.cancelRequest(piece, begin, length);
}
/** @since 0.8.2 */
void extensionMessage(int id, byte[] bs)
{
if (metainfo != null && metainfo.isPrivate() &&
(id == ExtensionHandler.ID_METADATA || id == ExtensionHandler.ID_PEX)) {
// shouldn't get this since we didn't advertise it but they could send it anyway
if (_log.shouldLog(Log.WARN))
_log.warn("Private torrent, ignoring ext msg " + id);
return;
}
ExtensionHandler.handleMessage(peer, listener, id, bs);
// Peer coord will get metadata from MagnetState,
// verify, and then call gotMetaInfo()
listener.gotExtension(peer, id, bs);
}
/**
* Switch from magnet mode to normal mode.
* If we already have the metainfo, this does nothing.
* @param meta non-null
* @since 0.8.4
*/
public synchronized void setMetaInfo(MetaInfo meta) {
if (metainfo != null)
return;
if (bitfield != null) {
if (bitfield.size() != meta.getPieces())
// fix bitfield, it was too big by 1-7 bits
bitfield = new BitField(bitfield.getFieldBytes(), meta.getPieces());
// else no extra
} else if (havesBeforeMetaInfo != null) {
// initialize it now
bitfield = new BitField(meta.getPieces());
} else {
// it will be initialized later
//bitfield = new BitField(meta.getPieces());
}
metainfo = meta;
if (bitfield != null) {
if (havesBeforeMetaInfo != null) {
// set all 'haves' we got before the metainfo in the bitfield
for (Integer i : havesBeforeMetaInfo) {
if (i.equals(PIECE_ALL)) {
bitfield.setAll();
if (_log.shouldLog(Log.WARN))
_log.warn("set have_all after rcv metainfo");
break;
}
int piece = i.intValue();
if (piece >= 0 && piece < meta.getPieces())
bitfield.set(piece);
if (_log.shouldLog(Log.WARN))
_log.warn("set have " + piece + " after rcv metainfo");
}
havesBeforeMetaInfo = null;
}
if (bitfield.count() > 0)
setInteresting(true);
}
}
/**
* Unused
* @since 0.8.4
*/
void portMessage(int port)
{
// for compatibility with old DHT PORT message
listener.gotPort(peer, port, port + 1);
}
/////////// fast message handlers /////////
/**
* BEP 6
* Treated as "have" for now
* @since 0.9.21
*/
void suggestMessage(int piece) {
if (_log.shouldInfo())
_log.info("Handling suggest as have(" + piece + ") from " + peer);
haveMessage(piece);
}
/**
* BEP 6
* @param isAll true for have_all, false for have_none
* @since 0.9.21
*/
void haveMessage(boolean isAll) {
bitfieldMessage(null, isAll);
}
/**
* BEP 6
* If the peer rejects lower chunks but not higher ones, thus creating holes,
* we won't figure it out and the piece will fail, since we don't currently
* keep a chunk bitmap in PartialPiece.
* As long as the peer rejects all the chunks, or rejects only the last chunks,
* no holes are created and we will be fine. The reject messages may be in any order,
* just don't make a hole when it's over.
*
* @since 0.9.21
*/
void rejectMessage(int piece, int begin, int length) {
if (_log.shouldInfo())
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
out.cancelRequest(piece, begin, length);
synchronized(this) {
Request deletedRequest = null;
// for this piece only
boolean haveMoreRequests = false;
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
Request req = iter.next();
if (req.getPiece() == piece) {
if (req.off == begin && req.len == length) {
iter.remove();
deletedRequest = req;
} else {
haveMoreRequests = true;
}
}
}
if (deletedRequest != null && !haveMoreRequests) {
// We must return the piece to the coordinator
// Create a new fake request so we can set the offset correctly
PartialPiece pp = deletedRequest.getPartialPiece();
int downloaded = pp.getDownloaded();
Request req;
if (deletedRequest.off == downloaded)
req = deletedRequest;
else
req = new Request(pp, downloaded, 1);
List<Request> pcs = Collections.singletonList(req);
listener.savePartialPieces(this.peer, pcs);
if (_log.shouldWarn())
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
}
if (lastRequest != null && lastRequest.getPiece() == piece &&
lastRequest.off == begin && lastRequest.len == length)
lastRequest = null;
}
}
/**
* BEP 6
* Ignored for now
* @since 0.9.21
*/
void allowedFastMessage(int piece) {
if (_log.shouldInfo())
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
}
void unknownMessage(int type, byte[] bs)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Warning: Ignoring unknown message type: " + type
+ " length: " + bs.length);
}
/////////// end message handlers /////////
/**
* We now have this piece.
* Tell the peer and cancel any requests for the piece.
*/
void havePiece(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
// Tell the other side that we are no longer interested in any of
// the outstanding requests for this piece.
cancelPiece(piece);
// Tell the other side that we really have this piece.
out.sendHave(piece);
// Request something else if necessary.
addRequest();
/**** taken care of in addRequest()
synchronized(this)
{
// Is the peer still interesting?
if (lastRequest == null)
setInteresting(false);
}
****/
}
/**
* Tell the other side that we are no longer interested in any of
* the outstanding requests (if any) for this piece.
* @since 0.8.1
*/
synchronized void cancelPiece(int piece) {
if (lastRequest != null && lastRequest.getPiece() == piece)
lastRequest = null;
Iterator<Request> it = outstandingRequests.iterator();
while (it.hasNext())
{
Request req = it.next();
if (req.getPiece() == piece)
{
it.remove();
// Send cancel even when we are choked to make sure that it is
// really never ever send.
out.sendCancel(req);
req.getPartialPiece().release();
}
}
}
/**
* Are we currently requesting the piece?
* @deprecated deadlocks
* @since 0.8.1
*/
@Deprecated
synchronized boolean isRequesting(int piece) {
if (pendingRequest != null && pendingRequest.getPiece() == piece)
return true;
for (Request req : outstandingRequests) {
if (req.getPiece() == piece)
return true;
}
return false;
}
/**
* Starts or resumes requesting pieces.
* @param resend should we resend outstanding requests?
*/
private void request(boolean resend)
{
// Are there outstanding requests that have to be resend?
if (resend)
{
synchronized (this) {
if (!outstandingRequests.isEmpty()) {
out.sendRequests(outstandingRequests);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resending requests to " + peer + outstandingRequests);
}
}
}
// Add/Send some more requests if necessary.
addRequest();
}
/**
* Adds a new request to the outstanding requests list.
* Then send interested if we weren't.
* Then send new requests if not choked.
* If nothing to request, send not interested if we were.
*
* This is called from several places:
*<pre>
* By getOustandingRequest() when the first part of a chunk comes in
* By havePiece() when somebody got a new piece completed
* By chokeMessage() when we receive an unchoke
* By setInteresting() when we are now interested
* By PeerCoordinator.updatePiecePriorities()
*</pre>
*/
synchronized void addRequest()
{
// no bitfield yet? nothing to request then.
if (bitfield == null)
return;
if (metainfo == null)
return;
boolean more_pieces = true;
while (more_pieces)
{
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
// We want something and we don't have outstanding requests?
if (more_pieces && lastRequest == null) {
// we have nothing in the queue right now
if (!interesting) {
// If we need something, set interesting but delay pulling
// a request from the PeerCoordinator until unchoked.
if (listener.needPiece(this.peer, bitfield)) {
setInteresting(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " addRequest() we need something, setting interesting, delaying requestNextPiece()");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " addRequest() needs nothing");
}
return;
}
if (choked) {
// If choked, delay pulling
// a request from the PeerCoordinator until unchoked.
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " addRequest() we are choked, delaying requestNextPiece()");
return;
}
// huh? rv unused
more_pieces = requestNextPiece();
} else if (more_pieces) // We want something
{
int pieceLength;
boolean isLastChunk;
pieceLength = metainfo.getPieceLength(lastRequest.getPiece());
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
// Last part of a piece?
if (isLastChunk)
more_pieces = requestNextPiece();
else
{
PartialPiece nextPiece = lastRequest.getPartialPiece();
int nextBegin = lastRequest.off + PARTSIZE;
int maxLength = pieceLength - nextBegin;
int nextLength = maxLength > PARTSIZE ? PARTSIZE
: maxLength;
Request req
= new Request(nextPiece,nextBegin, nextLength);
outstandingRequests.add(req);
if (!choked)
out.sendRequest(req);
lastRequest = req;
}
}
}
// failsafe
// However this is bad as it thrashes the peer when we change our mind
// Ticket 691 cause here?
if (interesting && lastRequest == null && outstandingRequests.isEmpty())
setInteresting(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " requests " + outstandingRequests);
}
/**
* Starts requesting first chunk of next piece. Returns true if
* something has been added to the requests, false otherwise.
* Caller should synchronize.
*/
private boolean requestNextPiece()
{
// Check that we already know what the other side has.
if (bitfield != null) {
// Check for adopting an orphaned partial piece
PartialPiece pp = listener.getPartialPiece(peer, bitfield);
if (pp != null) {
// Double-check that r not already in outstandingRequests
if (!getRequestedPieces().contains(Integer.valueOf(pp.getPiece()))) {
Request r = pp.getRequest();
outstandingRequests.add(r);
if (!choked)
out.sendRequest(r);
lastRequest = r;
return true;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Got dup from coord: " + pp);
pp.release();
}
}
/******* getPartialPiece() does it all now
// Note that in addition to the bitfield, PeerCoordinator uses
// its request tracking and isRequesting() to determine
// what piece to give us next.
int nextPiece = listener.wantPiece(peer, bitfield);
if (nextPiece != -1
&& (lastRequest == null || lastRequest.getPiece() != nextPiece)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " want piece " + nextPiece);
// Fail safe to make sure we are interested
// When we transition into the end game we may not be interested...
if (!interesting) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " transition to end game, setting interesting");
interesting = true;
out.sendInterest(true);
}
int piece_length = metainfo.getPieceLength(nextPiece);
//Catch a common place for OOMs esp. on 1MB pieces
byte[] bs;
try {
bs = new byte[piece_length];
} catch (OutOfMemoryError oom) {
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
return false;
}
int length = Math.min(piece_length, PARTSIZE);
Request req = new Request(nextPiece, bs, 0, length);
outstandingRequests.add(req);
if (!choked)
out.sendRequest(req);
lastRequest = req;
return true;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " no more pieces to request");
}
*******/
}
// failsafe
// However this is bad as it thrashes the peer when we change our mind
// Ticket 691 cause here?
if (outstandingRequests.isEmpty())
lastRequest = null;
// If we are not in the end game, we may run out of things to request
// because we are asking other peers. Set not-interesting now rather than
// wait for those other requests to be satisfied via havePiece()
if (interesting && lastRequest == null) {
interesting = false;
out.sendInterest(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " nothing more to request, now uninteresting");
}
return false;
}
synchronized void setInteresting(boolean interest)
{
if (interest != interesting)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setInteresting(" + interest + ")");
interesting = interest;
out.sendInterest(interest);
if (interesting && !choked)
request(true); // we shouldnt have any pending requests, but if we do, resend them
}
}
synchronized void setChoking(boolean choke)
{
if (choking != choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setChoking(" + choke + ")");
choking = choke;
out.sendChoke(choke);
}
}
void keepAlive()
{
out.sendAlive();
}
synchronized void retransmitRequests()
{
if (interesting && !choked)
out.retransmitRequests(outstandingRequests);
}
/**
* debug
* @return string or null
* @since 0.8.1
*/
synchronized String getRequests() {
if (outstandingRequests.isEmpty())
return null;
else
return outstandingRequests.toString();
}
}