]> granicus.if.org Git - transmission/commitdiff
(trunk libT) #3767 "rarest first policy" -- fixed.
authorJordan Lee <jordan@transmissionbt.com>
Thu, 17 Feb 2011 05:14:53 +0000 (05:14 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Thu, 17 Feb 2011 05:14:53 +0000 (05:14 +0000)
This commit, started by a patch from athy, implements a rarest first policy when deciding which pieces to request from peers. It keeps a count of how many peers have each piece, and updates the count when getting bitfields, have, have all, and have none messages, as well as decrementing the counts when peers disconnect.

This running total is generated only for downloading torrents. Seeds don't have this overhead.

libtransmission/peer-common.h
libtransmission/peer-mgr.c
libtransmission/peer-msgs.c
libtransmission/webseed.c

index 55e1776132f3c206496a92cc242af7d119d79817..c4ad31414c661615ff1398e35a66a317fb4f101e 100644 (file)
@@ -28,6 +28,8 @@
 
 #include "transmission.h"
 
+struct tr_bitfield;
+
 enum
 {
     /** when we're making requests from another peer,
@@ -64,6 +66,10 @@ typedef enum
     TR_PEER_CLIENT_GOT_SUGGEST,
     TR_PEER_CLIENT_GOT_PORT,
     TR_PEER_CLIENT_GOT_REJ,
+    TR_PEER_CLIENT_GOT_BITFIELD,
+    TR_PEER_CLIENT_GOT_HAVE,
+    TR_PEER_CLIENT_GOT_HAVE_ALL,
+    TR_PEER_CLIENT_GOT_HAVE_NONE,
     TR_PEER_PEER_GOT_DATA,
     TR_PEER_PEER_PROGRESS,
     TR_PEER_ERROR
@@ -72,17 +78,21 @@ PeerEventType;
 
 typedef struct
 {
-    PeerEventType    eventType;
-    uint32_t         pieceIndex;   /* for GOT_BLOCK, CANCEL, ALLOWED, SUGGEST */
-    uint32_t         offset;       /* for GOT_BLOCK */
-    uint32_t         length;       /* for GOT_BLOCK + GOT_DATA */
-    float            progress;     /* for PEER_PROGRESS */
-    int              err;          /* errno for GOT_ERROR */
-    tr_bool          wasPieceData; /* for GOT_DATA */
-    tr_port          port;         /* for GOT_PORT */
+    PeerEventType         eventType;
+
+    uint32_t              pieceIndex;   /* for GOT_BLOCK, GOT_HAVE, CANCEL, ALLOWED, SUGGEST */
+    struct tr_bitfield  * bitfield;     /* for GOT_BITFIELD */
+    uint32_t              offset;       /* for GOT_BLOCK */
+    uint32_t              length;       /* for GOT_BLOCK + GOT_DATA */
+    float                 progress;     /* for PEER_PROGRESS */
+    int                   err;          /* errno for GOT_ERROR */
+    tr_bool               wasPieceData; /* for GOT_DATA */
+    tr_port               port;         /* for GOT_PORT */
 }
 tr_peer_event;
 
+extern const tr_peer_event TR_PEER_EVENT_INIT;
+
 struct tr_peer;
 
 typedef void tr_peer_callback( struct tr_peer       * peer,
index bf9ec429e92f425052ffaa3efb5e19ab3da62cff..3f951acee7db11059b6d601c60d0e8337874a408 100644 (file)
@@ -98,6 +98,7 @@ enum
     CANCEL_HISTORY_SEC = 60
 };
 
+const tr_peer_event TR_PEER_EVENT_INIT = { 0, 0, NULL, 0, 0, 0.0f, 0, FALSE, 0 };
 
 /**
 ***
@@ -177,6 +178,13 @@ struct weighted_piece
     int16_t requestCount;
 };
 
+enum piece_sort_state
+{
+    PIECES_UNSORTED,
+    PIECES_SORTED_BY_INDEX,
+    PIECES_SORTED_BY_WEIGHT
+};
+
 /** @brief Opaque, per-torrent data structure for peer connection information */
 typedef struct tr_torrent_peers
 {
@@ -200,6 +208,14 @@ typedef struct tr_torrent_peers
 
     struct weighted_piece    * pieces;
     int                        pieceCount;
+    enum piece_sort_state      pieceSortState;
+
+    /* An array of pieceCount items stating how many peers have each piece.
+       This is used to help us for downloading pieces "rarest first."
+       This may be NULL if we don't have metainfo yet, or if we're not
+       downloading and don't care about rarity */
+    uint16_t                 * pieceReplication;
+    size_t                     pieceReplicationSize;
 
     int                        interestedCount;
     int                        maxPeers;
@@ -424,27 +440,44 @@ peerDestructor( Torrent * t, tr_peer * peer )
     tr_free( peer );
 }
 
-static void
-removePeer( Torrent * t, tr_peer * peer )
+static tr_bool
+replicationExists( const Torrent * t )
 {
-    tr_peer * removed;
-    struct peer_atom * atom = peer->atom;
-
-    assert( torrentIsLocked( t ) );
-    assert( atom );
-
-    atom->time = tr_time( );
+    return t->pieceReplication != NULL;
+}
 
-    removed = tr_ptrArrayRemoveSorted( &t->peers, peer, peerCompare );
-    assert( removed == peer );
-    peerDestructor( t, removed );
+static void
+replicationFree( Torrent * t )
+{
+    tr_free( t->pieceReplication );
+    t->pieceReplication = NULL;
+    t->pieceReplicationSize = 0;
 }
 
 static void
-removeAllPeers( Torrent * t )
+replicationNew( Torrent * t )
 {
-    while( !tr_ptrArrayEmpty( &t->peers ) )
-        removePeer( t, tr_ptrArrayNth( &t->peers, 0 ) );
+    tr_piece_index_t piece_i;
+    const tr_piece_index_t piece_count = t->tor->info.pieceCount;
+    tr_peer ** peers = (tr_peer**) tr_ptrArrayBase( &t->peers );
+    const int peer_count = tr_ptrArraySize( &t->peers );
+
+    assert( !replicationExists( t ) );
+
+    t->pieceReplicationSize = piece_count;
+    t->pieceReplication = tr_new0( uint16_t, piece_count );
+
+    for( piece_i=0; piece_i<piece_count; ++piece_i )
+    {
+        int peer_i;
+        uint16_t r = 0;
+
+        for( peer_i=0; peer_i<peer_count; ++peer_i )
+            if( tr_bitsetHasFast( &peers[peer_i]->have, piece_i ) )
+                ++r;
+
+        t->pieceReplication[piece_i] = r;
+    }
 }
 
 static void
@@ -463,6 +496,8 @@ torrentDestructor( void * vt )
     tr_ptrArrayDestruct( &t->outgoingHandshakes, NULL );
     tr_ptrArrayDestruct( &t->peers, NULL );
 
+    replicationFree( t );
+
     tr_free( t->requests );
     tr_free( t->pieces );
     tr_free( t );
@@ -784,19 +819,79 @@ requestListRemove( Torrent * t, tr_block_index_t block, const tr_peer * peer )
     }
 }
 
-/**
-*** struct weighted_piece
-**/
+static int
+countActiveWebseeds( const Torrent * t )
+{
+    int activeCount = 0;
+    const tr_webseed ** w = (const tr_webseed **) tr_ptrArrayBase( &t->webseeds );
+    const tr_webseed ** const wend = w + tr_ptrArraySize( &t->webseeds );
 
-enum
+    for( ; w!=wend; ++w )
+        if( tr_webseedIsActive( *w ) )
+            ++activeCount;
+
+    return activeCount;
+}
+
+static void
+updateEndgame( Torrent * t )
 {
-    PIECES_UNSORTED,
-    PIECES_SORTED_BY_INDEX,
-    PIECES_SORTED_BY_WEIGHT
-};
+    const tr_torrent * tor = t->tor;
+    const tr_block_index_t missing = tr_cpBlocksMissing( &tor->completion );
+
+    assert( t->requestCount >= 0 );
+
+    if( (tr_block_index_t) t->requestCount < missing )
+    {
+        /* not in endgame */
+        t->endgame = 0;
+    }
+    else if( !t->endgame ) /* only recalculate when endgame first begins */
+    {
+        int numDownloading = 0;
+        const tr_peer ** p = (const tr_peer **) tr_ptrArrayBase( &t->peers );
+        const tr_peer ** const pend = p + tr_ptrArraySize( &t->peers );
+
+        /* add the active bittorrent peers... */
+        for( ; p!=pend; ++p )
+            if( (*p)->pendingReqsToPeer > 0 )
+                ++numDownloading;
+
+        /* add the active webseeds... */
+        numDownloading += countActiveWebseeds( t );
+
+        /* average number of pending requests per downloading peer */
+        t->endgame = t->requestCount / MAX( numDownloading, 1 );
+    }
+}
+
+
+/****
+*****
+*****  Piece List Manipulation / Accessors
+*****
+****/
+
+static inline void
+invalidatePieceSorting( Torrent * t )
+{
+    t->pieceSortState = PIECES_UNSORTED;
+}
 
 const tr_torrent * weightTorrent;
 
+const uint16_t * weightReplication;
+
+static void
+setComparePieceByWeightTorrent( Torrent * t )
+{
+    if( !replicationExists( t ) )
+        replicationNew( t );
+
+    weightTorrent = t->tor;
+    weightReplication = t->pieceReplication;
+}
+
 /* we try to create a "weight" s.t. high-priority pieces come before others,
  * and that partially-complete pieces come before empty ones. */
 static int
@@ -806,6 +901,7 @@ comparePieceByWeight( const void * va, const void * vb )
     const struct weighted_piece * b = vb;
     int ia, ib, missing, pending;
     const tr_torrent * tor = weightTorrent;
+    const uint16_t * rep = weightReplication;
 
     /* primary key: weight */
     missing = tr_cpMissingBlocksInPiece( &tor->completion, a->index );
@@ -823,7 +919,13 @@ comparePieceByWeight( const void * va, const void * vb )
     if( ia > ib ) return -1;
     if( ia < ib ) return 1;
 
-    /* tertiary key: random */
+    /* tertiary key: rarest first. */
+    ia = rep[a->index];
+    ib = rep[b->index];
+    if( ia < ib ) return -1;
+    if( ia > ib ) return 1;
+
+    /* quaternary key: random */
     if( a->salt < b->salt ) return -1;
     if( a->salt > b->salt ) return 1;
 
@@ -842,84 +944,70 @@ comparePieceByIndex( const void * va, const void * vb )
 }
 
 static void
-pieceListSort( Torrent * t, int mode )
+pieceListSort( Torrent * t, enum piece_sort_state state )
 {
-    assert( mode==PIECES_SORTED_BY_INDEX
-         || mode==PIECES_SORTED_BY_WEIGHT );
+    assert( state==PIECES_SORTED_BY_INDEX
+         || state==PIECES_SORTED_BY_WEIGHT );
 
-    weightTorrent = t->tor;
 
-    if( mode == PIECES_SORTED_BY_WEIGHT )
+    if( state == PIECES_SORTED_BY_WEIGHT )
+    {
+        setComparePieceByWeightTorrent( t );
         qsort( t->pieces, t->pieceCount, sizeof( struct weighted_piece ), comparePieceByWeight );
+    }
     else
         qsort( t->pieces, t->pieceCount, sizeof( struct weighted_piece ), comparePieceByIndex );
-}
 
-static int
-countActiveWebseeds( const Torrent * t )
-{
-    int activeCount = 0;
-    const tr_webseed ** w = (const tr_webseed **) tr_ptrArrayBase( &t->webseeds );
-    const tr_webseed ** const wend = w + tr_ptrArraySize( &t->webseeds );
-
-    for( ; w!=wend; ++w )
-        if( tr_webseedIsActive( *w ) )
-            ++activeCount;
-
-    return activeCount;
-}
-
-static void
-updateEndgame( Torrent * t )
-{
-    const tr_torrent * tor = t->tor;
-    const tr_block_index_t missing = tr_cpBlocksMissing( &tor->completion );
-
-    assert( t->requestCount >= 0 );
-
-    if( (tr_block_index_t) t->requestCount < missing )
-    {
-        /* not in endgame */
-        t->endgame = 0;
-    }
-    else if( !t->endgame ) /* only recalculate when endgame first begins */
-    {
-        int numDownloading = 0;
-        const tr_peer ** p = (const tr_peer **) tr_ptrArrayBase( &t->peers );
-        const tr_peer ** const pend = p + tr_ptrArraySize( &t->peers );
-
-        /* add the active bittorrent peers... */
-        for( ; p!=pend; ++p )
-            if( (*p)->pendingReqsToPeer > 0 )
-                ++numDownloading;
-
-        /* add the active webseeds... */
-        numDownloading += countActiveWebseeds( t );
-
-        /* average number of pending requests per downloading peer */
-        t->endgame = t->requestCount / MAX( numDownloading, 1 );
-    }
+    t->pieceSortState = state;
 }
 
 /**
- * This function is useful for sanity checking,
- * but is too expensive even for nightly builds...
+ * These functions are useful for testing, but too expensive for nightly builds.
  * let's leave it disabled but add an easy hook to compile it back in
  */
 #if 0
+#define assertWeightedPiecesAreSorted(t)
+#define assertReplicationCountIsExact(t)
+#else
 static void
 assertWeightedPiecesAreSorted( Torrent * t )
 {
     if( !t->endgame )
     {
         int i;
-        weightTorrent = t->tor;
+        setComparePieceByWeightTorrent( t );
         for( i=0; i<t->pieceCount-1; ++i )
             assert( comparePieceByWeight( &t->pieces[i], &t->pieces[i+1] ) <= 0 );
     }
 }
-#else
-#define assertWeightedPiecesAreSorted(t)
+static void
+assertReplicationCountIsExact( Torrent * t )
+{
+    /* This assert might fail due to errors of implementations in other
+     * clients. It happens when receiving duplicate bitfields/HaveAll/HaveNone
+     * from a client. If a such a behavior is noticed,
+     * a bug report should be filled to the faulty client. */
+
+    size_t piece_i;
+    const uint16_t * rep = t->pieceReplication;
+    const size_t piece_count = t->pieceReplicationSize;
+    const tr_peer ** peers = (const tr_peer**) tr_ptrArrayBase( &t->peers );
+    const int peer_count = tr_ptrArraySize( &t->peers );
+
+    assert( piece_count == t->tor->info.pieceCount );
+
+    for( piece_i=0; piece_i<piece_count; ++piece_i )
+    {
+        int peer_i;
+        uint16_t r = 0;
+
+        for( peer_i=0; peer_i<peer_count; ++peer_i )
+            if( tr_bitsetHasFast( &peers[peer_i]->have, piece_i ) )
+                ++r;
+
+        assert( rep[piece_i] == r );
+    }
+}
 #endif
 
 static struct weighted_piece *
@@ -937,7 +1025,6 @@ pieceListLookup( Torrent * t, tr_piece_index_t index )
 static void
 pieceListRebuild( Torrent * t )
 {
-    assertWeightedPiecesAreSorted( t );
 
     if( !tr_torrentIsSeed( t->tor ) )
     {
@@ -1002,8 +1089,6 @@ pieceListRemovePiece( Torrent * t, tr_piece_index_t piece )
 {
     struct weighted_piece * p;
 
-    assertWeightedPiecesAreSorted( t );
-
     if(( p = pieceListLookup( t, piece )))
     {
         const int pos = p - t->pieces;
@@ -1019,8 +1104,6 @@ pieceListRemovePiece( Torrent * t, tr_piece_index_t piece )
             t->pieces = NULL;
         }
     }
-
-    assertWeightedPiecesAreSorted( t );
 }
 
 static void
@@ -1034,12 +1117,18 @@ pieceListResortPiece( Torrent * t, struct weighted_piece * p )
 
     /* is the torrent already sorted? */
     pos = p - t->pieces;
-    weightTorrent = t->tor;
+    setComparePieceByWeightTorrent( t );
     if( isSorted && ( pos > 0 ) && ( comparePieceByWeight( p-1, p ) > 0 ) )
         isSorted = FALSE;
     if( isSorted && ( pos < t->pieceCount - 1 ) && ( comparePieceByWeight( p, p+1 ) > 0 ) )
         isSorted = FALSE;
 
+    if( t->pieceSortState != PIECES_SORTED_BY_WEIGHT )
+    {
+       pieceListSort( t, PIECES_SORTED_BY_WEIGHT);
+       isSorted = TRUE;
+    }
+
     /* if it's not sorted, move it around */
     if( !isSorted )
     {
@@ -1071,15 +1160,106 @@ pieceListRemoveRequest( Torrent * t, tr_block_index_t block )
     struct weighted_piece * p;
     const tr_piece_index_t index = tr_torBlockPiece( t->tor, block );
 
-    assertWeightedPiecesAreSorted( t );
-
     if( ((p = pieceListLookup( t, index ))) && ( p->requestCount > 0 ) )
     {
         --p->requestCount;
         pieceListResortPiece( t, p );
     }
+}
 
-    assertWeightedPiecesAreSorted( t );
+
+/****
+*****
+*****  Replication count ( for rarest first policy )
+*****
+****/
+
+/**
+ * Increase the replication count of this piece and sort it if the
+ * piece list is already sorted
+ */
+static void
+tr_incrReplicationOfPiece( Torrent * t, const size_t index )
+{
+    assert( replicationExists( t ) );
+    assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+    /* One more replication of this piece is present in the swarm */
+    ++t->pieceReplication[index];
+
+    /* we only resort the piece if the list is already sorted */
+    if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+        pieceListResortPiece( t, pieceListLookup( t, index ) );
+}
+
+/**
+ * Increases the replication count of pieces present in the bitfield
+ */
+static void
+tr_incrReplicationFromBitfield( Torrent * t, const tr_bitfield * b )
+{
+    size_t i;
+    uint16_t * rep = t->pieceReplication;
+    const size_t n = t->tor->info.pieceCount;
+
+    assert( replicationExists( t ) );
+    assert( n == t->pieceReplicationSize );
+    assert( tr_bitfieldTestFast( b, n-1 ) );
+
+    if( tr_bitfieldTestFast( b, n-1 ) )
+        for( i=0; i<n; ++i )
+            if( tr_bitfieldHasFast( b, i ) )
+                ++rep[i];
+
+    if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+        invalidatePieceSorting( t );
+}
+
+/**
+ * Increase the replication count of every piece
+ */
+static void
+tr_incrReplication( Torrent * t )
+{
+    int i;
+    const int n = t->pieceReplicationSize;
+
+    assert( replicationExists( t ) );
+    assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+    for( i=0; i<n; ++i )
+        ++t->pieceReplication[i];
+}
+
+/**
+ * Decrease the replication count of pieces present in the bitset.
+ */
+static void
+tr_decrReplicationFromBitset( Torrent * t, const tr_bitset * bitset )
+{
+    int i;
+    const int n = t->pieceReplicationSize;
+
+    assert( replicationExists( t ) );
+    assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+    if( bitset->haveAll )
+    {
+        for( i=0; i<n; ++i )
+            --t->pieceReplication[i];
+    }
+    else if ( !bitset->haveNone )
+    {
+        const tr_bitfield * const b = &bitset->bitfield;
+
+        if( tr_bitfieldTestFast( b, n-1 ) )
+            for( i=0; i<n; ++i )
+                if( tr_bitfieldHasFast( b, i ) )
+                    --t->pieceReplication[i];
+
+        if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+            invalidatePieceSorting( t );
+    }
 }
 
 /**
@@ -1116,12 +1296,17 @@ tr_peerMgrGetNextRequests( tr_torrent           * tor,
     /* walk through the pieces and find blocks that should be requested */
     got = 0;
     t = tor->torrentPeers;
-    assertWeightedPiecesAreSorted( t );
 
     /* prep the pieces list */
     if( t->pieces == NULL )
         pieceListRebuild( t );
 
+    if( t->pieceSortState != PIECES_SORTED_BY_WEIGHT )
+        pieceListSort( t, PIECES_SORTED_BY_WEIGHT );
+
+    assertReplicationCountIsExact( t );
+    assertWeightedPiecesAreSorted( t );
+
     updateEndgame( t );
     pieces = t->pieces;
     for( i=0; i<t->pieceCount && got<numwant; ++i )
@@ -1189,7 +1374,7 @@ tr_peerMgrGetNextRequests( tr_torrent           * tor,
         /* not enough requests || last piece modified */
         if ( i == t->pieceCount ) --i;
 
-        weightTorrent = t->tor;
+        setComparePieceByWeightTorrent( t );
         while( --i >= 0 )
         {
             tr_bool exact;
@@ -1419,6 +1604,32 @@ peerCallbackFunc( tr_peer * peer, const tr_peer_event * e, void * vt )
             break;
         }
 
+        case TR_PEER_CLIENT_GOT_HAVE:
+            if( replicationExists( t ) ) {
+                tr_incrReplicationOfPiece( t, e->pieceIndex );
+                assertReplicationCountIsExact( t );
+            }
+            break;
+
+        case TR_PEER_CLIENT_GOT_HAVE_ALL:
+            if( replicationExists( t ) ) {
+                tr_incrReplication( t );
+                assertReplicationCountIsExact( t );
+            }
+            break;
+
+        case TR_PEER_CLIENT_GOT_HAVE_NONE:
+            /* noop */
+            break;
+
+        case TR_PEER_CLIENT_GOT_BITFIELD:
+            assert( e->bitfield != NULL );
+            if( replicationExists( t ) ) {
+                tr_incrReplicationFromBitfield( t, e->bitfield );
+                assertReplicationCountIsExact( t );
+            }
+            break;
+
         case TR_PEER_CLIENT_GOT_REJ:
             removeRequestFromTables( t, _tr_block( t->tor, e->pieceIndex, e->offset ), peer );
             break;
@@ -2141,6 +2352,7 @@ tr_peerMgrStartTorrent( tr_torrent * tor )
 
     t->isRunning = TRUE;
     t->maxPeers = t->tor->maxConnectedPeers;
+    t->pieceSortState = PIECES_UNSORTED;
 
     rechokePulse( 0, 0, t->manager );
 }
@@ -2152,6 +2364,9 @@ stopTorrent( Torrent * t )
 
     t->isRunning = FALSE;
 
+    replicationFree( t );
+    invalidatePieceSorting( t );
+
     /* disconnect the peers. */
     for( i=0, n=tr_ptrArraySize( &t->peers ); i<n; ++i )
         peerDestructor( t, tr_ptrArrayNth( &t->peers, i ) );
@@ -2971,6 +3186,26 @@ getReconnectIntervalSecs( const struct peer_atom * atom, const time_t now )
     return sec;
 }
 
+static void
+removePeer( Torrent * t, tr_peer * peer )
+{
+    tr_peer * removed;
+    struct peer_atom * atom = peer->atom;
+
+    assert( torrentIsLocked( t ) );
+    assert( atom );
+
+    atom->time = tr_time( );
+
+    removed = tr_ptrArrayRemoveSorted( &t->peers, peer, peerCompare );
+
+    if( replicationExists( t ) )
+        tr_decrReplicationFromBitset( t, &peer->have );
+
+    assert( removed == peer );
+    peerDestructor( t, removed );
+}
+
 static void
 closePeer( Torrent * t, tr_peer * peer )
 {
@@ -2996,6 +3231,13 @@ closePeer( Torrent * t, tr_peer * peer )
     removePeer( t, peer );
 }
 
+static void
+removeAllPeers( Torrent * t )
+{
+    while( !tr_ptrArrayEmpty( &t->peers ) )
+        removePeer( t, tr_ptrArrayNth( &t->peers, 0 ) );
+}
+
 static void
 closeBadPeers( Torrent * t, const uint64_t now_msec, const time_t now_sec )
 {
index 70d4c5a0424d5d93ae265663b78122ec3bc067e5..d1e741ced8939bd0721348352ab15b84cf019938 100644 (file)
@@ -171,6 +171,7 @@ struct tr_incoming
  */
 struct tr_peermsgs
 {
+    tr_bool         got_a_bitfield_or_have_all_or_have_none;
     tr_bool         peerSupportsPex;
     tr_bool         peerSupportsMetadataXfer;
     tr_bool         clientSentLtepHandshake;
@@ -451,8 +452,6 @@ protocolSendHaveNone( tr_peermsgs * msgs )
 ***  EVENTS
 **/
 
-static const tr_peer_event blankEvent = { 0, 0, 0, 0, 0.0f, 0, 0, 0 };
-
 static void
 publish( tr_peermsgs * msgs, tr_peer_event * e )
 {
@@ -466,7 +465,7 @@ publish( tr_peermsgs * msgs, tr_peer_event * e )
 static void
 fireError( tr_peermsgs * msgs, int err )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_ERROR;
     e.err = err;
     publish( msgs, &e );
@@ -475,7 +474,7 @@ fireError( tr_peermsgs * msgs, int err )
 static void
 firePeerProgress( tr_peermsgs * msgs )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_PEER_PROGRESS;
     e.progress = msgs->peer->progress;
     publish( msgs, &e );
@@ -484,7 +483,7 @@ firePeerProgress( tr_peermsgs * msgs )
 static void
 fireGotBlock( tr_peermsgs * msgs, const struct peer_request * req )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_BLOCK;
     e.pieceIndex = req->index;
     e.offset = req->offset;
@@ -495,7 +494,7 @@ fireGotBlock( tr_peermsgs * msgs, const struct peer_request * req )
 static void
 fireGotRej( tr_peermsgs * msgs, const struct peer_request * req )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_REJ;
     e.pieceIndex = req->index;
     e.offset = req->offset;
@@ -506,17 +505,33 @@ fireGotRej( tr_peermsgs * msgs, const struct peer_request * req )
 static void
 fireGotChoke( tr_peermsgs * msgs )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_CHOKE;
     publish( msgs, &e );
 }
 
+static void
+fireClientGotHaveAll( tr_peermsgs * msgs )
+{
+    tr_peer_event e = TR_PEER_EVENT_INIT;
+    e.eventType = TR_PEER_CLIENT_GOT_HAVE_ALL;
+    publish( msgs, &e );
+}
+
+static void
+fireClientGotHaveNone( tr_peermsgs * msgs )
+{
+    tr_peer_event e = TR_PEER_EVENT_INIT;
+    e.eventType = TR_PEER_CLIENT_GOT_HAVE_NONE;
+    publish( msgs, &e );
+}
+
 static void
 fireClientGotData( tr_peermsgs * msgs,
                    uint32_t      length,
                    int           wasPieceData )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
 
     e.length = length;
     e.eventType = TR_PEER_CLIENT_GOT_DATA;
@@ -527,7 +542,7 @@ fireClientGotData( tr_peermsgs * msgs,
 static void
 fireClientGotSuggest( tr_peermsgs * msgs, uint32_t pieceIndex )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_SUGGEST;
     e.pieceIndex = pieceIndex;
     publish( msgs, &e );
@@ -536,7 +551,7 @@ fireClientGotSuggest( tr_peermsgs * msgs, uint32_t pieceIndex )
 static void
 fireClientGotPort( tr_peermsgs * msgs, tr_port port )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_PORT;
     e.port = port;
     publish( msgs, &e );
@@ -545,18 +560,36 @@ fireClientGotPort( tr_peermsgs * msgs, tr_port port )
 static void
 fireClientGotAllowedFast( tr_peermsgs * msgs, uint32_t pieceIndex )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_ALLOWED_FAST;
     e.pieceIndex = pieceIndex;
     publish( msgs, &e );
 }
 
+static void
+fireClientGotBitfield( tr_peermsgs * msgs, tr_bitfield * bitfield )
+{
+    tr_peer_event e = TR_PEER_EVENT_INIT;
+    e.eventType = TR_PEER_CLIENT_GOT_BITFIELD;
+    e.bitfield = bitfield;
+    publish( msgs, &e );
+}
+
+static void
+fireClientGotHave( tr_peermsgs * msgs, tr_piece_index_t index )
+{
+    tr_peer_event e = TR_PEER_EVENT_INIT;
+    e.eventType = TR_PEER_CLIENT_GOT_HAVE;
+    e.pieceIndex = index;
+    publish( msgs, &e );
+}
+
 static void
 firePeerGotData( tr_peermsgs  * msgs,
                  uint32_t       length,
                  int            wasPieceData )
 {
-    tr_peer_event e = blankEvent;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
 
     e.length = length;
     e.eventType = TR_PEER_PEER_GOT_DATA;
@@ -1410,11 +1443,11 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen )
                 fireError( msgs, ERANGE );
                 return READ_ERR;
             }
-            if( tr_bitsetAdd( &msgs->peer->have, ui32 ) )
-            {
-                fireError( msgs, ERANGE );
-                return READ_ERR;
-            }
+
+            /* a peer can send the same HAVE message twice... */
+            if( !tr_bitsetHas( &msgs->peer->have, ui32 ) )
+                if( !tr_bitsetAdd( &msgs->peer->have, ui32 ) )
+                    fireClientGotHave( msgs, ui32 );
             updatePeerProgress( msgs );
             break;
 
@@ -1422,10 +1455,13 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen )
             const size_t bitCount = tr_torrentHasMetadata( msgs->torrent )
                                   ? msgs->torrent->info.pieceCount
                                   : msglen * 8;
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
             dbgmsg( msgs, "got a bitfield" );
             tr_bitsetReserve( &msgs->peer->have, bitCount );
             tr_peerIoReadBytes( msgs->peer->io, inbuf,
                                 msgs->peer->have.bitfield.bits, msglen );
+            fireClientGotBitfield( msgs, &msgs->peer->have.bitfield );
             updatePeerProgress( msgs );
             break;
         }
@@ -1501,7 +1537,10 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen )
         case BT_FEXT_HAVE_ALL:
             dbgmsg( msgs, "Got a BT_FEXT_HAVE_ALL" );
             if( fext ) {
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
                 tr_bitsetSetHaveAll( &msgs->peer->have );
+                fireClientGotHaveAll( msgs );
                 updatePeerProgress( msgs );
             } else {
                 fireError( msgs, EMSGSIZE );
@@ -1512,7 +1551,10 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen )
         case BT_FEXT_HAVE_NONE:
             dbgmsg( msgs, "Got a BT_FEXT_HAVE_NONE" );
             if( fext ) {
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
                 tr_bitsetSetHaveNone( &msgs->peer->have );
+                fireClientGotHaveNone( msgs );
                 updatePeerProgress( msgs );
             } else {
                 fireError( msgs, EMSGSIZE );
index 8a3c5164d29c8e07b624a90e27f6ecfc87149ace..2396aebbe70dd9eba98c156afffd04a2ee33dc86 100644 (file)
@@ -77,8 +77,6 @@ webseed_free( struct tr_webseed * w )
 ****
 ***/
 
-static const tr_peer_event blank_event = { 0, 0, 0, 0, 0.0f, 0, 0, 0 };
-
 static void
 publish( tr_webseed * w, tr_peer_event * e )
 {
@@ -89,7 +87,7 @@ publish( tr_webseed * w, tr_peer_event * e )
 static void
 fire_client_got_rej( tr_torrent * tor, tr_webseed * w, tr_block_index_t block )
 {
-    tr_peer_event e = blank_event;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_REJ;
     e.pieceIndex = tr_torBlockPiece( tor, block );
     e.offset = tor->blockSize * block - tor->info.pieceSize * e.pieceIndex;
@@ -100,7 +98,7 @@ fire_client_got_rej( tr_torrent * tor, tr_webseed * w, tr_block_index_t block )
 static void
 fire_client_got_block( tr_torrent * tor, tr_webseed * w, tr_block_index_t block )
 {
-    tr_peer_event e = blank_event;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_BLOCK;
     e.pieceIndex = tr_torBlockPiece( tor, block );
     e.offset = tor->blockSize * block - tor->info.pieceSize * e.pieceIndex;
@@ -111,7 +109,7 @@ fire_client_got_block( tr_torrent * tor, tr_webseed * w, tr_block_index_t block
 static void
 fire_client_got_data( tr_webseed * w, uint32_t length )
 {
-    tr_peer_event e = blank_event;
+    tr_peer_event e = TR_PEER_EVENT_INIT;
     e.eventType = TR_PEER_CLIENT_GOT_DATA;
     e.length = length;
     e.wasPieceData = TRUE;