1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 09:37:56 +00:00
transmission/libtransmission/peer-msgs.c
Mike Gelfand f6f7bf8227 #4400, #5462: Move random helpers to crypto-utils
On a way to factoring out OpenSSL support to a standalone file to ease
addition of other crypto libraries support in the future, move helpers
providing random numbers/data generation to crypto-utils.{c,h}. OpenSSL-
related functionality (generation of cryptographically strong random
data) is moved to crypto-utils-openssl.c.

Rename functions to follow currently accepted style:
* tr_cryptoRandBuf -> tr_rand_buffer
* tr_cryptoRandInt -> tr_rand_int
* tr_cryptoWeakRandInt -> tr_rand_int_weak

Fix rare case of invalid value being returned from tr_rand_int. Return
value for abs(INT_MIN) may be undefined and thus negative, and so
tr_rand_int will return negative value which is incorrect (out of
requested and expected range).
2014-12-04 11:27:38 +00:00

2673 lines
75 KiB
C

/*
* This file Copyright (C) 2007-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include "transmission.h"
#include "cache.h"
#include "completion.h"
#include "file.h"
#include "log.h"
#include "peer-io.h"
#include "peer-mgr.h"
#include "peer-msgs.h"
#include "session.h"
#include "torrent.h"
#include "torrent-magnet.h"
#include "tr-dht.h"
#include "utils.h"
#include "variant.h"
#include "version.h"
#ifndef EBADMSG
#define EBADMSG EINVAL
#endif
/**
***
**/
enum
{
BT_CHOKE = 0,
BT_UNCHOKE = 1,
BT_INTERESTED = 2,
BT_NOT_INTERESTED = 3,
BT_HAVE = 4,
BT_BITFIELD = 5,
BT_REQUEST = 6,
BT_PIECE = 7,
BT_CANCEL = 8,
BT_PORT = 9,
BT_FEXT_SUGGEST = 13,
BT_FEXT_HAVE_ALL = 14,
BT_FEXT_HAVE_NONE = 15,
BT_FEXT_REJECT = 16,
BT_FEXT_ALLOWED_FAST = 17,
BT_LTEP = 20,
LTEP_HANDSHAKE = 0,
UT_PEX_ID = 1,
UT_METADATA_ID = 3,
MAX_PEX_PEER_COUNT = 50,
MIN_CHOKE_PERIOD_SEC = 10,
/* idle seconds before we send a keepalive */
KEEPALIVE_INTERVAL_SECS = 100,
PEX_INTERVAL_SECS = 90, /* sec between sendPex () calls */
REQQ = 512,
METADATA_REQQ = 64,
MAGIC_NUMBER = 21549,
/* used in lowering the outMessages queue period */
IMMEDIATE_PRIORITY_INTERVAL_SECS = 0,
HIGH_PRIORITY_INTERVAL_SECS = 2,
LOW_PRIORITY_INTERVAL_SECS = 10,
/* number of pieces we'll allow in our fast set */
MAX_FAST_SET_SIZE = 3,
/* how many blocks to keep prefetched per peer */
PREFETCH_SIZE = 18,
/* when we're making requests from another peer,
batch them together to send enough requests to
meet our bandwidth goals for the next N seconds */
REQUEST_BUF_SECS = 10,
/* defined in BEP #9 */
METADATA_MSG_TYPE_REQUEST = 0,
METADATA_MSG_TYPE_DATA = 1,
METADATA_MSG_TYPE_REJECT = 2
};
enum
{
AWAITING_BT_LENGTH,
AWAITING_BT_ID,
AWAITING_BT_MESSAGE,
AWAITING_BT_PIECE
};
typedef enum
{
ENCRYPTION_PREFERENCE_UNKNOWN,
ENCRYPTION_PREFERENCE_YES,
ENCRYPTION_PREFERENCE_NO
}
encryption_preference_t;
/**
***
**/
struct peer_request
{
uint32_t index;
uint32_t offset;
uint32_t length;
};
static void
blockToReq (const tr_torrent * tor,
tr_block_index_t block,
struct peer_request * setme)
{
tr_torrentGetBlockLocation (tor, block, &setme->index,
&setme->offset,
&setme->length);
}
/**
***
**/
/* this is raw, unchanged data from the peer regarding
* the current message that it's sending us. */
struct tr_incoming
{
uint8_t id;
uint32_t length; /* includes the +1 for id length */
struct peer_request blockReq; /* metadata for incoming blocks */
struct evbuffer * block; /* piece data for incoming blocks */
};
/**
* Low-level communication state information about a connected peer.
*
* This structure remembers the low-level protocol states that we're
* in with this peer, such as active requests, pex messages, and so on.
* Its fields are all private to peer-msgs.c.
*
* Data not directly involved with sending & receiving messages is
* stored in tr_peer, where it can be accessed by both peermsgs and
* the peer manager.
*
* @see struct peer_atom
* @see tr_peer
*/
struct tr_peerMsgs
{
struct tr_peer peer; /* parent */
uint16_t magic_number;
/* Whether or not we've choked this peer. */
bool peer_is_choked;
/* whether or not the peer has indicated it will download from us. */
bool peer_is_interested;
/* whether or the peer is choking us. */
bool client_is_choked;
/* whether or not we've indicated to the peer that we would download from them if unchoked. */
bool client_is_interested;
bool peerSupportsPex;
bool peerSupportsMetadataXfer;
bool clientSentLtepHandshake;
bool peerSentLtepHandshake;
/*bool haveFastSet;*/
int desiredRequestCount;
int prefetchCount;
int is_active[2];
/* how long the outMessages batch should be allowed to grow before
* it's flushed -- some messages (like requests >:) should be sent
* very quickly; others aren't as urgent. */
int8_t outMessagesBatchPeriod;
uint8_t state;
uint8_t ut_pex_id;
uint8_t ut_metadata_id;
uint16_t pexCount;
uint16_t pexCount6;
tr_port dht_port;
encryption_preference_t encryption_preference;
size_t metadata_size_hint;
#if 0
size_t fastsetSize;
tr_piece_index_t fastset[MAX_FAST_SET_SIZE];
#endif
tr_torrent * torrent;
tr_peer_callback callback;
void * callbackData;
struct evbuffer * outMessages; /* all the non-piece messages */
struct peer_request peerAskedFor[REQQ];
int peerAskedForMetadata[METADATA_REQQ];
int peerAskedForMetadataCount;
tr_pex * pex;
tr_pex * pex6;
/*time_t clientSentPexAt;*/
time_t clientSentAnythingAt;
time_t chokeChangedAt;
/* when we started batching the outMessages */
time_t outMessagesBatchedAt;
struct tr_incoming incoming;
/* if the peer supports the Extension Protocol in BEP 10 and
supplied a reqq argument, it's stored here. Otherwise, the
value is zero and should be ignored. */
int64_t reqq;
struct event * pexTimer;
struct tr_peerIo * io;
};
/**
***
**/
static inline tr_session*
getSession (struct tr_peerMsgs * msgs)
{
return msgs->torrent->session;
}
/**
***
**/
static void
myDebug (const char * file, int line,
const struct tr_peerMsgs * msgs,
const char * fmt, ...) TR_GNUC_PRINTF(4, 5);
static void
myDebug (const char * file, int line,
const struct tr_peerMsgs * msgs,
const char * fmt, ...)
{
const tr_sys_file_t fp = tr_logGetFile ();
if (fp != TR_BAD_SYS_FILE)
{
va_list args;
char timestr[64];
struct evbuffer * buf = evbuffer_new ();
char * base = tr_sys_path_basename (file, NULL);
char * message;
evbuffer_add_printf (buf, "[%s] %s - %s [%s]: ",
tr_logGetTimeStr (timestr, sizeof (timestr)),
tr_torrentName (msgs->torrent),
tr_peerIoGetAddrStr (msgs->io),
tr_quark_get_string (msgs->peer.client, NULL));
va_start (args, fmt);
evbuffer_add_vprintf (buf, fmt, args);
va_end (args);
evbuffer_add_printf (buf, " (%s:%d)", base, line);
message = evbuffer_free_to_str (buf);
tr_sys_file_write_line (fp, message, NULL);
tr_free (base);
tr_free (message);
}
}
#define dbgmsg(msgs, ...) \
do \
{ \
if (tr_logGetDeepEnabled ()) \
myDebug (__FILE__, __LINE__, msgs, __VA_ARGS__); \
} \
while (0)
/**
***
**/
static void
pokeBatchPeriod (tr_peerMsgs * msgs, int interval)
{
if (msgs->outMessagesBatchPeriod > interval)
{
msgs->outMessagesBatchPeriod = interval;
dbgmsg (msgs, "lowering batch interval to %d seconds", interval);
}
}
static void
dbgOutMessageLen (tr_peerMsgs * msgs)
{
dbgmsg (msgs, "outMessage size is now %"TR_PRIuSIZE, evbuffer_get_length (msgs->outMessages));
}
static void
protocolSendReject (tr_peerMsgs * msgs, const struct peer_request * req)
{
struct evbuffer * out = msgs->outMessages;
assert (tr_peerIoSupportsFEXT (msgs->io));
evbuffer_add_uint32 (out, sizeof (uint8_t) + 3 * sizeof (uint32_t));
evbuffer_add_uint8 (out, BT_FEXT_REJECT);
evbuffer_add_uint32 (out, req->index);
evbuffer_add_uint32 (out, req->offset);
evbuffer_add_uint32 (out, req->length);
dbgmsg (msgs, "rejecting %u:%u->%u...", req->index, req->offset, req->length);
dbgOutMessageLen (msgs);
}
static void
protocolSendRequest (tr_peerMsgs * msgs, const struct peer_request * req)
{
struct evbuffer * out = msgs->outMessages;
evbuffer_add_uint32 (out, sizeof (uint8_t) + 3 * sizeof (uint32_t));
evbuffer_add_uint8 (out, BT_REQUEST);
evbuffer_add_uint32 (out, req->index);
evbuffer_add_uint32 (out, req->offset);
evbuffer_add_uint32 (out, req->length);
dbgmsg (msgs, "requesting %u:%u->%u...", req->index, req->offset, req->length);
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
static void
protocolSendCancel (tr_peerMsgs * msgs, const struct peer_request * req)
{
struct evbuffer * out = msgs->outMessages;
evbuffer_add_uint32 (out, sizeof (uint8_t) + 3 * sizeof (uint32_t));
evbuffer_add_uint8 (out, BT_CANCEL);
evbuffer_add_uint32 (out, req->index);
evbuffer_add_uint32 (out, req->offset);
evbuffer_add_uint32 (out, req->length);
dbgmsg (msgs, "cancelling %u:%u->%u...", req->index, req->offset, req->length);
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
static void
protocolSendPort (tr_peerMsgs *msgs, uint16_t port)
{
struct evbuffer * out = msgs->outMessages;
dbgmsg (msgs, "sending Port %u", port);
evbuffer_add_uint32 (out, 3);
evbuffer_add_uint8 (out, BT_PORT);
evbuffer_add_uint16 (out, port);
}
static void
protocolSendHave (tr_peerMsgs * msgs, uint32_t index)
{
struct evbuffer * out = msgs->outMessages;
evbuffer_add_uint32 (out, sizeof (uint8_t) + sizeof (uint32_t));
evbuffer_add_uint8 (out, BT_HAVE);
evbuffer_add_uint32 (out, index);
dbgmsg (msgs, "sending Have %u", index);
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, LOW_PRIORITY_INTERVAL_SECS);
}
#if 0
static void
protocolSendAllowedFast (tr_peerMsgs * msgs, uint32_t pieceIndex)
{
tr_peerIo * io = msgs->io;
struct evbuffer * out = msgs->outMessages;
assert (tr_peerIoSupportsFEXT (msgs->io));
evbuffer_add_uint32 (io, out, sizeof (uint8_t) + sizeof (uint32_t));
evbuffer_add_uint8 (io, out, BT_FEXT_ALLOWED_FAST);
evbuffer_add_uint32 (io, out, pieceIndex);
dbgmsg (msgs, "sending Allowed Fast %u...", pieceIndex);
dbgOutMessageLen (msgs);
}
#endif
static void
protocolSendChoke (tr_peerMsgs * msgs, int choke)
{
struct evbuffer * out = msgs->outMessages;
evbuffer_add_uint32 (out, sizeof (uint8_t));
evbuffer_add_uint8 (out, choke ? BT_CHOKE : BT_UNCHOKE);
dbgmsg (msgs, "sending %s...", choke ? "Choke" : "Unchoke");
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
static void
protocolSendHaveAll (tr_peerMsgs * msgs)
{
struct evbuffer * out = msgs->outMessages;
assert (tr_peerIoSupportsFEXT (msgs->io));
evbuffer_add_uint32 (out, sizeof (uint8_t));
evbuffer_add_uint8 (out, BT_FEXT_HAVE_ALL);
dbgmsg (msgs, "sending HAVE_ALL...");
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
static void
protocolSendHaveNone (tr_peerMsgs * msgs)
{
struct evbuffer * out = msgs->outMessages;
assert (tr_peerIoSupportsFEXT (msgs->io));
evbuffer_add_uint32 (out, sizeof (uint8_t));
evbuffer_add_uint8 (out, BT_FEXT_HAVE_NONE);
dbgmsg (msgs, "sending HAVE_NONE...");
dbgOutMessageLen (msgs);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
/**
*** EVENTS
**/
static void
publish (tr_peerMsgs * msgs, tr_peer_event * e)
{
if (msgs->callback != NULL)
(*msgs->callback) (&msgs->peer, e, msgs->callbackData);
}
static void
fireError (tr_peerMsgs * msgs, int err)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_ERROR;
e.err = err;
publish (msgs, &e);
}
static void
fireGotBlock (tr_peerMsgs * msgs, const struct peer_request * req)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_BLOCK;
e.pieceIndex = req->index;
e.offset = req->offset;
e.length = req->length;
publish (msgs, &e);
}
static void
fireGotRej (tr_peerMsgs * msgs, const struct peer_request * req)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_REJ;
e.pieceIndex = req->index;
e.offset = req->offset;
e.length = req->length;
publish (msgs, &e);
}
static void
fireGotChoke (tr_peerMsgs * msgs)
{
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
fireClientGotPieceData (tr_peerMsgs * msgs, uint32_t length)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.length = length;
e.eventType = TR_PEER_CLIENT_GOT_PIECE_DATA;
publish (msgs, &e);
}
static void
firePeerGotPieceData (tr_peerMsgs * msgs, uint32_t length)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.length = length;
e.eventType = TR_PEER_PEER_GOT_PIECE_DATA;
publish (msgs, &e);
}
static void
fireClientGotSuggest (tr_peerMsgs * msgs, uint32_t pieceIndex)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_SUGGEST;
e.pieceIndex = pieceIndex;
publish (msgs, &e);
}
static void
fireClientGotPort (tr_peerMsgs * msgs, tr_port port)
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_PORT;
e.port = port;
publish (msgs, &e);
}
static void
fireClientGotAllowedFast (tr_peerMsgs * msgs, uint32_t pieceIndex)
{
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);
}
/**
*** ALLOWED FAST SET
*** For explanation, see http://www.bittorrent.org/beps/bep_0006.html
**/
#if 0
size_t
tr_generateAllowedSet (tr_piece_index_t * setmePieces,
size_t desiredSetSize,
size_t pieceCount,
const uint8_t * infohash,
const tr_address * addr)
{
size_t setSize = 0;
assert (setmePieces);
assert (desiredSetSize <= pieceCount);
assert (desiredSetSize);
assert (pieceCount);
assert (infohash);
assert (addr);
if (addr->type == TR_AF_INET)
{
uint8_t w[SHA_DIGEST_LENGTH + 4], *walk=w;
uint8_t x[SHA_DIGEST_LENGTH];
uint32_t ui32 = ntohl (htonl (addr->addr.addr4.s_addr) & 0xffffff00); /* (1) */
memcpy (w, &ui32, sizeof (uint32_t));
walk += sizeof (uint32_t);
memcpy (walk, infohash, SHA_DIGEST_LENGTH); /* (2) */
walk += SHA_DIGEST_LENGTH;
tr_sha1 (x, w, walk-w, NULL); /* (3) */
assert (sizeof (w) == walk-w);
while (setSize<desiredSetSize)
{
int i;
for (i=0; i<5 && setSize<desiredSetSize; ++i) /* (4) */
{
size_t k;
uint32_t j = i * 4; /* (5) */
uint32_t y = ntohl (* (uint32_t*)(x + j)); /* (6) */
uint32_t index = y % pieceCount; /* (7) */
for (k=0; k<setSize; ++k) /* (8) */
if (setmePieces[k] == index)
break;
if (k == setSize)
setmePieces[setSize++] = index; /* (9) */
}
tr_sha1 (x, x, sizeof (x), NULL); /* (3) */
}
}
return setSize;
}
static void
updateFastSet (tr_peerMsgs * msgs UNUSED)
{
const bool fext = tr_peerIoSupportsFEXT (msgs->io);
const int peerIsNeedy = msgs->peer->progress < 0.10;
if (fext && peerIsNeedy && !msgs->haveFastSet)
{
size_t i;
const struct tr_address * addr = tr_peerIoGetAddress (msgs->io, NULL);
const tr_info * inf = &msgs->torrent->info;
const size_t numwant = MIN (MAX_FAST_SET_SIZE, inf->pieceCount);
/* build the fast set */
msgs->fastsetSize = tr_generateAllowedSet (msgs->fastset, numwant, inf->pieceCount, inf->hash, addr);
msgs->haveFastSet = true;
/* send it to the peer */
for (i=0; i<msgs->fastsetSize; ++i)
protocolSendAllowedFast (msgs, msgs->fastset[i]);
}
}
#endif
/***
**** ACTIVE
***/
static bool
tr_peerMsgsCalculateActive (const tr_peerMsgs * msgs, tr_direction direction)
{
bool is_active;
assert (tr_isPeerMsgs (msgs));
assert (tr_isDirection (direction));
if (direction == TR_CLIENT_TO_PEER)
{
is_active = tr_peerMsgsIsPeerInterested (msgs)
&& !tr_peerMsgsIsPeerChoked (msgs);
if (is_active)
assert (!tr_peerIsSeed (&msgs->peer));
}
else /* TR_PEER_TO_CLIENT */
{
if (!tr_torrentHasMetadata (msgs->torrent))
{
is_active = true;
}
else
{
is_active = tr_peerMsgsIsClientInterested (msgs)
&& !tr_peerMsgsIsClientChoked (msgs);
if (is_active)
assert (!tr_torrentIsSeed (msgs->torrent));
}
}
return is_active;
}
bool
tr_peerMsgsIsActive (const tr_peerMsgs * msgs, tr_direction direction)
{
bool is_active;
assert (tr_isPeerMsgs (msgs));
assert (tr_isDirection (direction));
is_active = msgs->is_active[direction];
assert (is_active == tr_peerMsgsCalculateActive (msgs, direction));
return is_active;
}
static void
tr_peerMsgsSetActive (tr_peerMsgs * msgs,
tr_direction direction,
bool is_active)
{
dbgmsg (msgs, "direction [%d] is_active [%d]", (int)direction, (int)is_active);
if (msgs->is_active[direction] != is_active)
{
msgs->is_active[direction] = is_active;
tr_swarmIncrementActivePeers (msgs->torrent->swarm, direction, is_active);
}
}
void
tr_peerMsgsUpdateActive (tr_peerMsgs * msgs, tr_direction direction)
{
const bool is_active = tr_peerMsgsCalculateActive (msgs, direction);
tr_peerMsgsSetActive (msgs, direction, is_active);
}
/**
*** INTEREST
**/
static void
sendInterest (tr_peerMsgs * msgs, bool b)
{
struct evbuffer * out = msgs->outMessages;
assert (msgs);
assert (tr_isBool (b));
msgs->client_is_interested = b;
dbgmsg (msgs, "Sending %s", b ? "Interested" : "Not Interested");
evbuffer_add_uint32 (out, sizeof (uint8_t));
evbuffer_add_uint8 (out, b ? BT_INTERESTED : BT_NOT_INTERESTED);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
}
static void
updateInterest (tr_peerMsgs * msgs UNUSED)
{
/* FIXME -- might need to poke the mgr on startup */
}
void
tr_peerMsgsSetInterested (tr_peerMsgs * msgs, bool b)
{
assert (tr_isBool (b));
if (msgs->client_is_interested != b)
{
sendInterest (msgs, b);
tr_peerMsgsUpdateActive (msgs, TR_PEER_TO_CLIENT);
}
}
static bool
popNextMetadataRequest (tr_peerMsgs * msgs, int * piece)
{
if (msgs->peerAskedForMetadataCount == 0)
return false;
*piece = msgs->peerAskedForMetadata[0];
tr_removeElementFromArray (msgs->peerAskedForMetadata, 0, sizeof (int),
msgs->peerAskedForMetadataCount--);
return true;
}
static bool
popNextRequest (tr_peerMsgs * msgs, struct peer_request * setme)
{
if (msgs->peer.pendingReqsToClient == 0)
return false;
*setme = msgs->peerAskedFor[0];
tr_removeElementFromArray (msgs->peerAskedFor,
0,
sizeof (struct peer_request),
msgs->peer.pendingReqsToClient--);
return true;
}
static void
cancelAllRequestsToClient (tr_peerMsgs * msgs)
{
struct peer_request req;
const int mustSendCancel = tr_peerIoSupportsFEXT (msgs->io);
while (popNextRequest (msgs, &req))
if (mustSendCancel)
protocolSendReject (msgs, &req);
}
void
tr_peerMsgsSetChoke (tr_peerMsgs * msgs, bool peer_is_choked)
{
const time_t now = tr_time ();
const time_t fibrillationTime = now - MIN_CHOKE_PERIOD_SEC;
assert (msgs != NULL);
assert (tr_isBool (peer_is_choked));
if (msgs->chokeChangedAt > fibrillationTime)
{
dbgmsg (msgs, "Not changing choke to %d to avoid fibrillation", peer_is_choked);
}
else if (msgs->peer_is_choked != peer_is_choked)
{
msgs->peer_is_choked = peer_is_choked;
if (peer_is_choked)
cancelAllRequestsToClient (msgs);
protocolSendChoke (msgs, peer_is_choked);
msgs->chokeChangedAt = now;
tr_peerMsgsUpdateActive (msgs, TR_CLIENT_TO_PEER);
}
}
/**
***
**/
void
tr_peerMsgsHave (tr_peerMsgs * msgs, uint32_t index)
{
protocolSendHave (msgs, index);
/* since we have more pieces now, we might not be interested in this peer */
updateInterest (msgs);
}
/**
***
**/
static bool
reqIsValid (const tr_peerMsgs * peer,
uint32_t index,
uint32_t offset,
uint32_t length)
{
return tr_torrentReqIsValid (peer->torrent, index, offset, length);
}
static bool
requestIsValid (const tr_peerMsgs * msgs, const struct peer_request * req)
{
return reqIsValid (msgs, req->index, req->offset, req->length);
}
void
tr_peerMsgsCancel (tr_peerMsgs * msgs, tr_block_index_t block)
{
struct peer_request req;
/*fprintf (stderr, "SENDING CANCEL MESSAGE FOR BLOCK %"TR_PRIuSIZE"\n\t\tFROM PEER %p ------------------------------------\n", (size_t)block, msgs->peer);*/
blockToReq (msgs->torrent, block, &req);
protocolSendCancel (msgs, &req);
}
/**
***
**/
static void
sendLtepHandshake (tr_peerMsgs * msgs)
{
tr_variant val;
bool allow_pex;
bool allow_metadata_xfer;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
const unsigned char * ipv6 = tr_globalIPv6 ();
static tr_quark version_quark = 0;
if (msgs->clientSentLtepHandshake)
return;
if (!version_quark)
version_quark = tr_quark_new (TR_NAME " " USERAGENT_PREFIX, -1);
dbgmsg (msgs, "sending an ltep handshake");
msgs->clientSentLtepHandshake = true;
/* decide if we want to advertise metadata xfer support (BEP 9) */
if (tr_torrentIsPrivate (msgs->torrent))
allow_metadata_xfer = false;
else
allow_metadata_xfer = true;
/* decide if we want to advertise pex support */
if (!tr_torrentAllowsPex (msgs->torrent))
allow_pex = false;
else if (msgs->peerSentLtepHandshake)
allow_pex = msgs->peerSupportsPex;
else
allow_pex = true;
tr_variantInitDict (&val, 8);
tr_variantDictAddInt (&val, TR_KEY_e, getSession (msgs)->encryptionMode != TR_CLEAR_PREFERRED);
if (ipv6 != NULL)
tr_variantDictAddRaw (&val, TR_KEY_ipv6, ipv6, 16);
if (allow_metadata_xfer && tr_torrentHasMetadata (msgs->torrent)
&& (msgs->torrent->infoDictLength > 0))
tr_variantDictAddInt (&val, TR_KEY_metadata_size, msgs->torrent->infoDictLength);
tr_variantDictAddInt (&val, TR_KEY_p, tr_sessionGetPublicPeerPort (getSession (msgs)));
tr_variantDictAddInt (&val, TR_KEY_reqq, REQQ);
tr_variantDictAddInt (&val, TR_KEY_upload_only, tr_torrentIsSeed (msgs->torrent));
tr_variantDictAddQuark (&val, TR_KEY_v, version_quark);
if (allow_metadata_xfer || allow_pex) {
tr_variant * m = tr_variantDictAddDict (&val, TR_KEY_m, 2);
if (allow_metadata_xfer)
tr_variantDictAddInt (m, TR_KEY_ut_metadata, UT_METADATA_ID);
if (allow_pex)
tr_variantDictAddInt (m, TR_KEY_ut_pex, UT_PEX_ID);
}
payload = tr_variantToBuf (&val, TR_VARIANT_FMT_BENC);
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload));
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, LTEP_HANDSHAKE);
evbuffer_add_buffer (out, payload);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
/* cleanup */
evbuffer_free (payload);
tr_variantFree (&val);
}
static void
parseLtepHandshake (tr_peerMsgs * msgs, int len, struct evbuffer * inbuf)
{
int64_t i;
tr_variant val, * sub;
uint8_t * tmp = tr_new (uint8_t, len);
const uint8_t *addr;
size_t addr_len;
tr_pex pex;
int8_t seedProbability = -1;
memset (&pex, 0, sizeof (tr_pex));
tr_peerIoReadBytes (msgs->io, inbuf, tmp, len);
msgs->peerSentLtepHandshake = true;
if (tr_variantFromBenc (&val, tmp, len) || !tr_variantIsDict (&val))
{
dbgmsg (msgs, "GET extended-handshake, couldn't get dictionary");
tr_free (tmp);
return;
}
dbgmsg (msgs, "here is the handshake: [%*.*s]", len, len, tmp);
/* does the peer prefer encrypted connections? */
if (tr_variantDictFindInt (&val, TR_KEY_e, &i)) {
msgs->encryption_preference = i ? ENCRYPTION_PREFERENCE_YES
: ENCRYPTION_PREFERENCE_NO;
if (i)
pex.flags |= ADDED_F_ENCRYPTION_FLAG;
}
/* check supported messages for utorrent pex */
msgs->peerSupportsPex = false;
msgs->peerSupportsMetadataXfer = false;
if (tr_variantDictFindDict (&val, TR_KEY_m, &sub)) {
if (tr_variantDictFindInt (sub, TR_KEY_ut_pex, &i)) {
msgs->peerSupportsPex = i != 0;
msgs->ut_pex_id = (uint8_t) i;
dbgmsg (msgs, "msgs->ut_pex is %d", (int)msgs->ut_pex_id);
}
if (tr_variantDictFindInt (sub, TR_KEY_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);
}
if (tr_variantDictFindInt (sub, TR_KEY_ut_holepunch, &i)) {
/* Mysterious µTorrent extension that we don't grok. However,
it implies support for µTP, so use it to indicate that. */
tr_peerMgrSetUtpFailed (msgs->torrent,
tr_peerIoGetAddress (msgs->io, NULL),
false);
}
}
/* look for metainfo size (BEP 9) */
if (tr_variantDictFindInt (&val, TR_KEY_metadata_size, &i)) {
tr_torrentSetMetadataSizeHint (msgs->torrent, i);
msgs->metadata_size_hint = (size_t) i;
}
/* look for upload_only (BEP 21) */
if (tr_variantDictFindInt (&val, TR_KEY_upload_only, &i))
seedProbability = i==0 ? 0 : 100;
/* get peer's listening port */
if (tr_variantDictFindInt (&val, TR_KEY_p, &i)) {
pex.port = htons ((uint16_t)i);
fireClientGotPort (msgs, pex.port);
dbgmsg (msgs, "peer's port is now %d", (int)i);
}
if (tr_peerIoIsIncoming (msgs->io)
&& tr_variantDictFindRaw (&val, TR_KEY_ipv4, &addr, &addr_len)
&& (addr_len == 4))
{
pex.addr.type = TR_AF_INET;
memcpy (&pex.addr.addr.addr4, addr, 4);
tr_peerMgrAddPex (msgs->torrent, TR_PEER_FROM_LTEP, &pex, seedProbability);
}
if (tr_peerIoIsIncoming (msgs->io)
&& tr_variantDictFindRaw (&val, TR_KEY_ipv6, &addr, &addr_len)
&& (addr_len == 16))
{
pex.addr.type = TR_AF_INET6;
memcpy (&pex.addr.addr.addr6, addr, 16);
tr_peerMgrAddPex (msgs->torrent, TR_PEER_FROM_LTEP, &pex, seedProbability);
}
/* get peer's maximum request queue size */
if (tr_variantDictFindInt (&val, TR_KEY_reqq, &i))
msgs->reqq = i;
tr_variantFree (&val);
tr_free (tmp);
}
static void
parseUtMetadata (tr_peerMsgs * msgs, int msglen, struct evbuffer * inbuf)
{
tr_variant dict;
char * msg_end;
const 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->io, inbuf, tmp, msglen);
msg_end = (char*)tmp + msglen;
if (!tr_variantFromBencFull (&dict, tmp, msglen, NULL, &benc_end))
{
tr_variantDictFindInt (&dict, TR_KEY_msg_type, &msg_type);
tr_variantDictFindInt (&dict, TR_KEY_piece, &piece);
tr_variantDictFindInt (&dict, TR_KEY_total_size, &total_size);
tr_variantFree (&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;
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_variant tmp;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
/* build the rejection message */
tr_variantInitDict (&tmp, 2);
tr_variantDictAddInt (&tmp, TR_KEY_msg_type, METADATA_MSG_TYPE_REJECT);
tr_variantDictAddInt (&tmp, TR_KEY_piece, piece);
payload = tr_variantToBuf (&tmp, TR_VARIANT_FMT_BENC);
/* write it out as a LTEP message to our outMessages buffer */
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload));
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, msgs->ut_metadata_id);
evbuffer_add_buffer (out, payload);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
/* cleanup */
evbuffer_free (payload);
tr_variantFree (&tmp);
}
}
tr_free (tmp);
}
static void
parseUtPex (tr_peerMsgs * msgs, int msglen, struct evbuffer * inbuf)
{
int loaded = 0;
uint8_t * tmp = tr_new (uint8_t, msglen);
tr_variant val;
tr_torrent * tor = msgs->torrent;
const uint8_t * added;
size_t added_len;
tr_peerIoReadBytes (msgs->io, inbuf, tmp, msglen);
if (tr_torrentAllowsPex (tor)
&& ((loaded = !tr_variantFromBenc (&val, tmp, msglen))))
{
if (tr_variantDictFindRaw (&val, TR_KEY_added, &added, &added_len))
{
tr_pex * pex;
size_t i, n;
size_t added_f_len = 0;
const uint8_t * added_f = NULL;
tr_variantDictFindRaw (&val, TR_KEY_added_f, &added_f, &added_f_len);
pex = tr_peerMgrCompactToPex (added, added_len, added_f, added_f_len, &n);
n = MIN (n, MAX_PEX_PEER_COUNT);
for (i=0; i<n; ++i)
{
int seedProbability = -1;
if (i < added_f_len) seedProbability = (added_f[i] & ADDED_F_SEED_FLAG) ? 100 : 0;
tr_peerMgrAddPex (tor, TR_PEER_FROM_PEX, pex+i, seedProbability);
}
tr_free (pex);
}
if (tr_variantDictFindRaw (&val, TR_KEY_added6, &added, &added_len))
{
tr_pex * pex;
size_t i, n;
size_t added_f_len = 0;
const uint8_t * added_f = NULL;
tr_variantDictFindRaw (&val, TR_KEY_added6_f, &added_f, &added_f_len);
pex = tr_peerMgrCompact6ToPex (added, added_len, added_f, added_f_len, &n);
n = MIN (n, MAX_PEX_PEER_COUNT);
for (i=0; i<n; ++i)
{
int seedProbability = -1;
if (i < added_f_len) seedProbability = (added_f[i] & ADDED_F_SEED_FLAG) ? 100 : 0;
tr_peerMgrAddPex (tor, TR_PEER_FROM_PEX, pex+i, seedProbability);
}
tr_free (pex);
}
}
if (loaded)
tr_variantFree (&val);
tr_free (tmp);
}
static void sendPex (tr_peerMsgs * msgs);
static void
parseLtep (tr_peerMsgs * msgs, int msglen, struct evbuffer * inbuf)
{
uint8_t ltep_msgid;
tr_peerIoReadUint8 (msgs->io, inbuf, &ltep_msgid);
msglen--;
if (ltep_msgid == LTEP_HANDSHAKE)
{
dbgmsg (msgs, "got ltep handshake");
parseLtepHandshake (msgs, msglen, inbuf);
if (tr_peerIoSupportsLTEP (msgs->io))
{
sendLtepHandshake (msgs);
sendPex (msgs);
}
}
else if (ltep_msgid == UT_PEX_ID)
{
dbgmsg (msgs, "got ut pex");
msgs->peerSupportsPex = true;
parseUtPex (msgs, msglen, inbuf);
}
else if (ltep_msgid == UT_METADATA_ID)
{
dbgmsg (msgs, "got ut metadata");
msgs->peerSupportsMetadataXfer = true;
parseUtMetadata (msgs, msglen, inbuf);
}
else
{
dbgmsg (msgs, "skipping unknown ltep message (%d)", (int)ltep_msgid);
evbuffer_drain (inbuf, msglen);
}
}
static int
readBtLength (tr_peerMsgs * msgs, struct evbuffer * inbuf, size_t inlen)
{
uint32_t len;
if (inlen < sizeof (len))
return READ_LATER;
tr_peerIoReadUint32 (msgs->io, inbuf, &len);
if (len == 0) /* peer sent us a keepalive message */
dbgmsg (msgs, "got KeepAlive");
else
{
msgs->incoming.length = len;
msgs->state = AWAITING_BT_ID;
}
return READ_NOW;
}
static int readBtMessage (tr_peerMsgs *, struct evbuffer *, size_t);
static int
readBtId (tr_peerMsgs * msgs, struct evbuffer * inbuf, size_t inlen)
{
uint8_t id;
if (inlen < sizeof (uint8_t))
return READ_LATER;
tr_peerIoReadUint8 (msgs->io, inbuf, &id);
msgs->incoming.id = id;
dbgmsg (msgs, "msgs->incoming.id is now %d; msgs->incoming.length is %"TR_PRIuSIZE, id, (size_t)msgs->incoming.length);
if (id == BT_PIECE)
{
msgs->state = AWAITING_BT_PIECE;
return READ_NOW;
}
else if (msgs->incoming.length != 1)
{
msgs->state = AWAITING_BT_MESSAGE;
return READ_NOW;
}
else return readBtMessage (msgs, inbuf, inlen - 1);
}
static void
updatePeerProgress (tr_peerMsgs * msgs)
{
tr_peerUpdateProgress (msgs->torrent, &msgs->peer);
/*updateFastSet (msgs);*/
updateInterest (msgs);
}
static void
prefetchPieces (tr_peerMsgs *msgs)
{
int i;
if (!getSession (msgs)->isPrefetchEnabled)
return;
for (i=msgs->prefetchCount; i<msgs->peer.pendingReqsToClient && i<PREFETCH_SIZE; ++i)
{
const struct peer_request * req = msgs->peerAskedFor + i;
if (requestIsValid (msgs, req))
{
tr_cachePrefetchBlock (getSession (msgs)->cache, msgs->torrent, req->index, req->offset, req->length);
++msgs->prefetchCount;
}
}
}
static void
peerMadeRequest (tr_peerMsgs * msgs, const struct peer_request * req)
{
const bool fext = tr_peerIoSupportsFEXT (msgs->io);
const int reqIsValid = requestIsValid (msgs, req);
const int clientHasPiece = reqIsValid && tr_torrentPieceIsComplete (msgs->torrent, req->index);
const int peerIsChoked = msgs->peer_is_choked;
bool allow = false;
if (!reqIsValid)
dbgmsg (msgs, "rejecting an invalid request.");
else if (!clientHasPiece)
dbgmsg (msgs, "rejecting request for a piece we don't have.");
else if (peerIsChoked)
dbgmsg (msgs, "rejecting request from choked peer");
else if (msgs->peer.pendingReqsToClient + 1 >= REQQ)
dbgmsg (msgs, "rejecting request ... reqq is full");
else
allow = true;
if (allow) {
msgs->peerAskedFor[msgs->peer.pendingReqsToClient++] = *req;
prefetchPieces (msgs);
} else if (fext) {
protocolSendReject (msgs, req);
}
}
static bool
messageLengthIsCorrect (const tr_peerMsgs * msg, uint8_t id, uint32_t len)
{
switch (id)
{
case BT_CHOKE:
case BT_UNCHOKE:
case BT_INTERESTED:
case BT_NOT_INTERESTED:
case BT_FEXT_HAVE_ALL:
case BT_FEXT_HAVE_NONE:
return len == 1;
case BT_HAVE:
case BT_FEXT_SUGGEST:
case BT_FEXT_ALLOWED_FAST:
return len == 5;
case BT_BITFIELD:
if (tr_torrentHasMetadata (msg->torrent))
return len == (msg->torrent->info.pieceCount + 7u) / 8u + 1u;
/* we don't know the piece count yet,
so we can only guess whether to send true or false */
if (msg->metadata_size_hint > 0)
return len <= msg->metadata_size_hint;
return true;
case BT_REQUEST:
case BT_CANCEL:
case BT_FEXT_REJECT:
return len == 13;
case BT_PIECE:
return len > 9 && len <= 16393;
case BT_PORT:
return len == 3;
case BT_LTEP:
return len >= 2;
default:
return false;
}
}
static int clientGotBlock (tr_peerMsgs * msgs,
struct evbuffer * block,
const struct peer_request * req);
static int
readBtPiece (tr_peerMsgs * msgs,
struct evbuffer * inbuf,
size_t inlen,
size_t * setme_piece_bytes_read)
{
struct peer_request * req = &msgs->incoming.blockReq;
assert (evbuffer_get_length (inbuf) >= inlen);
dbgmsg (msgs, "In readBtPiece");
if (!req->length)
{
if (inlen < 8)
return READ_LATER;
tr_peerIoReadUint32 (msgs->io, inbuf, &req->index);
tr_peerIoReadUint32 (msgs->io, inbuf, &req->offset);
req->length = msgs->incoming.length - 9;
dbgmsg (msgs, "got incoming block header %u:%u->%u", req->index, req->offset, req->length);
return READ_NOW;
}
else
{
int err;
size_t n;
size_t nLeft;
struct evbuffer * block_buffer;
if (msgs->incoming.block == NULL)
msgs->incoming.block = evbuffer_new ();
block_buffer = msgs->incoming.block;
/* read in another chunk of data */
nLeft = req->length - evbuffer_get_length (block_buffer);
n = MIN (nLeft, inlen);
tr_peerIoReadBytesToBuf (msgs->io, inbuf, block_buffer, n);
fireClientGotPieceData (msgs, n);
*setme_piece_bytes_read += n;
dbgmsg (msgs, "got %"TR_PRIuSIZE" bytes for block %u:%u->%u ... %d remain",
n, req->index, req->offset, req->length,
(int)(req->length - evbuffer_get_length (block_buffer)));
if (evbuffer_get_length (block_buffer) < req->length)
return READ_LATER;
/* pass the block along... */
err = clientGotBlock (msgs, block_buffer, req);
evbuffer_drain (block_buffer, evbuffer_get_length (block_buffer));
/* cleanup */
req->length = 0;
msgs->state = AWAITING_BT_LENGTH;
return err ? READ_ERR : READ_NOW;
}
}
static void updateDesiredRequestCount (tr_peerMsgs * msgs);
static int
readBtMessage (tr_peerMsgs * msgs, struct evbuffer * inbuf, size_t inlen)
{
uint32_t ui32;
uint32_t msglen = msgs->incoming.length;
const uint8_t id = msgs->incoming.id;
#ifndef NDEBUG
const size_t startBufLen = evbuffer_get_length (inbuf);
#endif
const bool fext = tr_peerIoSupportsFEXT (msgs->io);
--msglen; /* id length */
dbgmsg (msgs, "got BT id %d, len %d, buffer size is %"TR_PRIuSIZE, (int)id, (int)msglen, inlen);
if (inlen < msglen)
return READ_LATER;
if (!messageLengthIsCorrect (msgs, id, msglen + 1))
{
dbgmsg (msgs, "bad packet - BT message #%d with a length of %d", (int)id, (int)msglen);
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
switch (id)
{
case BT_CHOKE:
dbgmsg (msgs, "got Choke");
msgs->client_is_choked = true;
if (!fext)
fireGotChoke (msgs);
tr_peerMsgsUpdateActive (msgs, TR_PEER_TO_CLIENT);
break;
case BT_UNCHOKE:
dbgmsg (msgs, "got Unchoke");
msgs->client_is_choked = false;
tr_peerMsgsUpdateActive (msgs, TR_PEER_TO_CLIENT);
updateDesiredRequestCount (msgs);
break;
case BT_INTERESTED:
dbgmsg (msgs, "got Interested");
msgs->peer_is_interested = true;
tr_peerMsgsUpdateActive (msgs, TR_CLIENT_TO_PEER);
break;
case BT_NOT_INTERESTED:
dbgmsg (msgs, "got Not Interested");
msgs->peer_is_interested = false;
tr_peerMsgsUpdateActive (msgs, TR_CLIENT_TO_PEER);
break;
case BT_HAVE:
tr_peerIoReadUint32 (msgs->io, inbuf, &ui32);
dbgmsg (msgs, "got Have: %u", ui32);
if (tr_torrentHasMetadata (msgs->torrent)
&& (ui32 >= msgs->torrent->info.pieceCount))
{
fireError (msgs, ERANGE);
return READ_ERR;
}
/* a peer can send the same HAVE message twice... */
if (!tr_bitfieldHas (&msgs->peer.have, ui32)) {
tr_bitfieldAdd (&msgs->peer.have, ui32);
fireClientGotHave (msgs, ui32);
}
updatePeerProgress (msgs);
break;
case BT_BITFIELD: {
uint8_t * tmp = tr_new (uint8_t, msglen);
dbgmsg (msgs, "got a bitfield");
tr_peerIoReadBytes (msgs->io, inbuf, tmp, msglen);
tr_bitfieldSetRaw (&msgs->peer.have, tmp, msglen, tr_torrentHasMetadata (msgs->torrent));
fireClientGotBitfield (msgs, &msgs->peer.have);
updatePeerProgress (msgs);
tr_free (tmp);
break;
}
case BT_REQUEST:
{
struct peer_request r;
tr_peerIoReadUint32 (msgs->io, inbuf, &r.index);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.offset);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.length);
dbgmsg (msgs, "got Request: %u:%u->%u", r.index, r.offset, r.length);
peerMadeRequest (msgs, &r);
break;
}
case BT_CANCEL:
{
int i;
struct peer_request r;
tr_peerIoReadUint32 (msgs->io, inbuf, &r.index);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.offset);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.length);
tr_historyAdd (&msgs->peer.cancelsSentToClient, tr_time (), 1);
dbgmsg (msgs, "got a Cancel %u:%u->%u", r.index, r.offset, r.length);
for (i=0; i<msgs->peer.pendingReqsToClient; ++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->peer.pendingReqsToClient)
tr_removeElementFromArray (msgs->peerAskedFor, i, sizeof (struct peer_request),
msgs->peer.pendingReqsToClient--);
break;
}
case BT_PIECE:
assert (0); /* handled elsewhere! */
break;
case BT_PORT:
dbgmsg (msgs, "Got a BT_PORT");
tr_peerIoReadUint16 (msgs->io, inbuf, &msgs->dht_port);
if (msgs->dht_port > 0)
tr_dhtAddNode (getSession (msgs),
tr_peerAddress (&msgs->peer),
msgs->dht_port, 0);
break;
case BT_FEXT_SUGGEST:
dbgmsg (msgs, "Got a BT_FEXT_SUGGEST");
tr_peerIoReadUint32 (msgs->io, inbuf, &ui32);
if (fext)
fireClientGotSuggest (msgs, ui32);
else {
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
break;
case BT_FEXT_ALLOWED_FAST:
dbgmsg (msgs, "Got a BT_FEXT_ALLOWED_FAST");
tr_peerIoReadUint32 (msgs->io, inbuf, &ui32);
if (fext)
fireClientGotAllowedFast (msgs, ui32);
else {
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
break;
case BT_FEXT_HAVE_ALL:
dbgmsg (msgs, "Got a BT_FEXT_HAVE_ALL");
if (fext) {
tr_bitfieldSetHasAll (&msgs->peer.have);
assert (tr_bitfieldHasAll (&msgs->peer.have));
fireClientGotHaveAll (msgs);
updatePeerProgress (msgs);
} else {
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
break;
case BT_FEXT_HAVE_NONE:
dbgmsg (msgs, "Got a BT_FEXT_HAVE_NONE");
if (fext) {
tr_bitfieldSetHasNone (&msgs->peer.have);
fireClientGotHaveNone (msgs);
updatePeerProgress (msgs);
} else {
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
break;
case BT_FEXT_REJECT:
{
struct peer_request r;
dbgmsg (msgs, "Got a BT_FEXT_REJECT");
tr_peerIoReadUint32 (msgs->io, inbuf, &r.index);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.offset);
tr_peerIoReadUint32 (msgs->io, inbuf, &r.length);
if (fext)
fireGotRej (msgs, &r);
else {
fireError (msgs, EMSGSIZE);
return READ_ERR;
}
break;
}
case BT_LTEP:
dbgmsg (msgs, "Got a BT_LTEP");
parseLtep (msgs, msglen, inbuf);
break;
default:
dbgmsg (msgs, "peer sent us an UNKNOWN: %d", (int)id);
tr_peerIoDrain (msgs->io, inbuf, msglen);
break;
}
assert (msglen + 1 == msgs->incoming.length);
assert (evbuffer_get_length (inbuf) == startBufLen - msglen);
msgs->state = AWAITING_BT_LENGTH;
return READ_NOW;
}
/* returns 0 on success, or an errno on failure */
static int
clientGotBlock (tr_peerMsgs * msgs,
struct evbuffer * data,
const struct peer_request * req)
{
int err;
tr_torrent * tor = msgs->torrent;
const tr_block_index_t block = _tr_block (tor, req->index, req->offset);
assert (msgs);
assert (req);
if (!requestIsValid (msgs, req)) {
dbgmsg (msgs, "dropping invalid block %u:%u->%u",
req->index, req->offset, req->length);
return EBADMSG;
}
if (req->length != tr_torBlockCountBytes (msgs->torrent, block)) {
dbgmsg (msgs, "wrong block size -- expected %u, got %d",
tr_torBlockCountBytes (msgs->torrent, block), req->length);
return EMSGSIZE;
}
dbgmsg (msgs, "got block %u:%u->%u", req->index, req->offset, req->length);
if (!tr_peerMgrDidPeerRequest (msgs->torrent, &msgs->peer, block)) {
dbgmsg (msgs, "we didn't ask for this message...");
return 0;
}
if (tr_torrentPieceIsComplete (msgs->torrent, req->index)) {
dbgmsg (msgs, "we did ask for this message, but the piece is already complete...");
return 0;
}
/**
*** Save the block
**/
if ((err = tr_cacheWriteBlock (getSession (msgs)->cache, tor, req->index, req->offset, req->length, data)))
return err;
tr_bitfieldAdd (&msgs->peer.blame, req->index);
fireGotBlock (msgs, req);
return 0;
}
static int peerPulse (void * vmsgs);
static void
didWrite (tr_peerIo * io UNUSED, size_t bytesWritten, bool wasPieceData, void * vmsgs)
{
tr_peerMsgs * msgs = vmsgs;
if (wasPieceData)
firePeerGotPieceData (msgs, bytesWritten);
if (tr_isPeerIo (io) && io->userData)
peerPulse (msgs);
}
static ReadState
canRead (tr_peerIo * io, void * vmsgs, size_t * piece)
{
ReadState ret;
tr_peerMsgs * msgs = vmsgs;
struct evbuffer * in = tr_peerIoGetReadBuffer (io);
const size_t inlen = evbuffer_get_length (in);
dbgmsg (msgs, "canRead: inlen is %"TR_PRIuSIZE", msgs->state is %d", inlen, msgs->state);
if (!inlen)
{
ret = READ_LATER;
}
else if (msgs->state == AWAITING_BT_PIECE)
{
ret = readBtPiece (msgs, in, inlen, piece);
}
else switch (msgs->state)
{
case AWAITING_BT_LENGTH:
ret = readBtLength (msgs, in, inlen); break;
case AWAITING_BT_ID:
ret = readBtId (msgs, in, inlen); break;
case AWAITING_BT_MESSAGE:
ret = readBtMessage (msgs, in, inlen); break;
default:
ret = READ_ERR;
assert (0);
}
dbgmsg (msgs, "canRead: ret is %d", (int)ret);
return ret;
}
int
tr_peerMsgsIsReadingBlock (const tr_peerMsgs * msgs, tr_block_index_t block)
{
if (msgs->state != AWAITING_BT_PIECE)
return false;
return block == _tr_block (msgs->torrent,
msgs->incoming.blockReq.index,
msgs->incoming.blockReq.offset);
}
/**
***
**/
static void
updateDesiredRequestCount (tr_peerMsgs * msgs)
{
tr_torrent * const torrent = msgs->torrent;
/* there are lots of reasons we might not want to request any blocks... */
if (tr_torrentIsSeed (torrent) || !tr_torrentHasMetadata (torrent)
|| msgs->client_is_choked
|| !msgs->client_is_interested)
{
msgs->desiredRequestCount = 0;
}
else
{
int estimatedBlocksInPeriod;
unsigned int rate_Bps;
unsigned int irate_Bps;
const int floor = 4;
const int seconds = REQUEST_BUF_SECS;
const uint64_t now = tr_time_msec ();
/* Get the rate limit we should use.
* FIXME: this needs to consider all the other peers as well... */
rate_Bps = tr_peerGetPieceSpeed_Bps (&msgs->peer, now, TR_PEER_TO_CLIENT);
if (tr_torrentUsesSpeedLimit (torrent, TR_PEER_TO_CLIENT))
rate_Bps = MIN (rate_Bps, tr_torrentGetSpeedLimit_Bps (torrent, TR_PEER_TO_CLIENT));
/* honor the session limits, if enabled */
if (tr_torrentUsesSessionLimits (torrent) &&
tr_sessionGetActiveSpeedLimit_Bps (torrent->session, TR_PEER_TO_CLIENT, &irate_Bps))
rate_Bps = MIN (rate_Bps, irate_Bps);
/* use this desired rate to figure out how
* many requests we should send to this peer */
estimatedBlocksInPeriod = (rate_Bps * seconds) / torrent->blockSize;
msgs->desiredRequestCount = MAX (floor, estimatedBlocksInPeriod);
/* honor the peer's maximum request count, if specified */
if (msgs->reqq > 0)
if (msgs->desiredRequestCount > msgs->reqq)
msgs->desiredRequestCount = msgs->reqq;
}
}
static void
updateMetadataRequests (tr_peerMsgs * msgs, time_t now)
{
int piece;
if (msgs->peerSupportsMetadataXfer
&& tr_torrentGetNextMetadataRequest (msgs->torrent, now, &piece))
{
tr_variant tmp;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
/* build the data message */
tr_variantInitDict (&tmp, 3);
tr_variantDictAddInt (&tmp, TR_KEY_msg_type, METADATA_MSG_TYPE_REQUEST);
tr_variantDictAddInt (&tmp, TR_KEY_piece, piece);
payload = tr_variantToBuf (&tmp, TR_VARIANT_FMT_BENC);
dbgmsg (msgs, "requesting metadata piece #%d", piece);
/* write it out as a LTEP message to our outMessages buffer */
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload));
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, msgs->ut_metadata_id);
evbuffer_add_buffer (out, payload);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
/* cleanup */
evbuffer_free (payload);
tr_variantFree (&tmp);
}
}
static void
updateBlockRequests (tr_peerMsgs * msgs)
{
if (tr_torrentIsPieceTransferAllowed (msgs->torrent, TR_PEER_TO_CLIENT)
&& (msgs->desiredRequestCount > 0)
&& (msgs->peer.pendingReqsToPeer <= (msgs->desiredRequestCount * 0.66)))
{
int i;
int n;
tr_block_index_t * blocks;
const int numwant = msgs->desiredRequestCount - msgs->peer.pendingReqsToPeer;
assert (tr_peerMsgsIsClientInterested (msgs));
assert (!tr_peerMsgsIsClientChoked (msgs));
blocks = tr_new (tr_block_index_t, numwant);
tr_peerMgrGetNextRequests (msgs->torrent, &msgs->peer, numwant, blocks, &n, false);
for (i=0; i<n; ++i)
{
struct peer_request req;
blockToReq (msgs->torrent, blocks[i], &req);
protocolSendRequest (msgs, &req);
}
tr_free (blocks);
}
}
static size_t
fillOutputBuffer (tr_peerMsgs * msgs, time_t now)
{
int piece;
size_t bytesWritten = 0;
struct peer_request req;
const bool haveMessages = evbuffer_get_length (msgs->outMessages) != 0;
const bool fext = tr_peerIoSupportsFEXT (msgs->io);
/**
*** Protocol messages
**/
if (haveMessages && !msgs->outMessagesBatchedAt) /* fresh batch */
{
dbgmsg (msgs, "started an outMessages batch (length is %"TR_PRIuSIZE")", evbuffer_get_length (msgs->outMessages));
msgs->outMessagesBatchedAt = now;
}
else if (haveMessages && ((now - msgs->outMessagesBatchedAt) >= msgs->outMessagesBatchPeriod))
{
const size_t len = evbuffer_get_length (msgs->outMessages);
/* flush the protocol messages */
dbgmsg (msgs, "flushing outMessages... to %p (length is %"TR_PRIuSIZE")", (void*)msgs->io, len);
tr_peerIoWriteBuf (msgs->io, msgs->outMessages, false);
msgs->clientSentAnythingAt = now;
msgs->outMessagesBatchedAt = 0;
msgs->outMessagesBatchPeriod = LOW_PRIORITY_INTERVAL_SECS;
bytesWritten += len;
}
/**
*** Metadata Pieces
**/
if ((tr_peerIoGetWriteBufferSpace (msgs->io, now) >= METADATA_PIECE_SIZE)
&& popNextMetadataRequest (msgs, &piece))
{
char * data;
int dataLen;
bool ok = false;
data = tr_torrentGetMetadataPiece (msgs->torrent, piece, &dataLen);
if ((dataLen > 0) && (data != NULL))
{
tr_variant tmp;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
/* build the data message */
tr_variantInitDict (&tmp, 3);
tr_variantDictAddInt (&tmp, TR_KEY_msg_type, METADATA_MSG_TYPE_DATA);
tr_variantDictAddInt (&tmp, TR_KEY_piece, piece);
tr_variantDictAddInt (&tmp, TR_KEY_total_size, msgs->torrent->infoDictLength);
payload = tr_variantToBuf (&tmp, TR_VARIANT_FMT_BENC);
/* write it out as a LTEP message to our outMessages buffer */
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload) + dataLen);
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, msgs->ut_metadata_id);
evbuffer_add_buffer (out, payload);
evbuffer_add (out, data, dataLen);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
evbuffer_free (payload);
tr_variantFree (&tmp);
tr_free (data);
ok = true;
}
if (!ok) /* send a rejection message */
{
tr_variant tmp;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
/* build the rejection message */
tr_variantInitDict (&tmp, 2);
tr_variantDictAddInt (&tmp, TR_KEY_msg_type, METADATA_MSG_TYPE_REJECT);
tr_variantDictAddInt (&tmp, TR_KEY_piece, piece);
payload = tr_variantToBuf (&tmp, TR_VARIANT_FMT_BENC);
/* write it out as a LTEP message to our outMessages buffer */
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload));
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, msgs->ut_metadata_id);
evbuffer_add_buffer (out, payload);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgOutMessageLen (msgs);
evbuffer_free (payload);
tr_variantFree (&tmp);
}
}
/**
*** Data Blocks
**/
if ((tr_peerIoGetWriteBufferSpace (msgs->io, now) >= msgs->torrent->blockSize)
&& popNextRequest (msgs, &req))
{
--msgs->prefetchCount;
if (requestIsValid (msgs, &req)
&& tr_torrentPieceIsComplete (msgs->torrent, req.index))
{
int err;
const uint32_t msglen = 4 + 1 + 4 + 4 + req.length;
struct evbuffer * out;
struct evbuffer_iovec iovec[1];
out = evbuffer_new ();
evbuffer_expand (out, msglen);
evbuffer_add_uint32 (out, sizeof (uint8_t) + 2 * sizeof (uint32_t) + req.length);
evbuffer_add_uint8 (out, BT_PIECE);
evbuffer_add_uint32 (out, req.index);
evbuffer_add_uint32 (out, req.offset);
evbuffer_reserve_space (out, req.length, iovec, 1);
err = tr_cacheReadBlock (getSession (msgs)->cache, msgs->torrent, req.index, req.offset, req.length, iovec[0].iov_base);
iovec[0].iov_len = req.length;
evbuffer_commit_space (out, iovec, 1);
/* check the piece if it needs checking... */
if (!err && tr_torrentPieceNeedsCheck (msgs->torrent, req.index))
if ((err = !tr_torrentCheckPiece (msgs->torrent, req.index)))
tr_torrentSetLocalError (msgs->torrent, _("Please Verify Local Data! Piece #%"TR_PRIuSIZE" is corrupt."), (size_t)req.index);
if (err)
{
if (fext)
protocolSendReject (msgs, &req);
}
else
{
const size_t n = evbuffer_get_length (out);
dbgmsg (msgs, "sending block %u:%u->%u", req.index, req.offset, req.length);
assert (n == msglen);
tr_peerIoWriteBuf (msgs->io, out, true);
bytesWritten += n;
msgs->clientSentAnythingAt = now;
tr_historyAdd (&msgs->peer.blocksSentToPeer, tr_time (), 1);
}
evbuffer_free (out);
if (err)
{
bytesWritten = 0;
msgs = NULL;
}
}
else if (fext) /* peer needs a reject message */
{
protocolSendReject (msgs, &req);
}
if (msgs != NULL)
prefetchPieces (msgs);
}
/**
*** Keepalive
**/
if ((msgs != NULL)
&& (msgs->clientSentAnythingAt != 0)
&& ((now - msgs->clientSentAnythingAt) > KEEPALIVE_INTERVAL_SECS))
{
dbgmsg (msgs, "sending a keepalive message");
evbuffer_add_uint32 (msgs->outMessages, 0);
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
}
return bytesWritten;
}
static int
peerPulse (void * vmsgs)
{
tr_peerMsgs * msgs = vmsgs;
const time_t now = tr_time ();
if (tr_isPeerIo (msgs->io)) {
updateDesiredRequestCount (msgs);
updateBlockRequests (msgs);
updateMetadataRequests (msgs, now);
}
for (;;)
if (fillOutputBuffer (msgs, now) < 1)
break;
return true; /* loop forever */
}
void
tr_peerMsgsPulse (tr_peerMsgs * msgs)
{
if (msgs != NULL)
peerPulse (msgs);
}
static void
gotError (tr_peerIo * io UNUSED, short what, void * vmsgs)
{
if (what & BEV_EVENT_TIMEOUT)
dbgmsg (vmsgs, "libevent got a timeout, what=%hd", what);
if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
dbgmsg (vmsgs, "libevent got an error! what=%hd, errno=%d (%s)",
what, errno, tr_strerror (errno));
fireError (vmsgs, ENOTCONN);
}
static void
sendBitfield (tr_peerMsgs * msgs)
{
void * bytes;
size_t byte_count = 0;
struct evbuffer * out = msgs->outMessages;
assert (tr_torrentHasMetadata (msgs->torrent));
bytes = tr_torrentCreatePieceBitfield (msgs->torrent, &byte_count);
evbuffer_add_uint32 (out, sizeof (uint8_t) + byte_count);
evbuffer_add_uint8 (out, BT_BITFIELD);
evbuffer_add (out, bytes, byte_count);
dbgmsg (msgs, "sending bitfield... outMessage size is now %"TR_PRIuSIZE, evbuffer_get_length (out));
pokeBatchPeriod (msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
tr_free (bytes);
}
static void
tellPeerWhatWeHave (tr_peerMsgs * msgs)
{
const bool fext = tr_peerIoSupportsFEXT (msgs->io);
if (fext && tr_torrentHasAll (msgs->torrent))
{
protocolSendHaveAll (msgs);
}
else if (fext && tr_torrentHasNone (msgs->torrent))
{
protocolSendHaveNone (msgs);
}
else if (!tr_torrentHasNone (msgs->torrent))
{
sendBitfield (msgs);
}
}
/**
***
**/
/* some peers give us error messages if we send
more than this many peers in a single pex message
http://wiki.theory.org/BitTorrentPeerExchangeConventions */
#define MAX_PEX_ADDED 50
#define MAX_PEX_DROPPED 50
typedef struct
{
tr_pex * added;
tr_pex * dropped;
tr_pex * elements;
int addedCount;
int droppedCount;
int elementCount;
}
PexDiffs;
static void
pexAddedCb (void * vpex, void * userData)
{
PexDiffs * diffs = userData;
tr_pex * pex = vpex;
if (diffs->addedCount < MAX_PEX_ADDED)
{
diffs->added[diffs->addedCount++] = *pex;
diffs->elements[diffs->elementCount++] = *pex;
}
}
static inline void
pexDroppedCb (void * vpex, void * userData)
{
PexDiffs * diffs = userData;
tr_pex * pex = vpex;
if (diffs->droppedCount < MAX_PEX_DROPPED)
{
diffs->dropped[diffs->droppedCount++] = *pex;
}
}
static inline void
pexElementCb (void * vpex, void * userData)
{
PexDiffs * diffs = userData;
tr_pex * pex = vpex;
diffs->elements[diffs->elementCount++] = *pex;
}
typedef void (tr_set_func)(void * element, void * userData);
/**
* @brief find the differences and commonalities in two sorted sets
* @param a the first set
* @param aCount the number of elements in the set 'a'
* @param b the second set
* @param bCount the number of elements in the set 'b'
* @param compare the sorting method for both sets
* @param elementSize the sizeof the element in the two sorted sets
* @param in_a called for items in set 'a' but not set 'b'
* @param in_b called for items in set 'b' but not set 'a'
* @param in_both called for items that are in both sets
* @param userData user data passed along to in_a, in_b, and in_both
*/
static void
tr_set_compare (const void * va, size_t aCount,
const void * vb, size_t bCount,
int compare (const void * a, const void * b),
size_t elementSize,
tr_set_func in_a_cb,
tr_set_func in_b_cb,
tr_set_func in_both_cb,
void * userData)
{
const uint8_t * a = va;
const uint8_t * b = vb;
const uint8_t * aend = a + elementSize * aCount;
const uint8_t * bend = b + elementSize * bCount;
while (a != aend || b != bend)
{
if (a == aend)
{
(*in_b_cb)((void*)b, userData);
b += elementSize;
}
else if (b == bend)
{
(*in_a_cb)((void*)a, userData);
a += elementSize;
}
else
{
const int val = (*compare)(a, b);
if (!val)
{
(*in_both_cb)((void*)a, userData);
a += elementSize;
b += elementSize;
}
else if (val < 0)
{
(*in_a_cb)((void*)a, userData);
a += elementSize;
}
else if (val > 0)
{
(*in_b_cb)((void*)b, userData);
b += elementSize;
}
}
}
}
static void
sendPex (tr_peerMsgs * msgs)
{
if (msgs->peerSupportsPex && tr_torrentAllowsPex (msgs->torrent))
{
PexDiffs diffs;
PexDiffs diffs6;
tr_pex * newPex = NULL;
tr_pex * newPex6 = NULL;
const int newCount = tr_peerMgrGetPeers (msgs->torrent, &newPex, TR_AF_INET, TR_PEERS_CONNECTED, MAX_PEX_PEER_COUNT);
const int newCount6 = tr_peerMgrGetPeers (msgs->torrent, &newPex6, TR_AF_INET6, TR_PEERS_CONNECTED, MAX_PEX_PEER_COUNT);
/* build the diffs */
diffs.added = tr_new (tr_pex, newCount);
diffs.addedCount = 0;
diffs.dropped = tr_new (tr_pex, msgs->pexCount);
diffs.droppedCount = 0;
diffs.elements = tr_new (tr_pex, newCount + msgs->pexCount);
diffs.elementCount = 0;
tr_set_compare (msgs->pex, msgs->pexCount,
newPex, newCount,
tr_pexCompare, sizeof (tr_pex),
pexDroppedCb, pexAddedCb, pexElementCb, &diffs);
diffs6.added = tr_new (tr_pex, newCount6);
diffs6.addedCount = 0;
diffs6.dropped = tr_new (tr_pex, msgs->pexCount6);
diffs6.droppedCount = 0;
diffs6.elements = tr_new (tr_pex, newCount6 + msgs->pexCount6);
diffs6.elementCount = 0;
tr_set_compare (msgs->pex6, msgs->pexCount6,
newPex6, newCount6,
tr_pexCompare, sizeof (tr_pex),
pexDroppedCb, pexAddedCb, pexElementCb, &diffs6);
dbgmsg (
msgs,
"pex: old peer count %d+%d, new peer count %d+%d, "
"added %d+%d, removed %d+%d",
msgs->pexCount, msgs->pexCount6, newCount, newCount6,
diffs.addedCount, diffs6.addedCount,
diffs.droppedCount, diffs6.droppedCount);
if (!diffs.addedCount && !diffs.droppedCount && !diffs6.addedCount &&
!diffs6.droppedCount)
{
tr_free (diffs.elements);
tr_free (diffs6.elements);
}
else
{
int i;
tr_variant val;
uint8_t * tmp, *walk;
struct evbuffer * payload;
struct evbuffer * out = msgs->outMessages;
/* update peer */
tr_free (msgs->pex);
msgs->pex = diffs.elements;
msgs->pexCount = diffs.elementCount;
tr_free (msgs->pex6);
msgs->pex6 = diffs6.elements;
msgs->pexCount6 = diffs6.elementCount;
/* build the pex payload */
tr_variantInitDict (&val, 3); /* ipv6 support: left as 3:
* speed vs. likelihood? */
if (diffs.addedCount > 0)
{
/* "added" */
tmp = walk = tr_new (uint8_t, diffs.addedCount * 6);
for (i = 0; i < diffs.addedCount; ++i) {
memcpy (walk, &diffs.added[i].addr.addr, 4); walk += 4;
memcpy (walk, &diffs.added[i].port, 2); walk += 2;
}
assert ((walk - tmp) == diffs.addedCount * 6);
tr_variantDictAddRaw (&val, TR_KEY_added, tmp, walk - tmp);
tr_free (tmp);
/* "added.f"
* unset each holepunch flag because we don't support it. */
tmp = walk = tr_new (uint8_t, diffs.addedCount);
for (i = 0; i < diffs.addedCount; ++i)
*walk++ = diffs.added[i].flags & ~ADDED_F_HOLEPUNCH;
assert ((walk - tmp) == diffs.addedCount);
tr_variantDictAddRaw (&val, TR_KEY_added_f, tmp, walk - tmp);
tr_free (tmp);
}
if (diffs.droppedCount > 0)
{
/* "dropped" */
tmp = walk = tr_new (uint8_t, diffs.droppedCount * 6);
for (i = 0; i < diffs.droppedCount; ++i) {
memcpy (walk, &diffs.dropped[i].addr.addr, 4); walk += 4;
memcpy (walk, &diffs.dropped[i].port, 2); walk += 2;
}
assert ((walk - tmp) == diffs.droppedCount * 6);
tr_variantDictAddRaw (&val, TR_KEY_dropped, tmp, walk - tmp);
tr_free (tmp);
}
if (diffs6.addedCount > 0)
{
/* "added6" */
tmp = walk = tr_new (uint8_t, diffs6.addedCount * 18);
for (i = 0; i < diffs6.addedCount; ++i) {
memcpy (walk, &diffs6.added[i].addr.addr.addr6.s6_addr, 16);
walk += 16;
memcpy (walk, &diffs6.added[i].port, 2);
walk += 2;
}
assert ((walk - tmp) == diffs6.addedCount * 18);
tr_variantDictAddRaw (&val, TR_KEY_added6, tmp, walk - tmp);
tr_free (tmp);
/* "added6.f"
* unset each holepunch flag because we don't support it. */
tmp = walk = tr_new (uint8_t, diffs6.addedCount);
for (i = 0; i < diffs6.addedCount; ++i)
*walk++ = diffs6.added[i].flags & ~ADDED_F_HOLEPUNCH;
assert ((walk - tmp) == diffs6.addedCount);
tr_variantDictAddRaw (&val, TR_KEY_added6_f, tmp, walk - tmp);
tr_free (tmp);
}
if (diffs6.droppedCount > 0)
{
/* "dropped6" */
tmp = walk = tr_new (uint8_t, diffs6.droppedCount * 18);
for (i = 0; i < diffs6.droppedCount; ++i) {
memcpy (walk, &diffs6.dropped[i].addr.addr.addr6.s6_addr, 16);
walk += 16;
memcpy (walk, &diffs6.dropped[i].port, 2);
walk += 2;
}
assert ((walk - tmp) == diffs6.droppedCount * 18);
tr_variantDictAddRaw (&val, TR_KEY_dropped6, tmp, walk - tmp);
tr_free (tmp);
}
/* write the pex message */
payload = tr_variantToBuf (&val, TR_VARIANT_FMT_BENC);
evbuffer_add_uint32 (out, 2 * sizeof (uint8_t) + evbuffer_get_length (payload));
evbuffer_add_uint8 (out, BT_LTEP);
evbuffer_add_uint8 (out, msgs->ut_pex_id);
evbuffer_add_buffer (out, payload);
pokeBatchPeriod (msgs, HIGH_PRIORITY_INTERVAL_SECS);
dbgmsg (msgs, "sending a pex message; outMessage size is now %"TR_PRIuSIZE, evbuffer_get_length (out));
dbgOutMessageLen (msgs);
evbuffer_free (payload);
tr_variantFree (&val);
}
/* cleanup */
tr_free (diffs.added);
tr_free (diffs.dropped);
tr_free (newPex);
tr_free (diffs6.added);
tr_free (diffs6.dropped);
tr_free (newPex6);
/*msgs->clientSentPexAt = tr_time ();*/
}
}
static void
pexPulse (evutil_socket_t foo UNUSED, short bar UNUSED, void * vmsgs)
{
struct tr_peerMsgs * msgs = vmsgs;
sendPex (msgs);
assert (msgs->pexTimer != NULL);
tr_timerAdd (msgs->pexTimer, PEX_INTERVAL_SECS, 0);
}
/***
**** tr_peer virtual functions
***/
static bool
peermsgs_is_transferring_pieces (const struct tr_peer * peer,
uint64_t now,
tr_direction direction,
unsigned int * setme_Bps)
{
unsigned int Bps = 0;
if (tr_isPeerMsgs (peer))
{
const tr_peerMsgs * msgs = (const tr_peerMsgs *) peer;
Bps = tr_peerIoGetPieceSpeed_Bps (msgs->io, now, direction);
}
if (setme_Bps != NULL)
*setme_Bps = Bps;
return Bps > 0;
}
static void
peermsgs_destruct (tr_peer * peer)
{
tr_peerMsgs * msgs = PEER_MSGS (peer);
assert (msgs != NULL);
tr_peerMsgsSetActive (msgs, TR_UP, false);
tr_peerMsgsSetActive (msgs, TR_DOWN, false);
if (msgs->pexTimer != NULL)
event_free (msgs->pexTimer);
if (msgs->incoming.block != NULL)
evbuffer_free (msgs->incoming.block);
if (msgs->io)
{
tr_peerIoClear (msgs->io);
tr_peerIoUnref (msgs->io); /* balanced by the ref in handshakeDoneCB () */
}
evbuffer_free (msgs->outMessages);
tr_free (msgs->pex6);
tr_free (msgs->pex);
tr_peerDestruct (&msgs->peer);
memset (msgs, ~0, sizeof (tr_peerMsgs));
}
static const struct tr_peer_virtual_funcs my_funcs =
{
.destruct = peermsgs_destruct,
.is_transferring_pieces = peermsgs_is_transferring_pieces
};
/***
****
***/
time_t
tr_peerMsgsGetConnectionAge (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return tr_peerIoGetAge (msgs->io);
}
bool
tr_peerMsgsIsPeerChoked (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return msgs->peer_is_choked;
}
bool
tr_peerMsgsIsPeerInterested (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return msgs->peer_is_interested;
}
bool
tr_peerMsgsIsClientChoked (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return msgs->client_is_choked;
}
bool
tr_peerMsgsIsClientInterested (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return msgs->client_is_interested;
}
bool
tr_peerMsgsIsUtpConnection (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return msgs->io->utp_socket != NULL;
}
bool
tr_peerMsgsIsEncrypted (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return tr_peerIoIsEncrypted (msgs->io);
}
bool
tr_peerMsgsIsIncomingConnection (const tr_peerMsgs * msgs)
{
assert (tr_isPeerMsgs (msgs));
return tr_peerIoIsIncoming (msgs->io);
}
/***
****
***/
bool
tr_isPeerMsgs (const void * msgs)
{
/* FIXME: this is pretty crude */
return (msgs != NULL)
&& (((struct tr_peerMsgs*)msgs)->magic_number == MAGIC_NUMBER);
}
tr_peerMsgs *
tr_peerMsgsCast (void * vm)
{
return tr_isPeerMsgs(vm) ? vm : NULL;
}
tr_peerMsgs *
tr_peerMsgsNew (struct tr_torrent * torrent,
struct tr_peerIo * io,
tr_peer_callback callback,
void * callbackData)
{
tr_peerMsgs * m;
assert (io != NULL);
m = tr_new0 (tr_peerMsgs, 1);
tr_peerConstruct (&m->peer, torrent);
m->peer.funcs = &my_funcs;
m->magic_number = MAGIC_NUMBER;
m->client_is_choked = true;
m->peer_is_choked = true;
m->client_is_interested = false;
m->peer_is_interested = false;
m->is_active[TR_UP] = false;
m->is_active[TR_DOWN] = false;
m->callback = callback;
m->callbackData = callbackData;
m->io = io;
m->torrent = torrent;
m->state = AWAITING_BT_LENGTH;
m->outMessages = evbuffer_new ();
m->outMessagesBatchedAt = 0;
m->outMessagesBatchPeriod = LOW_PRIORITY_INTERVAL_SECS;
if (tr_torrentAllowsPex (torrent))
{
m->pexTimer = evtimer_new (torrent->session->event_base, pexPulse, m);
tr_timerAdd (m->pexTimer, PEX_INTERVAL_SECS, 0);
}
if (tr_peerIoSupportsUTP (m->io))
{
const tr_address * addr = tr_peerIoGetAddress (m->io, NULL);
tr_peerMgrSetUtpSupported (torrent, addr);
tr_peerMgrSetUtpFailed (torrent, addr, false);
}
if (tr_peerIoSupportsLTEP (m->io))
sendLtepHandshake (m);
tellPeerWhatWeHave (m);
if (tr_dhtEnabled (torrent->session) && tr_peerIoSupportsDHT (m->io))
{
/* Only send PORT over IPv6 when the IPv6 DHT is running (BEP-32). */
const struct tr_address *addr = tr_peerIoGetAddress (m->io, NULL);
if (addr->type == TR_AF_INET || tr_globalIPv6 ())
protocolSendPort (m, tr_dhtPort (torrent->session));
}
tr_peerIoSetIOFuncs (m->io, canRead, didWrite, gotError, m);
updateDesiredRequestCount (m);
return m;
}