diff --git a/client.js b/client.js index 82a6a14..67e5e71 100644 --- a/client.js +++ b/client.js @@ -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 diff --git a/lib/http-tracker.js b/lib/http-tracker.js index 48d033e..042ebdb 100644 --- a/lib/http-tracker.js +++ b/lib/http-tracker.js @@ -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) diff --git a/lib/parse_udp.js b/lib/parse_udp.js index d46ca4e..0499149 100644 --- a/lib/parse_udp.js +++ b/lib/parse_udp.js @@ -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') } diff --git a/lib/parse_websocket.js b/lib/parse_websocket.js index 88de032..6b16bbc 100644 --- a/lib/parse_websocket.js +++ b/lib/parse_websocket.js @@ -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 } diff --git a/lib/swarm.js b/lib/swarm.js index 67fdef7..0e8fb2f 100644 --- a/lib/swarm.js +++ b/lib/swarm.js @@ -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' - cb(null, { - complete: self.complete, - incomplete: self.incomplete, - peers: self._getPeers(params.numwant, peerType) - }) + 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, !!params.socket) + }) } 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 } diff --git a/lib/udp-tracker.js b/lib/udp-tracker.js index e12a79d..30d1e11 100644 --- a/lib/udp-tracker.js +++ b/lib/udp-tracker.js @@ -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) ])) } diff --git a/lib/websocket-tracker.js b/lib/websocket-tracker.js index b5233b4..b79d166 100644 --- a/lib/websocket-tracker.js +++ b/lib/websocket-tracker.js @@ -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) } } diff --git a/test/client.js b/test/client.js index d1bd887..2a0c47e 100644 --- a/test/client.js +++ b/test/client.js @@ -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') })