tracker should not modify opts object, it's passed to all trackers

This commit is contained in:
Feross Aboukhadijeh 2015-07-27 15:19:18 -07:00
parent 53c973b4ae
commit c3abef72ce
8 changed files with 85 additions and 74 deletions

View File

@ -23,7 +23,7 @@ inherits(Client, EventEmitter)
* @param {Number} port torrent client listening port
* @param {Object} torrent parsed torrent
* @param {Object} opts options object
* @param {Number} opts.numWant number of peers to request
* @param {Number} opts.numwant number of peers to request
* @param {Number} opts.interval announce interval (in ms)
* @param {Number} opts.rtcConfig RTCPeerConnection configuration object
* @param {Number} opts.wrtc custom webrtc implementation
@ -56,7 +56,7 @@ function Client (peerId, port, torrent, opts) {
self._wrtc = opts.wrtc
// optional
self._numWant = opts.numWant || common.DEFAULT_ANNOUNCE_PEERS
self._numwant = opts.numwant || common.DEFAULT_ANNOUNCE_PEERS
self._intervalMs = opts.interval || common.DEFAULT_ANNOUNCE_INTERVAL
debug('new client %s', self._infoHashHex)
@ -160,6 +160,7 @@ Client.prototype.start = function (opts) {
* @param {Object} opts
* @param {number=} opts.uploaded
* @param {number=} opts.downloaded
* @param {number=} opts.numwant
* @param {number=} opts.left (if not set, calculated automatically)
*/
Client.prototype.stop = function (opts) {
@ -175,6 +176,7 @@ Client.prototype.stop = function (opts) {
* @param {Object} opts
* @param {number=} opts.uploaded
* @param {number=} opts.downloaded
* @param {number=} opts.numwant
* @param {number=} opts.left (if not set, calculated automatically)
*/
Client.prototype.complete = function (opts) {
@ -194,6 +196,7 @@ Client.prototype.complete = function (opts) {
* @param {Object} opts
* @param {number=} opts.uploaded
* @param {number=} opts.downloaded
* @param {number=} opts.numwant
* @param {number=} opts.left (if not set, calculated automatically)
*/
Client.prototype.update = function (opts) {
@ -207,6 +210,7 @@ Client.prototype.update = function (opts) {
Client.prototype._announce = function (opts) {
var self = this
self._trackers.forEach(function (tracker) {
// tracker should not modify `opts` object, it's passed to all trackers
tracker.announce(opts)
})
}
@ -214,15 +218,13 @@ Client.prototype._announce = function (opts) {
/**
* Send a scrape request to the trackers.
* @param {Object} opts
* @param {number=} opts.uploaded
* @param {number=} opts.downloaded
* @param {number=} opts.left (if not set, calculated automatically)
*/
Client.prototype.scrape = function (opts) {
var self = this
debug('send `scrape`')
if (!opts) opts = {}
self._trackers.forEach(function (tracker) {
// tracker should not modify `opts` object, it's passed to all trackers
tracker.scrape(opts)
})
}
@ -258,7 +260,7 @@ Client.prototype._defaultAnnounceOpts = function (opts) {
var self = this
if (!opts) opts = {}
if (opts.numWant == null) opts.numWant = self._numWant
if (opts.numwant == null) opts.numwant = self._numwant
if (opts.uploaded == null) opts.uploaded = 0
if (opts.downloaded == null) opts.downloaded = 0

View File

@ -45,16 +45,20 @@ function HTTPTracker (client, announceUrl, opts) {
HTTPTracker.prototype.announce = function (opts) {
var self = this
if (self.destroyed) return
if (self._trackerId) opts.trackerid = self._trackerId
if (opts.compact == null) opts.compact = 1
if (opts.numwant == null) opts.numwant = self.client._numWant // spec says 'numwant'
var params = {
numwant: opts.numwant,
uploaded: opts.uploaded,
downloaded: opts.downloaded,
event: opts.event,
compact: (opts.compact == null) ? 1 : opts.compact,
info_hash: self.client._infoHashBinary,
peer_id: self.client._peerIdBinary,
port: self.client._port
}
if (self._trackerId) params.trackerid = self._trackerId
opts.info_hash = self.client._infoHashBinary
opts.peer_id = self.client._peerIdBinary
opts.port = self.client._port
self._request(self._announceUrl, opts, self._onAnnounceResponse.bind(self))
self._request(self._announceUrl, params, self._onAnnounceResponse.bind(self))
}
HTTPTracker.prototype.scrape = function (opts) {
@ -66,13 +70,15 @@ HTTPTracker.prototype.scrape = function (opts) {
return
}
opts.info_hash = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? opts.infoHash.map(function (infoHash) { return infoHash.toString('binary') })
var infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? opts.infoHash.map(function (infoHash) {
return infoHash.toString('binary')
})
: (opts.infoHash || self.client._infoHash).toString('binary')
if (opts.infoHash) delete opts.infoHash
self._request(self._scrapeUrl, opts, self._onScrapeResponse.bind(self))
var params = {
info_hash: infoHashes
}
self._request(self._scrapeUrl, params, self._onScrapeResponse.bind(self))
}
// TODO: Improve this interface
@ -95,12 +101,11 @@ HTTPTracker.prototype.destroy = function (cb) {
cb(null)
}
HTTPTracker.prototype._request = function (requestUrl, opts, cb) {
HTTPTracker.prototype._request = function (requestUrl, params, cb) {
var self = this
var u = requestUrl + (requestUrl.indexOf('?') === -1 ? '?' : '&') +
common.querystringStringify(opts)
common.querystringStringify(params)
get.concat(u, function (err, data, res) {
if (self.destroyed) return
if (err) return self.client.emit('warning', err)

View File

@ -13,7 +13,6 @@ function parseUdpRequest (msg, rinfo) {
transactionId: msg.readUInt32BE(12)
}
// TODO: randomize
if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) {
throw new Error('received packet with invalid connection id')
}

View File

@ -30,6 +30,7 @@ function parseWebSocketRequest (socket, params) {
Number(params.offers && params.offers.length) || 0, // no default - explicit only
common.MAX_ANNOUNCE_PEERS
)
params.compact = -1 // return full peer objects (used for websocket responses)
return params
}

View File

@ -15,20 +15,24 @@ Swarm.prototype.announce = function (params, cb) {
var self = this
var peer = self.peers[params.addr || params.peer_id]
// Dispatch announce event
var fn = '_onAnnounce_' + params.event
if (self[fn]) {
self[fn](params, peer) // process event
var peerType = params.compact === undefined ? 'webrtc' : 'addr'
if (params.event === 'started') {
self._onAnnounceStarted(params, peer)
} else if (params.event === 'stopped') {
self._onAnnounceStopped(params, peer)
} else if (params.event === 'completed') {
self._onAnnounceCompleted(params, peer)
} else if (params.event === 'update') {
self._onAnnounceUpdate(params, peer)
} else {
cb(new Error('invalid event'))
return
}
console.log('FEROSS', params.compact)
cb(null, {
complete: self.complete,
incomplete: self.incomplete,
peers: self._getPeers(params.numwant, peerType)
peers: self._getPeers(params.numwant, !!params.socket)
})
} else {
cb(new Error('invalid event'))
}
}
Swarm.prototype.scrape = function (params, cb) {
@ -38,7 +42,7 @@ Swarm.prototype.scrape = function (params, cb) {
})
}
Swarm.prototype._onAnnounce_started = function (params, peer) {
Swarm.prototype._onAnnounceStarted = function (params, peer) {
if (peer) {
debug('unexpected `started` event from peer that is already in swarm')
return this._onAnnounce_update(params, peer) // treat as an update
@ -48,14 +52,14 @@ Swarm.prototype._onAnnounce_started = function (params, peer) {
else this.incomplete += 1
peer = this.peers[params.addr || params.peer_id] = {
complete: params.left === 0,
ip: params.ip, // only http+udp
peerId: params.peer_id, // as hex
port: params.port, // only http+udp
ip: params.ip, // only http, udp
port: params.port, // only http, udp
socket: params.socket // only websocket
}
}
Swarm.prototype._onAnnounce_stopped = function (params, peer) {
Swarm.prototype._onAnnounceStopped = function (params, peer) {
if (!peer) {
debug('unexpected `stopped` event from peer that is not in swarm')
return // do nothing
@ -66,7 +70,7 @@ Swarm.prototype._onAnnounce_stopped = function (params, peer) {
this.peers[params.addr || params.peer_id] = null
}
Swarm.prototype._onAnnounce_completed = function (params, peer) {
Swarm.prototype._onAnnounceCompleted = function (params, peer) {
if (!peer) {
debug('unexpected `completed` event from peer that is not in swarm')
return this._onAnnounce_started(params, peer) // treat as a start
@ -81,7 +85,7 @@ Swarm.prototype._onAnnounce_completed = function (params, peer) {
peer.complete = true
}
Swarm.prototype._onAnnounce_update = function (params, peer) {
Swarm.prototype._onAnnounceUpdate = function (params, peer) {
if (!peer) {
debug('unexpected `update` event from peer that is not in swarm')
return this._onAnnounce_started(params, peer) // treat as a start
@ -94,16 +98,14 @@ Swarm.prototype._onAnnounce_update = function (params, peer) {
}
}
Swarm.prototype._getPeers = function (numWant, peerType) {
Swarm.prototype._getPeers = function (numwant, isWebRTC) {
var peers = []
var ite = randomIterate(Object.keys(this.peers))
while (true) {
var peerId = ite()
if (peers.length >= numWant || peerId == null) return peers
var peerId
while ((peerId = ite()) && peers.length < numwant) {
var peer = this.peers[peerId]
if (peer &&
((peerType === 'webrtc' && peer.socket) || (peerType === 'addr' && peer.ip))) {
peers.push(peer)
}
if (!peer) continue
if ((isWebRTC && peer.socket) || (!isWebRTC && peer.ip)) peers.push(peer)
}
return peers
}

View File

@ -208,7 +208,6 @@ UDPTracker.prototype._request = function (opts) {
}
function announce (connectionId, opts) {
opts = opts || {}
transactionId = genTransactionId()
send(Buffer.concat([
@ -217,14 +216,14 @@ UDPTracker.prototype._request = function (opts) {
transactionId,
self.client._infoHash,
self.client._peerId,
toUInt64(opts.downloaded || 0),
toUInt64(opts.downloaded),
opts.left != null ? toUInt64(opts.left) : new Buffer('FFFFFFFFFFFFFFFF', 'hex'),
toUInt64(opts.uploaded || 0),
common.toUInt32(common.EVENTS[opts.event] || 0),
toUInt64(opts.uploaded),
common.toUInt32(common.EVENTS[opts.event]),
common.toUInt32(0), // ip address (optional)
common.toUInt32(0), // key (optional)
common.toUInt32(opts.numWant || common.DEFAULT_ANNOUNCE_PEERS),
toUInt16(self.client._port || 0)
common.toUInt32(opts.numwant),
toUInt16(self.client._port)
]))
}

View File

@ -51,20 +51,23 @@ WebSocketTracker.prototype.announce = function (opts) {
return self._socket.once('connect', self.announce.bind(self, opts))
}
opts.info_hash = self.client._infoHashBinary
opts.peer_id = self.client._peerIdBinary
// Limit number of offers (temporarily)
// TODO: Limit number of offers (temporarily)
// TODO: remove this when we cleanup old RTCPeerConnections cleanly
if (opts.numWant > 10) opts.numWant = 10
var numwant = Math.min(opts.numwant, 10)
self._generateOffers(opts.numWant, function (offers) {
opts.offers = offers
if (self._trackerId) {
opts.trackerid = self._trackerId
self._generateOffers(numwant, function (offers) {
var params = {
numwant: numwant,
uploaded: opts.uploaded || 0,
downloaded: opts.downloaded,
event: opts.event,
info_hash: self.client._infoHashBinary,
peer_id: self.client._peerIdBinary,
offers: offers
}
self._send(opts)
if (self._trackerId) params.trackerid = self._trackerId
self._send(params)
})
}
@ -252,13 +255,13 @@ WebSocketTracker.prototype._send = function (params) {
self._socket.send(message)
}
WebSocketTracker.prototype._generateOffers = function (numWant, cb) {
WebSocketTracker.prototype._generateOffers = function (numwant, cb) {
var self = this
var offers = []
debug('generating %s offers', numWant)
debug('generating %s offers', numwant)
// TODO: cleanup dead peers and peers that never get a return offer, from self._peers
for (var i = 0; i < numWant; ++i) {
for (var i = 0; i < numwant; ++i) {
generateOffer()
}
@ -280,8 +283,8 @@ WebSocketTracker.prototype._generateOffers = function (numWant, cb) {
}
function checkDone () {
if (offers.length === numWant) {
debug('generated %s offers', numWant)
if (offers.length === numwant) {
debug('generated %s offers', numwant)
cb(offers)
}
}

View File

@ -190,7 +190,7 @@ function testClientAnnounceWithNumWant (t, serverType) {
})
client2.start()
client2.once('update', function () {
var client3 = new Client(peerId3, port + 2, parsedTorrent, { numWant: 1 })
var client3 = new Client(peerId3, port + 2, parsedTorrent, { numwant: 1 })
client3.on('error', function (err) {
t.error(err)
})
@ -228,10 +228,10 @@ function testClientAnnounceWithNumWant (t, serverType) {
})
}
test('http: client announce with numWant', function (t) {
test('http: client announce with numwant', function (t) {
testClientAnnounceWithNumWant(t, 'http')
})
test('udp: client announce with numWant', function (t) {
test('udp: client announce with numwant', function (t) {
testClientAnnounceWithNumWant(t, 'udp')
})