From f69e33529142748ec3df80442d57135ff5bf7d27 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 24 Nov 2009 02:16:31 +0000 Subject: [PATCH] (trunk) #2096: Magnet, BEP #9 support --- gtk/add-dialog.c | 8 +- gtk/icons.c | 4 +- gtk/icons.h | 1 + gtk/torrent-cell-renderer.c | 4 +- gtk/tr-core.c | 30 ++- gtk/util.c | 2 +- libtransmission/Makefile.am | 2 + libtransmission/completion.c | 5 + libtransmission/metainfo.c | 73 ++++++- libtransmission/metainfo.h | 10 +- libtransmission/peer-mgr.c | 13 +- libtransmission/peer-mgr.h | 3 +- libtransmission/peer-msgs.c | 331 +++++++++++++++++++++++++++---- libtransmission/rpcimpl.c | 16 +- libtransmission/torrent-ctor.c | 30 +++ libtransmission/torrent-magnet.c | 284 ++++++++++++++++++++++++++ libtransmission/torrent-magnet.h | 38 ++++ libtransmission/torrent.c | 181 ++++++++++------- libtransmission/torrent.h | 25 ++- libtransmission/transmission.h | 3 + libtransmission/utils-test.c | 24 +++ libtransmission/utils.c | 15 ++ libtransmission/utils.h | 5 +- 23 files changed, 967 insertions(+), 140 deletions(-) create mode 100644 libtransmission/torrent-magnet.c create mode 100644 libtransmission/torrent-magnet.h diff --git a/gtk/add-dialog.c b/gtk/add-dialog.c index 0bb64554e..3f30f66f8 100644 --- a/gtk/add-dialog.c +++ b/gtk/add-dialog.c @@ -492,15 +492,15 @@ onAddURLResponse( GtkDialog * dialog, int response, gpointer user_data ) } else { - GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( dialog ), - GTK_DIALOG_MODAL, + GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( dialog ), 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", _( "Unrecognized URL" ) ); gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), _( "Transmission doesn't know how to use \"%s\"" ), url ); - gtk_dialog_run( GTK_DIALOG( w ) ); - gtk_widget_destroy( w ); + g_signal_connect_swapped( w, "response", + G_CALLBACK( gtk_widget_destroy ), w ); + gtk_widget_show( w ); destroy = FALSE; } } diff --git a/gtk/icons.c b/gtk/icons.c index b329eda55..e9edad47f 100644 --- a/gtk/icons.c +++ b/gtk/icons.c @@ -230,7 +230,9 @@ icon_cache_get_mime_type_icon( IconCache * icon_cache, GdkPixbuf * pixbuf; const char * stock_name; - if( !strcmp( mime_type, DIRECTORY_MIME_TYPE ) ) + if( !strcmp( mime_type, UNKNOWN_MIME_TYPE ) ) + stock_name = GTK_STOCK_MISSING_IMAGE; + else if( !strcmp( mime_type, DIRECTORY_MIME_TYPE ) ) stock_name = GTK_STOCK_DIRECTORY; else stock_name = GTK_STOCK_FILE; diff --git a/gtk/icons.h b/gtk/icons.h index dd3ee507b..6ecc0965a 100644 --- a/gtk/icons.h +++ b/gtk/icons.h @@ -7,6 +7,7 @@ #define ICONS_H #define DIRECTORY_MIME_TYPE "folder" +#define UNKNOWN_MIME_TYPE "unknown" const char * get_mime_type_from_filename( const char *file ); diff --git a/gtk/torrent-cell-renderer.c b/gtk/torrent-cell-renderer.c index 00ba332b5..77a28ec89 100644 --- a/gtk/torrent-cell-renderer.c +++ b/gtk/torrent-cell-renderer.c @@ -306,7 +306,9 @@ get_icon( const tr_torrent * tor, GtkIconSize icon_size, GtkWidget * for_widget const char * mime_type; const tr_info * info = tr_torrentInfo( tor ); - if( info->fileCount > 1 ) + if( info->fileCount == 0 ) + mime_type = UNKNOWN_MIME_TYPE; + else if( info->fileCount > 1 ) mime_type = DIRECTORY_MIME_TYPE; else mime_type = get_mime_type_from_filename( info->files[0].name ); diff --git a/gtk/tr-core.c b/gtk/tr-core.c index 918b7c658..67223eb62 100644 --- a/gtk/tr-core.c +++ b/gtk/tr-core.c @@ -1023,15 +1023,40 @@ onURLDone( tr_session * session, void tr_core_add_from_url( TrCore * core, const char * url ) { + tr_session * session = tr_core_session( core ); + if( gtr_is_magnet_link( url ) ) { - g_message( "FIXME: magnet link \"%s\" not handled", url ); + tr_ctor * ctor = tr_ctorNew( session ); + int err = tr_ctorSetMagnet( ctor, url ); + if( !err ) + { + tr_session * session = tr_core_session( core ); + TrTorrent * gtor = tr_torrent_new_ctor( session, ctor, &err ); + if( !err ) + tr_core_add_torrent( core, gtor, FALSE ); + else + g_message( "tr_torrent_new_ctor err %d", err ); + } + else + { + GtkWidget * w = gtk_message_dialog_new( NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", _( "Unrecognized URL" ) ); + gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), + _( "Transmission doesn't know how to use \"%s\"" ), url ); + g_signal_connect_swapped( w, "response", + G_CALLBACK( gtk_widget_destroy ), w ); + gtk_widget_show( w ); + tr_ctorFree( ctor ); + } } else { struct url_dialog_data * data = g_new( struct url_dialog_data, 1 ); data->core = core; - tr_webRun( tr_core_session( core ), url, NULL, onURLDone, data ); + tr_webRun( session, url, NULL, onURLDone, data ); } } @@ -1051,6 +1076,7 @@ add_filename( TrCore * core, if( session == NULL ) return; +g_message( "filename [%s]", filename ); if( gtr_is_supported_url( filename ) || gtr_is_magnet_link( filename ) ) { tr_core_add_from_url( core, filename ); diff --git a/gtk/util.c b/gtk/util.c index 3e278db04..bf6bec1a9 100644 --- a/gtk/util.c +++ b/gtk/util.c @@ -346,7 +346,7 @@ checkfilenames( int argc, char **argv ) for( i=0; itor ) ) return TR_LEECH; if( cp->sizeNow == cp->tor->info.totalSize ) return TR_SEED; if( cp->sizeNow == tr_cpSizeWhenDone( cp ) ) return TR_PARTIAL_SEED; return TR_LEECH; @@ -255,6 +257,9 @@ calculateHaveValid( const tr_completion * ccp ) const uint64_t lastPieceSize = tor->lastPieceSize; const tr_piece_index_t lastPiece = tor->info.pieceCount - 1; + if( !tr_torrentHasMetadata( tor ) ) + return 0; + for( i=0; i!=lastPiece; ++i ) if( tr_cpPieceIsComplete( ccp, i ) ) b += pieceSize; diff --git a/libtransmission/metainfo.c b/libtransmission/metainfo.c index 42b746604..ba17054f2 100644 --- a/libtransmission/metainfo.c +++ b/libtransmission/metainfo.c @@ -37,6 +37,7 @@ #include "session.h" #include "bencode.h" #include "crypto.h" /* tr_sha1 */ +#include "magnet.h" #include "metainfo.h" #include "platform.h" #include "utils.h" @@ -227,7 +228,7 @@ parseFiles( tr_info * inf, } static char * -announceToScrape( const char * announce ) +tr_convertAnnounceToScrape( const char * announce ) { char * scrape = NULL; const char * s; @@ -294,7 +295,7 @@ getannounce( tr_info * inf, tr_tracker_info * t = trackers + trackerCount++; t->tier = validTiers; t->announce = tr_strdup( url ); - t->scrape = announceToScrape( url ); + t->scrape = tr_convertAnnounceToScrape( url ); anyAdded = TRUE; } @@ -324,7 +325,7 @@ getannounce( tr_info * inf, trackers = tr_new0( tr_tracker_info, 1 ); trackers[trackerCount].tier = 0; trackers[trackerCount].announce = tr_strdup( url ); - trackers[trackerCount++].scrape = announceToScrape( url ); + trackers[trackerCount++].scrape = tr_convertAnnounceToScrape( url ); /*fprintf( stderr, "single announce: [%s]\n", url );*/ } tr_free( url ); @@ -381,8 +382,10 @@ escape( char * out, const uint8_t * in, size_t in_len ) /* rfc2396 */ static const char* tr_metainfoParseImpl( const tr_session * session, - tr_info * inf, - const tr_benc * meta_in ) + tr_info * inf, + int * infoDictOffset, + int * infoDictLength, + const tr_benc * meta_in ) { int64_t i; size_t raw_len; @@ -404,6 +407,22 @@ tr_metainfoParseImpl( const tr_session * session, tr_sha1( inf->hash, bstr, len, NULL ); tr_sha1_to_hex( inf->hashString, inf->hash ); escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH ); + + if( infoDictLength != NULL ) + *infoDictLength = len; + + if( infoDictOffset != NULL ) + { + int mlen = 0; + char * mstr = tr_bencToStr( meta_in, TR_FMT_BENC, &mlen ); + const char * offset = tr_memmem( mstr, mlen, bstr, len ); + if( offset != NULL ) + *infoDictOffset = offset - mstr; + tr_free( mstr ); + if( offset == NULL ) + return "info"; + } + tr_free( bstr ); } @@ -483,9 +502,15 @@ tr_metainfoParseImpl( const tr_session * session, tr_bool tr_metainfoParse( const tr_session * session, tr_info * inf, + int * infoDictOffset, + int * infoDictLength, const tr_benc * meta_in ) { - const char * badTag = tr_metainfoParseImpl( session, inf, meta_in ); + const char * badTag = tr_metainfoParseImpl( session, + inf, + infoDictOffset, + infoDictLength, + meta_in ); const tr_bool success = badTag == NULL; if( badTag ) @@ -500,8 +525,8 @@ tr_metainfoParse( const tr_session * session, void tr_metainfoFree( tr_info * inf ) { + int i; tr_file_index_t ff; - int i; for( i = 0; i < inf->webseedCount; ++i ) tr_free( inf->webseeds[i] ); @@ -542,3 +567,37 @@ tr_metainfoRemoveSaved( const tr_session * session, tr_free( filename ); } +/*** +**** +***/ + +void +tr_metainfoSetFromMagnet( tr_info * inf, const tr_magnet_info * m ) +{ + /* hash */ + memcpy( inf->hash, m->hash, 20 ); + tr_sha1_to_hex( inf->hashString, inf->hash ); + escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH ); + + /* name */ + if( *m->displayName ) + inf->name = tr_strdup( m->displayName ); + else + inf->name = tr_strdup( inf->hashString ); + + /* trackers */ + if( m->announceCount > 0 ) + { + int i; + const int n = m->announceCount; + + inf->trackerCount = n; + inf->trackers = tr_new0( tr_tracker_info, n ); + for( i=0; iannounceURLs[i]; + inf->trackers[i].tier = i; + inf->trackers[i].announce = tr_strdup( url ); + inf->trackers[i].scrape = tr_convertAnnounceToScrape( url ); + } + } +} diff --git a/libtransmission/metainfo.h b/libtransmission/metainfo.h index 37fa5531a..07f92ca97 100644 --- a/libtransmission/metainfo.h +++ b/libtransmission/metainfo.h @@ -23,7 +23,7 @@ *****************************************************************************/ #ifndef __TRANSMISSION__ -#error only libtransmission should #include this header. + #error only libtransmission should #include this header. #endif #ifndef TR_METAINFO_H @@ -32,9 +32,12 @@ #include "transmission.h" struct tr_benc; +struct tr_magnet_info; tr_bool tr_metainfoParse( const tr_session * session, - tr_info * info, + tr_info * setmeInfo, + int * setmeInfoDictLength, + int * setmeInfoDictOffset, const struct tr_benc * benc ); void tr_metainfoRemoveSaved( const tr_session * session, @@ -43,4 +46,7 @@ void tr_metainfoRemoveSaved( const tr_session * session, void tr_metainfoMigrate( tr_session * session, tr_info * inf ); +void tr_metainfoSetFromMagnet( tr_info * inf, const struct tr_magnet_info * m ); + + #endif diff --git a/libtransmission/peer-mgr.c b/libtransmission/peer-mgr.c index d32c9f472..c8fa8c394 100644 --- a/libtransmission/peer-mgr.c +++ b/libtransmission/peer-mgr.c @@ -343,6 +343,7 @@ static tr_peer* peerConstructor( struct peer_atom * atom ) { tr_peer * peer = tr_new0( tr_peer, 1 ); + tr_bitsetConstructor( &peer->have, 0 ); peer->atom = atom; return peer; } @@ -383,7 +384,7 @@ peerDestructor( Torrent * t, tr_peer * peer ) tr_peerIoClear( peer->io ); tr_peerIoUnref( peer->io ); /* balanced by the ref in handshakeDoneCB() */ - tr_bitfieldFree( peer->have ); + tr_bitsetDestructor( &peer->have ); tr_bitfieldFree( peer->blame ); tr_free( peer->client ); @@ -906,7 +907,7 @@ tr_peerMgrGetNextRequests( tr_torrent * tor, int got; Torrent * t; struct weighted_piece * pieces; - const tr_bitfield * have = peer->have; + const tr_bitset * have = &peer->have; const time_t now = time( NULL ); /* sanity clause */ @@ -928,7 +929,7 @@ tr_peerMgrGetNextRequests( tr_torrent * tor, struct weighted_piece * p = pieces + i; /* if the peer has this piece that we want... */ - if( tr_bitfieldHasFast( have, p->index ) ) + if( tr_bitsetHasFast( have, p->index ) ) { tr_block_index_t b = tr_torPieceFirstBlock( tor, p->index ); const tr_block_index_t e = b + tr_torPieceCountBlocks( tor, p->index ); @@ -1941,7 +1942,7 @@ tr_peerMgrTorrentAvailability( const tr_torrent * tor, else if( peerCount ) { int j; for( j = 0; j < peerCount; ++j ) - if( tr_bitfieldHas( peers[j]->have, i ) ) + if( tr_bitsetHas( &peers[j]->have, i ) ) ++tab[i]; } } @@ -1964,7 +1965,7 @@ tr_peerMgrGetAvailable( const tr_torrent * tor ) peerCount = tr_ptrArraySize( &t->peers ); peers = (const tr_peer**) tr_ptrArrayBase( &t->peers ); for( i=0; ihave ); + tr_bitsetOr( pieces, &peers[i]->have ); managerUnlock( t->manager ); return pieces; @@ -2351,7 +2352,7 @@ shouldPeerBeClosed( const Torrent * t, peerHasEverything = FALSE; else { tr_bitfield * tmp = tr_bitfieldDup( tr_cpPieceBitfield( &tor->completion ) ); - tr_bitfieldDifference( tmp, peer->have ); + tr_bitsetDifference( tmp, &peer->have ); peerHasEverything = tr_bitfieldCountTrueBits( tmp ) == 0; tr_bitfieldFree( tmp ); } diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index f6ba395eb..f8f550da0 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -24,6 +24,7 @@ #endif #include "bitfield.h" +#include "bitset.h" #include "net.h" #include "peer-common.h" /* struct peer_request */ #include "publish.h" /* tr_publisher_tag */ @@ -93,7 +94,7 @@ typedef struct tr_peer struct peer_atom * atom; struct tr_bitfield * blame; - struct tr_bitfield * have; + struct tr_bitset have; /** how complete the peer's copy of the torrent is. [0.0...1.0] */ float progress; diff --git a/libtransmission/peer-msgs.c b/libtransmission/peer-msgs.c index 75dea5c98..24825e8dc 100644 --- a/libtransmission/peer-msgs.c +++ b/libtransmission/peer-msgs.c @@ -35,6 +35,7 @@ #include "session.h" #include "stats.h" #include "torrent.h" +#include "torrent-magnet.h" #include "tr-dht.h" #include "utils.h" #include "version.h" @@ -66,11 +67,12 @@ enum LTEP_HANDSHAKE = 0, - TR_LTEP_PEX = 1, + UT_PEX_ID = 1, + UT_METADATA_ID = 3, MAX_PEX_PEER_COUNT = 50, - MIN_CHOKE_PERIOD_SEC = ( 10 ), + MIN_CHOKE_PERIOD_SEC = 10, /* idle seconds before we send a keepalive */ KEEPALIVE_INTERVAL_SECS = 100, @@ -79,6 +81,8 @@ enum REQQ = 512, + METADATA_REQQ = 64, + MAX_BLOCK_SIZE = ( 1024 * 16 ), /* used in lowering the outMessages queue period */ @@ -91,7 +95,12 @@ enum LAZY_PIECE_COUNT = 26, /* number of pieces we'll allow in our fast set */ - MAX_FAST_SET_SIZE = 3 + MAX_FAST_SET_SIZE = 3, + + /* defined in BEP #9 */ + METADATA_MSG_TYPE_REQUEST = 0, + METADATA_MSG_TYPE_DATA = 1, + METADATA_MSG_TYPE_REJECT = 2 }; enum @@ -165,8 +174,11 @@ struct tr_incoming struct tr_peermsgs { tr_bool peerSupportsPex; + tr_bool peerSupportsMetadataXfer; tr_bool clientSentLtepHandshake; tr_bool peerSentLtepHandshake; + tr_bool requestingMetadataFromPeer; + /*tr_bool haveFastSet;*/ int activeRequestCount; @@ -181,6 +193,7 @@ struct tr_peermsgs uint8_t state; uint8_t ut_pex_id; + uint8_t ut_metadata_id; uint16_t pexCount; uint16_t pexCount6; @@ -199,7 +212,10 @@ struct tr_peermsgs struct peer_request peerAskedFor[REQQ]; int peerAskedForCount; - + + int peerAskedForMetadata[METADATA_REQQ]; + int peerAskedForMetadataCount; + tr_pex * pex; tr_pex * pex6; @@ -219,6 +235,20 @@ struct tr_peermsgs struct event pexTimer; }; +/** +*** +**/ + +#if 0 +static tr_bitfield* +getHave( const struct tr_peermsgs * msgs ) +{ + if( msgs->peer->have == NULL ) + msgs->peer->have = tr_bitfieldNew( msgs->torrent->info.pieceCount ); + return msgs->peer->have; +} +#endif + static TR_INLINE tr_session* getSession( struct tr_peermsgs * msgs ) { @@ -654,9 +684,9 @@ isPieceInteresting( const tr_peermsgs * msgs, { const tr_torrent * torrent = msgs->torrent; - return ( !torrent->info.pieces[piece].dnd ) /* we want it */ + return ( !torrent->info.pieces[piece].dnd ) /* we want it */ && ( !tr_cpPieceIsComplete( &torrent->completion, piece ) ) /* !have */ - && ( tr_bitfieldHas( msgs->peer->have, piece ) ); /* peer has it */ + && ( tr_bitsetHas( &msgs->peer->have, piece ) ); /* peer has it */ } /* "interested" means we'll ask for piece data if they unchoke us */ @@ -677,11 +707,6 @@ isPeerInteresting( const tr_peermsgs * msgs ) torrent = msgs->torrent; bitfield = tr_cpPieceBitfield( &torrent->completion ); - if( !msgs->peer->have ) - return TRUE; - - assert( bitfield->byteCount == msgs->peer->have->byteCount ); - for( i = 0; i < torrent->info.pieceCount; ++i ) if( isPieceInteresting( msgs, i ) ) return TRUE; @@ -716,6 +741,21 @@ updateInterest( tr_peermsgs * msgs ) sendInterest( msgs, i ); } +static tr_bool +popNextMetadataRequest( tr_peermsgs * msgs, int * piece ) +{ + if( msgs->peerAskedForMetadataCount == 0 ) + return FALSE; + + *piece = msgs->peerAskedForMetadata[0]; + + memmove( msgs->peerAskedForMetadata, + msgs->peerAskedForMetadata + 1, + sizeof( int ) * --msgs->peerAskedForMetadataCount ); + + return TRUE; +} + static tr_bool popNextRequest( tr_peermsgs * msgs, struct peer_request * setme ) { @@ -819,7 +859,8 @@ sendLtepHandshake( tr_peermsgs * msgs ) tr_benc val, *m; char * buf; int len; - int pex; + tr_bool allow_pex; + tr_bool allow_metadata_xfer; struct evbuffer * out = msgs->outMessages; const unsigned char * ipv6 = tr_globalIPv6(); @@ -829,25 +870,37 @@ sendLtepHandshake( tr_peermsgs * msgs ) dbgmsg( msgs, "sending an ltep handshake" ); msgs->clientSentLtepHandshake = 1; + /* decide if we want to advertise metadata xfer support (BEP 9) */ + if( tr_torrentIsPrivate( msgs->torrent ) ) + allow_metadata_xfer = 0; + else + allow_metadata_xfer = 1; + /* decide if we want to advertise pex support */ if( !tr_torrentAllowsPex( msgs->torrent ) ) - pex = 0; + allow_pex = 0; else if( msgs->peerSentLtepHandshake ) - pex = msgs->peerSupportsPex ? 1 : 0; + allow_pex = msgs->peerSupportsPex ? 1 : 0; else - pex = 1; + allow_pex = 1; - tr_bencInitDict( &val, 7 ); + tr_bencInitDict( &val, 8 ); tr_bencDictAddInt( &val, "e", getSession(msgs)->encryptionMode != TR_CLEAR_PREFERRED ); - if( ipv6 ) + if( ipv6 != NULL ) tr_bencDictAddRaw( &val, "ipv6", ipv6, 16 ); + if( allow_metadata_xfer && tr_torrentHasMetadata( msgs->torrent ) + && ( msgs->torrent->infoDictLength > 0 ) ) + tr_bencDictAddInt( &val, "metadata_size", msgs->torrent->infoDictLength ); tr_bencDictAddInt( &val, "p", tr_sessionGetPeerPort( getSession(msgs) ) ); tr_bencDictAddInt( &val, "reqq", REQQ ); tr_bencDictAddInt( &val, "upload_only", tr_torrentIsSeed( msgs->torrent ) ); tr_bencDictAddStr( &val, "v", TR_NAME " " USERAGENT_PREFIX ); - m = tr_bencDictAddDict( &val, "m", 1 ); - if( pex ) - tr_bencDictAddInt( m, "ut_pex", TR_LTEP_PEX ); + m = tr_bencDictAddDict( &val, "m", 2 ); + if( allow_metadata_xfer ) + tr_bencDictAddInt( m, "ut_metadata", UT_METADATA_ID ); + if( allow_pex ) + tr_bencDictAddInt( m, "ut_pex", UT_PEX_ID ); + buf = tr_bencToStr( &val, TR_FMT_BENC, &len ); tr_peerIoWriteUint32( msgs->peer->io, out, 2 * sizeof( uint8_t ) + len ); @@ -898,14 +951,25 @@ parseLtepHandshake( tr_peermsgs * msgs, /* check supported messages for utorrent pex */ msgs->peerSupportsPex = 0; + msgs->peerSupportsMetadataXfer = 0; + if( tr_bencDictFindDict( &val, "m", &sub ) ) { if( tr_bencDictFindInt( sub, "ut_pex", &i ) ) { + msgs->peerSupportsPex = i != 0; msgs->ut_pex_id = (uint8_t) i; - msgs->peerSupportsPex = msgs->ut_pex_id == 0 ? 0 : 1; dbgmsg( msgs, "msgs->ut_pex is %d", (int)msgs->ut_pex_id ); } + if( tr_bencDictFindInt( sub, "ut_metadata", &i ) ) { + msgs->peerSupportsMetadataXfer = i != 0; + msgs->ut_metadata_id = (uint8_t) i; + dbgmsg( msgs, "msgs->ut_metadata_id is %d", (int)msgs->ut_metadata_id ); + } } + /* look for metainfo size (BEP 9) */ + if( tr_bencDictFindInt( &val, "metadata_size", &i ) ) + tr_torrentSetMetadataSizeHint( msgs->torrent, i ); + /* look for upload_only (BEP 21) */ if( tr_bencDictFindInt( &val, "upload_only", &i ) ) { fireUploadOnly( msgs, i!=0 ); @@ -925,7 +989,7 @@ parseLtepHandshake( tr_peermsgs * msgs, memcpy( &pex.addr.addr.addr4, addr, 4 ); tr_peerMgrAddPex( msgs->torrent, TR_PEER_FROM_ALT, &pex ); } - + if( tr_bencDictFindRaw( &val, "ipv6", &addr, &addr_len) && addr_len == 16 ) { pex.addr.type = TR_AF_INET6; memcpy( &pex.addr.addr.addr6, addr, 16 ); @@ -940,6 +1004,86 @@ parseLtepHandshake( tr_peermsgs * msgs, tr_free( tmp ); } +static void +parseUtMetadata( tr_peermsgs * msgs, int msglen, struct evbuffer * inbuf ) +{ + tr_benc dict; + char * msg_end; + char * benc_end; + int64_t msg_type = -1; + int64_t piece = -1; + int64_t total_size = 0; + uint8_t * tmp = tr_new( uint8_t, msglen ); + + tr_peerIoReadBytes( msgs->peer->io, inbuf, tmp, msglen ); + msg_end = (char*)tmp + msglen; + + if( !tr_bencLoad( tmp, msglen, &dict, &benc_end ) ) + { + tr_bencDictFindInt( &dict, "msg_type", &msg_type ); + tr_bencDictFindInt( &dict, "piece", &piece ); + tr_bencDictFindInt( &dict, "total_size", &total_size ); + tr_bencFree( &dict ); + } + + dbgmsg( msgs, "got ut_metadata msg: type %d, piece %d, total_size %d", + (int)msg_type, (int)piece, (int)total_size ); + + if( msg_type == METADATA_MSG_TYPE_REJECT ) + { + /* NOOP */ + } + + if( ( msg_type == METADATA_MSG_TYPE_DATA ) + && ( !tr_torrentHasMetadata( msgs->torrent ) ) + && ( msg_end - benc_end <= METADATA_PIECE_SIZE ) + && ( piece * METADATA_PIECE_SIZE + (msg_end - benc_end) <= total_size ) ) + { + const int pieceLen = msg_end - benc_end; +dbgmsg( msgs, "got a metadata piece... calling tr_torrentSetMetadataPiece" ); + msgs->requestingMetadataFromPeer = FALSE; + tr_torrentSetMetadataPiece( msgs->torrent, piece, benc_end, pieceLen ); + } + + if( msg_type == METADATA_MSG_TYPE_REQUEST ) + { + if( ( piece >= 0 ) + && tr_torrentHasMetadata( msgs->torrent ) + && !tr_torrentIsPrivate( msgs->torrent ) + && ( msgs->peerAskedForMetadataCount < METADATA_REQQ ) ) + { + msgs->peerAskedForMetadata[msgs->peerAskedForMetadataCount++] = piece; + } + else + { + tr_benc tmp; + int payloadLen; + char * payload; + tr_peerIo * io = msgs->peer->io; + struct evbuffer * out = msgs->outMessages; + + /* build the rejection message */ + tr_bencInitDict( &tmp, 2 ); + tr_bencDictAddInt( &tmp, "msg_type", METADATA_MSG_TYPE_REJECT ); + tr_bencDictAddInt( &tmp, "piece", piece ); + payload = tr_bencToStr( &tmp, TR_FMT_BENC, &payloadLen ); + tr_bencFree( &tmp ); + + /* write it out as a LTEP message to our outMessages buffer */ + tr_peerIoWriteUint32( io, out, 2 * sizeof( uint8_t ) + payloadLen ); + tr_peerIoWriteUint8 ( io, out, BT_LTEP ); + tr_peerIoWriteUint8 ( io, out, msgs->ut_metadata_id ); + tr_peerIoWriteBytes ( io, out, payload, payloadLen ); + pokeBatchPeriod( msgs, HIGH_PRIORITY_INTERVAL_SECS ); + dbgOutMessageLen( msgs ); + + tr_free( payload ); + } + } + + tr_free( tmp ); +} + static void parseUtPex( tr_peermsgs * msgs, int msglen, struct evbuffer * inbuf ) { @@ -1017,12 +1161,18 @@ parseLtep( tr_peermsgs * msgs, sendPex( msgs ); } } - else if( ltep_msgid == TR_LTEP_PEX ) + else if( ltep_msgid == UT_PEX_ID ) { dbgmsg( msgs, "got ut pex" ); msgs->peerSupportsPex = 1; parseUtPex( msgs, msglen, inbuf ); } + else if( ltep_msgid == UT_METADATA_ID ) + { + dbgmsg( msgs, "got ut metadata" ); + msgs->peerSupportsMetadataXfer = 1; + parseUtMetadata( msgs, msglen, inbuf ); + } else { dbgmsg( msgs, "skipping unknown ltep message (%d)", (int)ltep_msgid ); @@ -1087,7 +1237,7 @@ readBtId( tr_peermsgs * msgs, static void updatePeerProgress( tr_peermsgs * msgs ) { - msgs->peer->progress = tr_bitfieldCountTrueBits( msgs->peer->have ) / (float)msgs->torrent->info.pieceCount; + msgs->peer->progress = tr_bitsetPercent( &msgs->peer->have ); dbgmsg( msgs, "peer progress is %f", msgs->peer->progress ); updateFastSet( msgs ); updateInterest( msgs ); @@ -1286,7 +1436,7 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen ) case BT_HAVE: tr_peerIoReadUint32( msgs->peer->io, inbuf, &ui32 ); dbgmsg( msgs, "got Have: %u", ui32 ); - if( tr_bitfieldAdd( msgs->peer->have, ui32 ) ) { + if( tr_bitsetAdd( &msgs->peer->have, ui32 ) ) { fireError( msgs, ERANGE ); return READ_ERR; } @@ -1295,7 +1445,8 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen ) case BT_BITFIELD: dbgmsg( msgs, "got a bitfield" ); - tr_peerIoReadBytes( msgs->peer->io, inbuf, msgs->peer->have->bits, msglen ); + tr_bitsetReserve( &msgs->peer->have, msglen*8 ); + tr_peerIoReadBytes( msgs->peer->io, inbuf, msgs->peer->have.bitfield.bits, msglen ); updatePeerProgress( msgs ); break; @@ -1318,13 +1469,17 @@ readBtMessage( tr_peermsgs * msgs, struct evbuffer * inbuf, size_t inlen ) tr_peerIoReadUint32( msgs->peer->io, inbuf, &r.offset ); tr_peerIoReadUint32( msgs->peer->io, inbuf, &r.length ); dbgmsg( msgs, "got a Cancel %u:%u->%u", r.index, r.offset, r.length ); + for( i=0; ipeerAskedForCount; ++i ) { const struct peer_request * req = msgs->peerAskedFor + i; if( ( req->index == r.index ) && ( req->offset == r.offset ) && ( req->length == r.length ) ) break; } + if( i < msgs->peerAskedForCount ) - memmove( msgs->peerAskedFor+i, msgs->peerAskedFor+i+1, --msgs->peerAskedForCount-i ); + memmove( msgs->peerAskedFor+i, + msgs->peerAskedFor+i+1, + sizeof(struct peer_request) *( --msgs->peerAskedForCount-i) ); break; } @@ -1366,7 +1521,7 @@ 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 ) { - tr_bitfieldAddRange( msgs->peer->have, 0, msgs->torrent->info.pieceCount ); + tr_bitsetSetHaveAll( &msgs->peer->have ); updatePeerProgress( msgs ); } else { fireError( msgs, EMSGSIZE ); @@ -1377,7 +1532,7 @@ 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 ) { - tr_bitfieldClear( msgs->peer->have ); + tr_bitsetSetHaveNone( &msgs->peer->have ); updatePeerProgress( msgs ); } else { fireError( msgs, EMSGSIZE ); @@ -1586,7 +1741,45 @@ updateDesiredRequestCount( tr_peermsgs * msgs, uint64_t now ) } static void -updateRequests( tr_peermsgs * msgs ) +updateMetadataRequests( tr_peermsgs * msgs, time_t now ) +{ + int piece; + + if( msgs->peerSupportsMetadataXfer + && !msgs->requestingMetadataFromPeer + && tr_torrentGetNextMetadataRequest( msgs->torrent, now, &piece ) ) + { + tr_benc tmp; + int payloadLen; + char * payload; + tr_peerIo * io = msgs->peer->io; + struct evbuffer * out = msgs->outMessages; + + /* build the data message */ + tr_bencInitDict( &tmp, 3 ); + tr_bencDictAddInt( &tmp, "msg_type", METADATA_MSG_TYPE_REQUEST ); + tr_bencDictAddInt( &tmp, "piece", piece ); + payload = tr_bencToStr( &tmp, TR_FMT_BENC, &payloadLen ); + tr_bencFree( &tmp ); + + dbgmsg( msgs, "requesting metadata piece #%d", piece ); + + /* write it out as a LTEP message to our outMessages buffer */ + tr_peerIoWriteUint32( io, out, 2 * sizeof( uint8_t ) + payloadLen ); + tr_peerIoWriteUint8 ( io, out, BT_LTEP ); + tr_peerIoWriteUint8 ( io, out, msgs->ut_metadata_id ); + tr_peerIoWriteBytes ( io, out, payload, payloadLen ); + pokeBatchPeriod( msgs, HIGH_PRIORITY_INTERVAL_SECS ); + dbgOutMessageLen( msgs ); + + msgs->requestingMetadataFromPeer = TRUE; + + tr_free( payload ); + } +} + +static void +updateBlockRequests( tr_peermsgs * msgs ) { const int MIN_BATCH_SIZE = 4; const int numwant = msgs->desiredRequestCount - msgs->activeRequestCount; @@ -1641,6 +1834,7 @@ prefetchPieces( tr_peermsgs *msgs ) static size_t fillOutputBuffer( tr_peermsgs * msgs, time_t now ) { + int piece; size_t bytesWritten = 0; struct peer_request req; const tr_bool haveMessages = EVBUFFER_LENGTH( msgs->outMessages ) != 0; @@ -1668,7 +1862,77 @@ fillOutputBuffer( tr_peermsgs * msgs, time_t now ) } /** - *** Blocks + *** Metadata Pieces + **/ + + if( ( tr_peerIoGetWriteBufferSpace( msgs->peer->io, now ) >= METADATA_PIECE_SIZE ) + && popNextMetadataRequest( msgs, &piece ) ) + { + char * data; + int dataLen; + tr_bool ok = FALSE; + + data = tr_torrentGetMetadataPiece( msgs->torrent, piece, &dataLen ); + if( ( dataLen > 0 ) && ( data != NULL ) ) + { + tr_benc tmp; + int payloadLen; + char * payload; + tr_peerIo * io = msgs->peer->io; + struct evbuffer * out = msgs->outMessages; + + /* build the data message */ + tr_bencInitDict( &tmp, 3 ); + tr_bencDictAddInt( &tmp, "msg_type", METADATA_MSG_TYPE_DATA ); + tr_bencDictAddInt( &tmp, "piece", piece ); + tr_bencDictAddInt( &tmp, "total_size", dataLen ); + payload = tr_bencToStr( &tmp, TR_FMT_BENC, &payloadLen ); + tr_bencFree( &tmp ); + + /* write it out as a LTEP message to our outMessages buffer */ + tr_peerIoWriteUint32( io, out, 2 * sizeof( uint8_t ) + payloadLen + dataLen ); + tr_peerIoWriteUint8 ( io, out, BT_LTEP ); + tr_peerIoWriteUint8 ( io, out, msgs->ut_metadata_id ); + tr_peerIoWriteBytes ( io, out, payload, payloadLen ); + tr_peerIoWriteBytes ( io, out, data, dataLen ); + pokeBatchPeriod( msgs, HIGH_PRIORITY_INTERVAL_SECS ); + dbgOutMessageLen( msgs ); + + tr_free( payload ); + tr_free( data ); + + ok = TRUE; + } + + if( !ok ) /* send a rejection message */ + { + tr_benc tmp; + int payloadLen; + char * payload; + tr_peerIo * io = msgs->peer->io; + struct evbuffer * out = msgs->outMessages; + + /* build the rejection message */ + tr_bencInitDict( &tmp, 2 ); + tr_bencDictAddInt( &tmp, "msg_type", METADATA_MSG_TYPE_REJECT ); + tr_bencDictAddInt( &tmp, "piece", piece ); + payload = tr_bencToStr( &tmp, TR_FMT_BENC, &payloadLen ); + tr_bencFree( &tmp ); + + /* write it out as a LTEP message to our outMessages buffer */ + tr_peerIoWriteUint32( io, out, 2 * sizeof( uint8_t ) + payloadLen ); + tr_peerIoWriteUint8 ( io, out, BT_LTEP ); + tr_peerIoWriteUint8 ( io, out, msgs->ut_metadata_id ); + tr_peerIoWriteBytes ( io, out, payload, payloadLen ); + pokeBatchPeriod( msgs, HIGH_PRIORITY_INTERVAL_SECS ); + dbgOutMessageLen( msgs ); + + tr_free( payload ); + } + } + + /** + *** Data Blocks **/ if( ( tr_peerIoGetWriteBufferSpace( msgs->peer->io, now ) >= msgs->torrent->blockSize ) @@ -1749,7 +2013,8 @@ peerPulse( void * vmsgs ) if ( tr_isPeerIo( msgs->peer->io ) ) { updateDesiredRequestCount( msgs, now ); - updateRequests( msgs ); + updateBlockRequests( msgs ); + updateMetadataRequests( msgs, now ); } for( ;; ) @@ -2111,7 +2376,6 @@ tr_peerMsgsNew( struct tr_torrent * torrent, m->peer->peerIsChoked = 1; m->peer->clientIsInterested = 0; m->peer->peerIsInterested = 0; - m->peer->have = tr_bitfieldNew( torrent->info.pieceCount ); m->state = AWAITING_BT_LENGTH; m->outMessages = evbuffer_new( ); m->outMessagesBatchedAt = 0; @@ -2167,4 +2431,3 @@ tr_peerMsgsUnsubscribe( tr_peermsgs * peer, { tr_publisherUnsubscribe( &peer->publisher, tag ); } - diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index e4eca0394..7e111d86d 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -1093,14 +1093,22 @@ torrentAdd( tr_session * session, } else { - if( filename != NULL ) - tr_ctorSetMetainfoFromFile( ctor, filename ); - else { + if( filename == NULL ) + { int len; - char * metainfo = tr_base64_decode( metainfo_base64, -1, &len ); + char * metainfo = tr_base64_decode( metainfo_base64, -1, &len ); tr_ctorSetMetainfo( ctor, (uint8_t*)metainfo, len ); tr_free( metainfo ); } + else if( !strncmp( filename, "magnet:?", 8 ) ) + { + tr_ctorSetMagnet( ctor, filename ); + } + else + { + tr_ctorSetMetainfoFromFile( ctor, filename ); + } + addTorrentImpl( idle_data, ctor ); } diff --git a/libtransmission/torrent-ctor.c b/libtransmission/torrent-ctor.c index 67dfb59bb..9631f17d1 100644 --- a/libtransmission/torrent-ctor.c +++ b/libtransmission/torrent-ctor.c @@ -13,6 +13,7 @@ #include #include "transmission.h" #include "bencode.h" +#include "magnet.h" #include "platform.h" #include "session.h" /* tr_sessionFindTorrentFile() */ #include "torrent.h" /* tr_ctorGetSave() */ @@ -42,6 +43,8 @@ struct tr_ctor tr_benc metainfo; char * sourceFile; + tr_magnet_info * magnetInfo; + struct optional_args optionalArgs[2]; char * incompleteDir; @@ -101,6 +104,20 @@ tr_ctorGetSourceFile( const tr_ctor * ctor ) return ctor->sourceFile; } +int +tr_ctorSetMagnet( tr_ctor * ctor, const char * uri ) +{ + int err; + + if( ctor->magnetInfo != NULL ) + tr_magnetFree( ctor->magnetInfo ); + + ctor->magnetInfo = tr_magnetParse( uri ); + + err = ctor->magnetInfo == NULL; + return err; +} + int tr_ctorSetMetainfoFromFile( tr_ctor * ctor, const char * filename ) @@ -371,6 +388,19 @@ tr_ctorGetIncompleteDir( const tr_ctor * ctor, return err; } +int +tr_ctorGetMagnet( const tr_ctor * ctor, const tr_magnet_info ** setme ) +{ + int err = 0; + + if( ctor->magnetInfo == NULL ) + err = 1; + else + *setme = ctor->magnetInfo; + + return err; +} + int tr_ctorGetMetainfo( const tr_ctor * ctor, const tr_benc ** setme ) diff --git a/libtransmission/torrent-magnet.c b/libtransmission/torrent-magnet.c new file mode 100644 index 000000000..7a4f4e3b6 --- /dev/null +++ b/libtransmission/torrent-magnet.c @@ -0,0 +1,284 @@ +/* + * This file Copyright (C) 2009 Charles Kerr + * + * This file is licensed by the GPL version 2. Works owned by the + * Transmission project are granted a special exemption to clause 2(b) + * so that the bulk of its code can remain under the MIT license. + * This exemption does not extend to derived works not owned by + * the Transmission project. + * + * $Id:$ + */ + +#include +#include /* struct evbuffer */ + +#include "transmission.h" +#include "bencode.h" +#include "crypto.h" +#include "magnet.h" +#include "metainfo.h" +#include "torrent.h" +#include "torrent-magnet.h" +#include "utils.h" +#include "web.h" + +#define dbgmsg( tor, ... ) \ + do { \ + if( tr_deepLoggingIsActive( ) ) \ + tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \ + } while( 0 ) + +/*** +**** +***/ + +enum +{ + /* don't ask for the same metadata piece more than this often */ + MIN_REPEAT_INTERVAL_SECS = 5 +}; + +struct metadata_node +{ + time_t requestedAt; + int piece; +}; + +struct tr_incomplete_metadata +{ + uint8_t * metadata; + int metadata_size; + int pieceCount; + + /** sorted from least to most recently requested */ + struct metadata_node * piecesNeeded; + int piecesNeededCount; +}; + +static void +incompleteMetadataFree( struct tr_incomplete_metadata * m ) +{ + tr_free( m->metadata ); + tr_free( m->piecesNeeded ); + tr_free( m ); +} + +void +tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size ) +{ + if( !tr_torrentHasMetadata( tor ) ) + { + if( tor->incompleteMetadata == NULL ) + { + int i; + struct tr_incomplete_metadata * m; + int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE; + dbgmsg( tor, "there are %d pieces", n ); + + m = tr_new( struct tr_incomplete_metadata, 1 ); + m->pieceCount = n; + m->metadata = tr_new( uint8_t, size ); + m->metadata_size = size; + m->piecesNeededCount = n; + m->piecesNeeded = tr_new( struct metadata_node, n ); + + for( i=0; ipiecesNeeded[i].piece = i; + m->piecesNeeded[i].requestedAt = 0; + } + + tor->incompleteMetadata = m; + } + } +} + +tr_bool +tr_torrentHasMetadata( const tr_torrent * tor ) +{ + return tor->info.fileCount > 0; +} + +void* +tr_torrentGetMetadataPiece( const tr_torrent * tor, int piece, int * len ) +{ + char * ret = NULL; + + assert( tr_isTorrent( tor ) ); + assert( piece >= 0 ); + assert( len != NULL ); + + if( tor->infoDictLength > 0 ) + { + FILE * fp = fopen( tor->info.torrent, "rb" ); + if( fp != NULL ) + { + const int offset = piece * METADATA_PIECE_SIZE; + + if( !fseek( fp, tor->infoDictOffset + offset, SEEK_SET ) ) + { + const int l = offset + METADATA_PIECE_SIZE <= tor->infoDictLength + ? METADATA_PIECE_SIZE + : tor->infoDictLength - offset; + char * buf = tr_new( char, l ); + const int n = fread( buf, 1, l, fp ); + if( n != l ) + { + *len = l; + ret = buf; + buf = NULL; + } + + tr_free( buf ); + } + + fclose( fp ); + } + } + + return ret; +} + +void +tr_torrentSetMetadataPiece( tr_torrent * tor, + int piece, + const void * data, + int len ) +{ + int i; + struct tr_incomplete_metadata * m; + const int offset = piece * METADATA_PIECE_SIZE; + + assert( tr_isTorrent( tor ) ); + + dbgmsg( tor, "got metadata piece %d", piece ); + + /* are we set up to download metadata? */ + m = tor->incompleteMetadata; + if( m == NULL ) + return; + + /* does this data pass the smell test? */ + if( offset + len > m->metadata_size ) + return; + + /* do we need this piece? */ + for( i=0; ipiecesNeededCount; ++i ) + if( m->piecesNeeded[i].piece == piece ) + break; + if( i==m->piecesNeededCount ) + return; + + memcpy( m->metadata + offset, data, len ); + + tr_removeElementFromArray( m->piecesNeeded, i, + sizeof( struct metadata_node ), + m->piecesNeededCount-- ); + + dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount ); + + /* are we done? */ + if( m->piecesNeededCount == 0 ) + { + tr_bool success = FALSE; + uint8_t sha1[SHA_DIGEST_LENGTH]; + dbgmsg( tor, "metainfo piece %d was the last one", piece ); + tr_sha1( sha1, m->metadata, m->metadata_size, NULL ); + if( !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH ) ) + { + int err; + tr_benc dict; + struct evbuffer * buf = evbuffer_new( ); + dbgmsg( tor, "metadata checksum passed! (length: %d)", m->metadata_size ); + + /* add a wrapper dictionary to the benc. + * include the announce-list too, + * so we can save it in the .torrent for future sessions */ + evbuffer_add_printf( buf, "d" ); + evbuffer_add_printf( buf, "13:announce-list" ); + evbuffer_add_printf( buf, "l" ); + for( i=0; iinfo.trackerCount; ++i ) { + const char * url = tor->info.trackers[i].announce; + evbuffer_add_printf( buf, "l%d:%se", strlen( url ), url ); + } + evbuffer_add_printf( buf, "e" ); + evbuffer_add_printf( buf, "4:info" ); + evbuffer_add( buf, m->metadata, m->metadata_size ); + evbuffer_add_printf( buf, "e" ); + + /* does it parse? */ + err = tr_bencLoad( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ), &dict, NULL ); + dbgmsg( tor, "err is %d", err ); + if( !err ) + { + if( tr_metainfoParse( tor->session, + &tor->info, + &tor->infoDictLength, + &tor->infoDictOffset, + &dict ) ) + { + const char * path = tor->info.torrent; + dbgmsg( tor, "saving completed metadata to \"%s\"", path ); + + success = TRUE; + tr_torrentGotNewInfoDict( tor ); + + tr_bencToFile( &dict, TR_FMT_BENC, path ); + tr_sessionSetTorrentFile( tor->session, + tor->info.hashString, path ); + } + } + + evbuffer_free( buf ); + } + + if( success ) + { + incompleteMetadataFree( tor->incompleteMetadata ); + tor->incompleteMetadata = NULL; + } + else /* drat. */ + { + const int n = m->pieceCount; + for( i=0; ipiecesNeeded[i].piece = i; + m->piecesNeeded[i].requestedAt = 0; + } + m->piecesNeededCount = n; + dbgmsg( tor, "metadata error; trying again. %d pieces left", n ); + } + } +} + +tr_bool +tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece ) +{ + tr_bool have_request = FALSE; + struct tr_incomplete_metadata * m; + + assert( tr_isTorrent( tor ) ); + + m = tor->incompleteMetadata; + + if( ( m != NULL ) + && ( m->piecesNeededCount > 0 ) + && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS <= now ) ) + { + int i; + const int piece = m->piecesNeeded[0].piece; + + tr_removeElementFromArray( m->piecesNeeded, 0, + sizeof( struct metadata_node ), + m->piecesNeededCount-- ); + + i = m->piecesNeededCount++; + m->piecesNeeded[0].piece = piece; + m->piecesNeeded[0].requestedAt = now; + + dbgmsg( tor, "next piece to request: %d", piece ); + *setme_piece = piece; + have_request = TRUE; + } + + return have_request; +} diff --git a/libtransmission/torrent-magnet.h b/libtransmission/torrent-magnet.h new file mode 100644 index 000000000..d339b6b56 --- /dev/null +++ b/libtransmission/torrent-magnet.h @@ -0,0 +1,38 @@ +/* + * This file Copyright (C) 2009 Charles Kerr + * + * This file is licensed by the GPL version 2. Works owned by the + * Transmission project are granted a special exemption to clause 2(b) + * so that the bulk of its code can remain under the MIT license. + * This exemption does not extend to derived works not owned by + * the Transmission project. + * + * $Id:$ + */ + +#ifndef __TRANSMISSION__ + #error only libtransmission should #include this header. +#endif + +#ifndef TR_TORRENT_MAGNET_H +#define TR_TORRENT_MAGNET_H 1 + +#include + +enum +{ + /* defined by BEP #9 */ + METADATA_PIECE_SIZE = ( 1024 * 16 ) +}; + +tr_bool tr_torrentHasMetadata( const tr_torrent * tor ); + +void* tr_torrentGetMetadataPiece( const tr_torrent * tor, int piece, int * len ); + +void tr_torrentSetMetadataPiece( tr_torrent * tor, int piece, const void * data, int len ); + +tr_bool tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme ); + +void tr_torrentSetMetadataSizeHint( tr_torrent * tor, int metadata_size ); + +#endif diff --git a/libtransmission/torrent.c b/libtransmission/torrent.c index 997c0b30f..a9f530869 100644 --- a/libtransmission/torrent.c +++ b/libtransmission/torrent.c @@ -32,6 +32,7 @@ #include "crypto.h" /* for tr_sha1 */ #include "resume.h" #include "fdlimit.h" /* tr_fdTorrentClose */ +#include "magnet.h" #include "metainfo.h" #include "peer-mgr.h" #include "platform.h" /* TR_PATH_DELIMITER_STR */ @@ -43,7 +44,10 @@ #include "utils.h" #include "verify.h" -#define MAX_BLOCK_SIZE ( 1024 * 16 ) +enum +{ + MAX_BLOCK_SIZE = 1024 * 16 +}; /*** **** @@ -550,63 +554,43 @@ getBlockSize( uint32_t pieceSize ) return b; } -static void refreshCurrentDir( tr_torrent * tor );; +static void refreshCurrentDir( tr_torrent * tor ); static void -torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) +torrentInitFromInfo( tr_torrent * tor ) { - int doStart; - uint64_t loaded; - uint64_t t; - const char * dir; - static int nextUniqueId = 1; - tr_info * info = &tor->info; - tr_session * session = tr_ctorGetSession( ctor ); - - assert( session != NULL ); - - tr_globalLock( session ); - - tor->session = session; - tor->uniqueId = nextUniqueId++; - tor->magicNumber = TORRENT_MAGIC_NUMBER; - - randomizeTiers( info ); - - tor->bandwidth = tr_bandwidthNew( session, session->bandwidth ); + uint64_t t; + tr_info * info = &tor->info; tor->blockSize = getBlockSize( info->pieceSize ); - if( !tr_ctorGetDownloadDir( ctor, TR_FORCE, &dir ) || - !tr_ctorGetDownloadDir( ctor, TR_FALLBACK, &dir ) ) - tor->downloadDir = tr_strdup( dir ); - - if( tr_ctorGetIncompleteDir( ctor, &dir ) ) - dir = tr_sessionGetIncompleteDir( session ); - if( tr_sessionIsIncompleteDirEnabled( session ) ) - tor->incompleteDir = tr_strdup( dir ); - - tor->lastPieceSize = info->totalSize % info->pieceSize; + if( info->pieceSize ) + tor->lastPieceSize = info->totalSize % info->pieceSize; if( !tor->lastPieceSize ) tor->lastPieceSize = info->pieceSize; - tor->lastBlockSize = info->totalSize % tor->blockSize; + if( tor->blockSize ) + tor->lastBlockSize = info->totalSize % tor->blockSize; if( !tor->lastBlockSize ) tor->lastBlockSize = tor->blockSize; - tor->blockCount = - ( info->totalSize + tor->blockSize - 1 ) / tor->blockSize; + tor->blockCount = tor->blockSize + ? ( info->totalSize + tor->blockSize - 1 ) / tor->blockSize + : 0; - tor->blockCountInPiece = - info->pieceSize / tor->blockSize; + tor->blockCountInPiece = tor->blockSize + ? info->pieceSize / tor->blockSize + : 0; - tor->blockCountInLastPiece = - ( tor->lastPieceSize + tor->blockSize - 1 ) / tor->blockSize; + tor->blockCountInLastPiece = tor->blockSize + ? ( tor->lastPieceSize + tor->blockSize - 1 ) / tor->blockSize + : 0; /* check our work */ - assert( ( info->pieceSize % tor->blockSize ) == 0 ); + if( tor->blockSize != 0 ) + assert( ( info->pieceSize % tor->blockSize ) == 0 ); t = info->pieceCount - 1; t *= info->pieceSize; t += tor->lastPieceSize; @@ -624,10 +608,53 @@ torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) tr_torrentInitFilePieces( tor ); + tr_bitfieldConstruct( &tor->checkedPieces, tor->info.pieceCount ); + + tor->completeness = tr_cpGetStatus( &tor->completion ); +} + +void +tr_torrentGotNewInfoDict( tr_torrent * tor ) +{ + torrentInitFromInfo( tor ); +} + +static void +torrentInit( tr_torrent * tor, const tr_ctor * ctor ) +{ + int doStart; + uint64_t loaded; + const char * dir; + static int nextUniqueId = 1; + tr_session * session = tr_ctorGetSession( ctor ); + + assert( session != NULL ); + + tr_globalLock( session ); + + tor->session = session; + tor->uniqueId = nextUniqueId++; + tor->magicNumber = TORRENT_MAGIC_NUMBER; + + randomizeTiers( &tor->info ); + tr_sha1( tor->obfuscatedHash, "req2", 4, - info->hash, SHA_DIGEST_LENGTH, + tor->info.hash, SHA_DIGEST_LENGTH, NULL ); + if( !tr_ctorGetDownloadDir( ctor, TR_FORCE, &dir ) || + !tr_ctorGetDownloadDir( ctor, TR_FALLBACK, &dir ) ) + tor->downloadDir = tr_strdup( dir ); + + if( tr_ctorGetIncompleteDir( ctor, &dir ) ) + dir = tr_sessionGetIncompleteDir( session ); + if( tr_sessionIsIncompleteDirEnabled( session ) ) + tor->incompleteDir = tr_strdup( dir ); + + tor->bandwidth = tr_bandwidthNew( session, session->bandwidth ); + + tor->error = TR_STAT_OK; + tr_peerMgrAddTorrent( session->peerMgr, tor ); assert( !tor->downloadedCur ); @@ -637,9 +664,6 @@ torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) tr_ctorInitTorrentWanted( ctor, tor ); - tor->error = TR_STAT_OK; - - tr_bitfieldConstruct( &tor->checkedPieces, tor->info.pieceCount ); tr_torrentUncheck( tor ); tr_torrentSetAddedDate( tor, time( NULL ) ); /* this is a default value to be @@ -667,8 +691,6 @@ torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) tr_torrentSetRatioLimit( tor, tr_sessionGetRatioLimit( tor->session ) ); } - tor->completeness = tr_cpGetStatus( &tor->completion ); - { tr_torrent * it = NULL; tr_torrent * last = NULL; @@ -682,7 +704,7 @@ torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) ++session->torrentCount; } - tr_globalUnlock( session ); + torrentInitFromInfo( tor ); /* maybe save our own copy of the metainfo */ if( tr_ctorGetSave( ctor ) ) @@ -690,19 +712,19 @@ torrentRealInit( tr_torrent * tor, const tr_ctor * ctor ) const tr_benc * val; if( !tr_ctorGetMetainfo( ctor, &val ) ) { - const char * filename = tor->info.torrent; - tr_bencToFile( val, TR_FMT_BENC, filename ); - tr_sessionSetTorrentFile( tor->session, tor->info.hashString, filename ); + const char * path = tor->info.torrent; + tr_bencToFile( val, TR_FMT_BENC, path ); + tr_sessionSetTorrentFile( tor->session, tor->info.hashString, path ); } } - tor->tiers = tr_announcerAddTorrent( session->announcer, tor ); + tor->tiers = tr_announcerAddTorrent( tor->session->announcer, tor ); tor->tiersSubscription = tr_announcerSubscribe( tor->tiers, onTrackerResponse, tor ); - tr_metainfoMigrate( session, &tor->info ); - if( doStart ) torrentStart( tor ); + + tr_globalUnlock( session ); } tr_parse_result @@ -722,7 +744,7 @@ tr_torrentParse( const tr_ctor * ctor, tr_info * setmeInfo ) if( tr_ctorGetMetainfo( ctor, &metainfo ) ) return TR_PARSE_ERR; - didParse = tr_metainfoParse( session, setmeInfo, metainfo ); + didParse = tr_metainfoParse( session, setmeInfo, NULL, NULL, metainfo ); doFree = didParse && ( setmeInfo == &tmp ); if( !didParse ) @@ -744,23 +766,33 @@ tr_torrent * tr_torrentNew( const tr_ctor * ctor, int * setmeError ) { - int err; - tr_info tmpInfo; + int err; + tr_info tmpInfo; tr_torrent * tor = NULL; + const tr_magnet_info * magnetInfo; assert( ctor != NULL ); assert( tr_isSession( tr_ctorGetSession( ctor ) ) ); - err = tr_torrentParse( ctor, &tmpInfo ); - if( !err ) + if( !tr_ctorGetMagnet( ctor, &magnetInfo ) ) { tor = tr_new0( tr_torrent, 1 ); - tor->info = tmpInfo; - torrentRealInit( tor, ctor ); + tr_metainfoSetFromMagnet( &tor->info, magnetInfo ); + torrentInit( tor, ctor ); } - else if( setmeError ) + else { - *setmeError = err; + err = tr_torrentParse( ctor, &tmpInfo ); + if( !err ) + { + tor = tr_new0( tr_torrent, 1 ); + tor->info = tmpInfo; + torrentInit( tor, ctor ); + } + else if( setmeError ) + { + *setmeError = err; + } } return tor; @@ -988,7 +1020,7 @@ tr_torrentStat( tr_torrent * tor ) : 0.8*tor->etaDLSpeed + 0.2*s->pieceDownloadSpeed; /* smooth across 5 readings */ tor->etaDLSpeedCalculatedAt = now; } - + if( s->leftUntilDone > s->desiredAvailable ) s->eta = TR_ETA_NOT_AVAIL; else if( s->pieceDownloadSpeed < 0.1 ) @@ -1006,7 +1038,7 @@ tr_torrentStat( tr_torrent * tor ) : 0.8*tor->etaULSpeed + 0.2*s->pieceUploadSpeed; /* smooth across 5 readings */ tor->etaULSpeedCalculatedAt = now; } - + if( s->pieceUploadSpeed < 0.1 ) s->eta = TR_ETA_UNKNOWN; else @@ -1020,13 +1052,13 @@ tr_torrentStat( tr_torrent * tor ) s->eta = TR_ETA_NOT_AVAIL; break; } - - if( !checkSeedRatio || s->ratio >= seedRatio || s->ratio == TR_RATIO_INF ) - s->percentRatio = 1.0; - else if( s->ratio == TR_RATIO_NA ) - s->percentRatio = 0.0; - else - s->percentRatio = s->ratio / seedRatio; + + if( !checkSeedRatio || s->ratio >= seedRatio || s->ratio == TR_RATIO_INF ) + s->percentRatio = 1.0; + else if( s->ratio == TR_RATIO_NA ) + s->percentRatio = 0.0; + else + s->percentRatio = s->ratio / seedRatio; tr_torrentUnlock( tor ); @@ -2096,7 +2128,10 @@ tr_torrentSetAnnounceList( tr_torrent * tor, /* try to parse it back again, to make sure it's good */ memset( &tmpInfo, 0, sizeof( tr_info ) ); - if( tr_metainfoParse( tor->session, &tmpInfo, &metainfo ) ) + if( tr_metainfoParse( tor->session, &tmpInfo, + &tor->infoDictOffset, + &tor->infoDictLength, + &metainfo ) ) { /* it's good, so keep these new trackers and free the old ones */ @@ -2367,7 +2402,6 @@ tr_torrentDeleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc ) fileFunc( path ); tr_free( path ); - tmp = tr_torrentBuildPartial( tor, 0 ); path = tr_buildPath( tor->currentDir, tmp, NULL ); fileFunc( path ); @@ -2656,7 +2690,6 @@ tr_torrentFindFile2( const tr_torrent * tor, tr_file_index_t fileNum, return b != NULL; } - char* tr_torrentFindFile( const tr_torrent * tor, tr_file_index_t fileNum ) { diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index a85d32bf5..60e49c211 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -25,6 +25,7 @@ struct tr_bandwidth; struct tr_ratecontrol; struct tr_torrent_tiers; +struct tr_magnet_info; /** *** Package-visible ctor API @@ -35,6 +36,8 @@ void tr_ctorSetSave( tr_ctor * ctor, int tr_ctorGetSave( const tr_ctor * ctor ); +int tr_ctorGetMagnet( const tr_ctor * ctor, const struct tr_magnet_info ** setme ); + void tr_ctorInitTorrentPriorities( const tr_ctor * ctor, tr_torrent * tor ); void tr_ctorInitTorrentWanted( const tr_ctor * ctor, tr_torrent * tor ); @@ -131,6 +134,8 @@ tr_verify_state; void tr_torrentSetVerifyState( tr_torrent * tor, tr_verify_state state ); +struct tr_incomplete_metadata; + struct tr_torrent { tr_session * session; @@ -143,6 +148,11 @@ struct tr_torrent uint8_t obfuscatedHash[SHA_DIGEST_LENGTH]; + /* Used when the torrent has been created with a magnet link + * and we're in the process of downloading the metainfo from + * other peers */ + struct tr_incomplete_metadata * incompleteMetadata; + /* If the initiator of the connection receives a handshake in which the * peer_id does not match the expected peerid, then the initiator is * expected to drop the connection. Note that the initiator presumably @@ -158,6 +168,12 @@ struct tr_torrent /* Where the files are when the torrent is incomplete */ char * incompleteDir; + /* Length, in bytes, of the "info" dict in the .torrent file */ + int infoDictLength; + + /* Offset, in bytes, of the beginning of the "info" dict in the .torrent file */ + int infoDictOffset; + /* Where the files are now. * This pointer will be equal to downloadDir or incompleteDir */ const char * currentDir; @@ -339,14 +355,16 @@ static TR_INLINE tr_bool tr_isTorrent( const tr_torrent * tor ) /* set a flag indicating that the torrent's .resume file * needs to be saved when the torrent is closed */ -static TR_INLINE void tr_torrentSetDirty( tr_torrent * tor ) +static TR_INLINE +void tr_torrentSetDirty( tr_torrent * tor ) { assert( tr_isTorrent( tor ) ); tor->isDirty = TRUE; } -static TR_INLINE const char * tr_torrentName( const tr_torrent * tor ) +static TR_INLINE +const char * tr_torrentName( const tr_torrent * tor ) { assert( tr_isTorrent( tor ) ); @@ -382,5 +400,8 @@ tr_bool tr_torrentFindFile2( const tr_torrent *, tr_file_index_t fileNo, * a la Firefox. */ char* tr_torrentBuildPartial( const tr_torrent *, tr_file_index_t fileNo ); +/* for when the info dict has been fundamentally changed wrt files, + * piece size, etc. such as in BEP 9 where peers exchange metadata */ +void tr_torrentGotNewInfoDict( tr_torrent * tor ); #endif diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 6d70478f8..338e639ac 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -858,6 +858,9 @@ void tr_ctorFree( tr_ctor * ctor ); void tr_ctorSetDeleteSource( tr_ctor * ctor, tr_bool doDelete ); +int tr_ctorSetMagnet( tr_ctor * ctor, + const char * url ); + int tr_ctorSetMetainfo( tr_ctor * ctor, const uint8_t * metainfo, size_t len ); diff --git a/libtransmission/utils-test.c b/libtransmission/utils-test.c index 2727d648a..0b0066f7e 100644 --- a/libtransmission/utils-test.c +++ b/libtransmission/utils-test.c @@ -297,6 +297,28 @@ test_hex( void ) return 0; } +static int +test_array( void ) +{ + int i; + int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + int n = sizeof( array ) / sizeof( array[0] ); + + tr_removeElementFromArray( array, 5, sizeof( int ), n-- ); + for( i=0; i