forked from I2P_Developers/i2p.i2p
i2psnark:
- Return partial piece to coordinator after reject - Fix tracking of downloaded portion of piece after reject - Send reject on receipt of bad request - Mark piece unrequested after receiving bad data, so it will be requested again, but not from the same peer - Fix NPE in Request constructor on error - Fix stuck before completion due to reject handling (ticket #1633)
This commit is contained in:
@ -107,6 +107,27 @@ public class BitField
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given bit to false.
|
||||||
|
*
|
||||||
|
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||||
|
* bigger then size (inclusive).
|
||||||
|
* @since 0.9.22
|
||||||
|
*/
|
||||||
|
public void clear(int bit)
|
||||||
|
{
|
||||||
|
if (bit < 0 || bit >= size)
|
||||||
|
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||||
|
int index = bit/8;
|
||||||
|
int mask = 128 >> (bit % 8);
|
||||||
|
synchronized(this) {
|
||||||
|
if ((bitfield[index] & mask) != 0) {
|
||||||
|
count--;
|
||||||
|
bitfield[index] &= ~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets all bits to true.
|
* Sets all bits to true.
|
||||||
*
|
*
|
||||||
|
@ -108,7 +108,8 @@ class PartialPiece implements Comparable<PartialPiece> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert this PartialPiece to a request for the next chunk.
|
* Convert this PartialPiece to a request for the next chunk.
|
||||||
* Used by PeerState only.
|
* Used by PeerState only. This depends on the downloaded value
|
||||||
|
* as set by setDownloaded() or read().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public Request getRequest() {
|
public Request getRequest() {
|
||||||
@ -128,14 +129,16 @@ class PartialPiece implements Comparable<PartialPiece> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many bytes are good - only valid by setDownloaded()
|
* How many bytes are good - as set by setDownloaded() or read()
|
||||||
*/
|
*/
|
||||||
public int getDownloaded() {
|
public int getDownloaded() {
|
||||||
return this.off;
|
return this.off;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this before returning a PartialPiece to the PeerCoordinator
|
* Call this if necessary before returning a PartialPiece to the PeerCoordinator.
|
||||||
|
* We do not use a bitmap to track individual chunks received.
|
||||||
|
* Any chunks after a 'hole' will be lost.
|
||||||
* @since 0.9.1
|
* @since 0.9.1
|
||||||
*/
|
*/
|
||||||
public void setDownloaded(int offset) {
|
public void setDownloaded(int offset) {
|
||||||
@ -191,11 +194,20 @@ class PartialPiece implements Comparable<PartialPiece> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocking.
|
* Blocking.
|
||||||
|
* If offset matches the previous downloaded amount
|
||||||
|
* (as set by a previous call to read() or setDownlaoded()),
|
||||||
|
* the downloaded amount will be incremented by len.
|
||||||
|
*
|
||||||
* @since 0.9.1
|
* @since 0.9.1
|
||||||
*/
|
*/
|
||||||
public void read(DataInputStream din, int off, int len) throws IOException {
|
public void read(DataInputStream din, int offset, int len) throws IOException {
|
||||||
if (bs != null) {
|
if (bs != null) {
|
||||||
din.readFully(bs, off, len);
|
din.readFully(bs, offset, len);
|
||||||
|
synchronized (this) {
|
||||||
|
// only works for in-order chunks
|
||||||
|
if (this.off == offset)
|
||||||
|
this.off += len;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// read in fully before synching on raf
|
// read in fully before synching on raf
|
||||||
ByteArray ba;
|
ByteArray ba;
|
||||||
@ -211,8 +223,11 @@ class PartialPiece implements Comparable<PartialPiece> {
|
|||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (raf == null)
|
if (raf == null)
|
||||||
createTemp();
|
createTemp();
|
||||||
raf.seek(off);
|
raf.seek(offset);
|
||||||
raf.write(tmp);
|
raf.write(tmp);
|
||||||
|
// only works for in-order chunks
|
||||||
|
if (this.off == offset)
|
||||||
|
this.off += len;
|
||||||
}
|
}
|
||||||
if (ba != null)
|
if (ba != null)
|
||||||
_cache.release(ba, false);
|
_cache.release(ba, false);
|
||||||
|
@ -170,6 +170,8 @@ class PeerConnectionOut implements Runnable
|
|||||||
lastSent = System.currentTimeMillis();
|
lastSent = System.currentTimeMillis();
|
||||||
|
|
||||||
// Remove all piece messages after sending a choke message.
|
// Remove all piece messages after sending a choke message.
|
||||||
|
// FiXME this causes REJECT messages to be sent before sending the CHOKE;
|
||||||
|
// BEP 6 recommends sending them after.
|
||||||
if (m.type == Message.CHOKE)
|
if (m.type == Message.CHOKE)
|
||||||
removeMessage(Message.PIECE);
|
removeMessage(Message.PIECE);
|
||||||
|
|
||||||
|
@ -920,6 +920,7 @@ class PeerCoordinator implements PeerListener
|
|||||||
* Returns a byte array containing the requested piece or null of
|
* Returns a byte array containing the requested piece or null of
|
||||||
* the piece is unknown.
|
* the piece is unknown.
|
||||||
*
|
*
|
||||||
|
* @return bytes or null for errors such as not having the piece yet
|
||||||
* @throws RuntimeException on IOE getting the data
|
* @throws RuntimeException on IOE getting the data
|
||||||
*/
|
*/
|
||||||
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
|
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
|
||||||
@ -1010,9 +1011,21 @@ class PeerCoordinator implements PeerListener
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// so we will try again
|
||||||
|
markUnrequested(peer, piece);
|
||||||
|
// just in case
|
||||||
|
removePartialPiece(piece);
|
||||||
// Oops. We didn't actually download this then... :(
|
// Oops. We didn't actually download this then... :(
|
||||||
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
||||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
// Mark this peer as not having the piece. PeerState will update its bitfield.
|
||||||
|
for (Piece pc : wantedPieces) {
|
||||||
|
if (pc.getId() == piece) {
|
||||||
|
pc.removePeer(peer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||||
return false; // No need to announce BAD piece to peers.
|
return false; // No need to announce BAD piece to peers.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1141,8 +1154,9 @@ class PeerCoordinator implements PeerListener
|
|||||||
*
|
*
|
||||||
* Also mark the piece unrequested if this peer was the only one.
|
* Also mark the piece unrequested if this peer was the only one.
|
||||||
*
|
*
|
||||||
* @param peer partials, must include the zero-offset (empty) ones too
|
* @param peer partials, must include the zero-offset (empty) ones too.
|
||||||
* No dup pieces, piece.setDownloaded() must be set
|
* No dup pieces, piece.setDownloaded() must be set.
|
||||||
|
* len field in Requests is ignored.
|
||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
public void savePartialPieces(Peer peer, List<Request> partials)
|
public void savePartialPieces(Peer peer, List<Request> partials)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
package org.klomp.snark;
|
package org.klomp.snark;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -238,6 +239,8 @@ class PeerState implements DataLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
// There is no check here that we actually have the piece;
|
||||||
|
// this will be caught in loadData() below
|
||||||
if (piece < 0
|
if (piece < 0
|
||||||
|| piece >= metainfo.getPieces()
|
|| piece >= metainfo.getPieces()
|
||||||
|| begin < 0
|
|| begin < 0
|
||||||
@ -251,6 +254,8 @@ class PeerState implements DataLoader
|
|||||||
+ ", " + begin
|
+ ", " + begin
|
||||||
+ ", " + length
|
+ ", " + length
|
||||||
+ "' message from " + peer);
|
+ "' message from " + peer);
|
||||||
|
if (peer.supportsFast())
|
||||||
|
out.sendReject(piece, begin, length);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +286,8 @@ class PeerState implements DataLoader
|
|||||||
/**
|
/**
|
||||||
* This is the callback that PeerConnectionOut calls
|
* This is the callback that PeerConnectionOut calls
|
||||||
*
|
*
|
||||||
* @return bytes or null for errors
|
* @return bytes or null for errors such as not having the piece yet
|
||||||
|
* @throws RuntimeException on IOE getting the data
|
||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
public ByteArray loadData(int piece, int begin, int length) {
|
public ByteArray loadData(int piece, int begin, int length) {
|
||||||
@ -291,6 +297,8 @@ class PeerState implements DataLoader
|
|||||||
// XXX - Protocol error-> diconnect?
|
// XXX - Protocol error-> diconnect?
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.warn("Got request for unknown piece: " + piece);
|
_log.warn("Got request for unknown piece: " + piece);
|
||||||
|
if (peer.supportsFast())
|
||||||
|
out.sendReject(piece, begin, length);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +311,8 @@ class PeerState implements DataLoader
|
|||||||
+ ", " + begin
|
+ ", " + begin
|
||||||
+ ", " + length
|
+ ", " + length
|
||||||
+ "' message from " + peer);
|
+ "' message from " + peer);
|
||||||
|
if (peer.supportsFast())
|
||||||
|
out.sendReject(piece, begin, length);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +370,11 @@ class PeerState implements DataLoader
|
|||||||
{
|
{
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
||||||
|
synchronized(this) {
|
||||||
|
// so we don't ask again
|
||||||
|
if (bitfield != null)
|
||||||
|
bitfield.clear(req.getPiece());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +508,12 @@ class PeerState implements DataLoader
|
|||||||
for (Integer p : pcs) {
|
for (Integer p : pcs) {
|
||||||
Request req = getLowestOutstandingRequest(p.intValue());
|
Request req = getLowestOutstandingRequest(p.intValue());
|
||||||
if (req != null) {
|
if (req != null) {
|
||||||
req.getPartialPiece().setDownloaded(req.off);
|
PartialPiece pp = req.getPartialPiece();
|
||||||
|
synchronized(pp) {
|
||||||
|
int dl = pp.getDownloaded();
|
||||||
|
if (req.off != dl)
|
||||||
|
req = new Request(pp, dl, 1);
|
||||||
|
}
|
||||||
rv.add(req);
|
rv.add(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,6 +618,13 @@ class PeerState implements DataLoader
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* BEP 6
|
* 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
|
* @since 0.9.21
|
||||||
*/
|
*/
|
||||||
void rejectMessage(int piece, int begin, int length) {
|
void rejectMessage(int piece, int begin, int length) {
|
||||||
@ -605,10 +632,34 @@ class PeerState implements DataLoader
|
|||||||
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||||
out.cancelRequest(piece, begin, length);
|
out.cancelRequest(piece, begin, length);
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
Request deletedRequest = null;
|
||||||
|
// for this piece only
|
||||||
|
boolean haveMoreRequests = false;
|
||||||
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
||||||
Request req = iter.next();
|
Request req = iter.next();
|
||||||
if (req.getPiece() == piece && req.off == begin && req.len == length)
|
if (req.getPiece() == piece) {
|
||||||
iter.remove();
|
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 &&
|
if (lastRequest != null && lastRequest.getPiece() == piece &&
|
||||||
lastRequest.off == begin && lastRequest.len == length)
|
lastRequest.off == begin && lastRequest.len == length)
|
||||||
|
@ -43,13 +43,13 @@ class Request
|
|||||||
*/
|
*/
|
||||||
Request(PartialPiece piece, int off, int len)
|
Request(PartialPiece piece, int off, int len)
|
||||||
{
|
{
|
||||||
// Sanity check
|
|
||||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
|
||||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
|
||||||
|
|
||||||
this.piece = piece;
|
this.piece = piece;
|
||||||
this.off = off;
|
this.off = off;
|
||||||
this.len = len;
|
this.len = len;
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||||
|
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
14
history.txt
14
history.txt
@ -1,3 +1,17 @@
|
|||||||
|
2015-08-25 zzz
|
||||||
|
* i2psnark:
|
||||||
|
- Return partial piece to coordinator after reject
|
||||||
|
- Fix tracking of downloaded portion of piece after reject
|
||||||
|
- Send reject on receipt of bad request
|
||||||
|
- Mark piece unrequested after receiving bad data, so it
|
||||||
|
will be requested again, but not from the same peer
|
||||||
|
- Fix NPE in Request constructor on error
|
||||||
|
- Fix stuck before completion due to reject handling (ticket #1633)
|
||||||
|
|
||||||
|
2015-08-02 zzz
|
||||||
|
* Console: Fix SSL excluded ciphers (thx lazyg)
|
||||||
|
* SU3File: Add keystore password command line option
|
||||||
|
|
||||||
* 2015-07-31 0.9.21 released
|
* 2015-07-31 0.9.21 released
|
||||||
|
|
||||||
2015-07-27 zzz
|
2015-07-27 zzz
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 1;
|
public final static long BUILD = 2;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
Reference in New Issue
Block a user