diff --git a/client.js b/client.js index 464e7be..af543af 100644 --- a/client.js +++ b/client.js @@ -36,7 +36,7 @@ function Client (peerId, port, torrent, opts) { // required self._peerId = Buffer.isBuffer(peerId) ? peerId - : new Buffer(peerId, 'utf8') + : new Buffer(peerId, 'hex') self._port = port self._infoHash = Buffer.isBuffer(torrent.infoHash) ? torrent.infoHash diff --git a/lib/common.js b/lib/common.js index 727e239..aadc705 100644 --- a/lib/common.js +++ b/lib/common.js @@ -15,7 +15,7 @@ exports.EVENT_IDS = { 1: 'completed', 2: 'started', 3: 'stopped' -}; +} function toUInt32 (n) { var buf = new Buffer(4) @@ -24,8 +24,12 @@ function toUInt32 (n) { } exports.toUInt32 = toUInt32 -exports.binaryToUtf8 = function (str) { - return new Buffer(str, 'binary').toString('utf8') +exports.binaryToHex = function (str) { + return new Buffer(str, 'binary').toString('hex') +} + +exports.hexToBinary = function (str) { + return new Buffer(str, 'hex').toString('binary') } /** diff --git a/lib/parse_http.js b/lib/parse_http.js index c6b67d7..9a4a95b 100644 --- a/lib/parse_http.js +++ b/lib/parse_http.js @@ -11,48 +11,44 @@ function parseHttpRequest (req, options) { if (s[0] === '/announce') { params.action = common.ACTIONS.ANNOUNCE - params.peer_id = typeof params.peer_id === 'string' && common.binaryToUtf8(params.peer_id) - params.port = Number(params.port) + if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) + throw new Error('invalid info_hash') + params.info_hash = common.binaryToHex(params.info_hash) + if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) + throw new Error('invalid peer_id') + params.peer_id = common.binaryToHex(params.peer_id) - if (typeof params.info_hash !== 'string') throw new Error('invalid info_hash') - if (params.info_hash.length !== 20) throw new Error('invalid info_hash length') - if (typeof params.peer_id !== 'string') throw new Error('invalid peer_id') - if (params.peer_id.length !== 20) throw new Error('invalid peer_id length') + params.port = Number(params.port) if (!params.port) throw new Error('invalid port') params.left = Number(params.left) params.compact = Number(params.compact) + params.numwant = Math.min( + Number(params.numwant) || common.NUM_ANNOUNCE_PEERS, + common.MAX_ANNOUNCE_PEERS + ) params.ip = options.trustProxy ? req.headers['x-forwarded-for'] || req.connection.remoteAddress : req.connection.remoteAddress.replace(REMOVE_IPV6_RE, '') // force ipv4 params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets? - params.numwant = Math.min( - Number(params.numwant) || common.NUM_ANNOUNCE_PEERS, - common.MAX_ANNOUNCE_PEERS - ) - - return params } else if (s[0] === '/scrape') { // unofficial scrape message params.action = common.ACTIONS.SCRAPE - - if (typeof params.info_hash === 'string') { + if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ] - } - if (params.info_hash) { - if (!Array.isArray(params.info_hash)) throw new Error('invalid info_hash array') - - params.info_hash.forEach(function (infoHash) { - if (infoHash.length !== 20) { + if (Array.isArray(params.info_hash)) { + params.info_hash = params.info_hash.map(function (binaryInfoHash) { + if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) throw new Error('invalid info_hash') - } + return common.binaryToHex(binaryInfoHash) }) } - return params } else { - return null + throw new Error('Invalid action in HTTP request: ' + params.action) } + + return params } diff --git a/lib/parse_udp.js b/lib/parse_udp.js index 2122d96..0e7a008 100644 --- a/lib/parse_udp.js +++ b/lib/parse_udp.js @@ -5,13 +5,11 @@ var ipLib = require('ip') var common = require('./common') function parseUdpRequest (msg, rinfo) { - if (msg.length < 16) { + if (msg.length < 16) throw new Error('received packet is too short') - } - if (rinfo.family !== 'IPv4') { + if (rinfo.family !== 'IPv4') throw new Error('udp tracker does not support IPv6') - } var params = { connectionId: msg.slice(0, 8), // 64-bit @@ -20,41 +18,46 @@ function parseUdpRequest (msg, rinfo) { } // TODO: randomize - if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) { + if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) throw new Error('received packet with invalid connection id') - } if (params.action === common.ACTIONS.CONNECT) { // No further params + } else if (params.action === common.ACTIONS.ANNOUNCE) { - params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes - params.peer_id = msg.slice(36, 56).toString('utf8') // 20 bytes + params.info_hash = msg.slice(16, 36).toString('hex') // 20 bytes + params.peer_id = msg.slice(36, 56).toString('hex') // 20 bytes params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this? params.left = fromUInt64(msg.slice(64, 72)) params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this? - params.event = msg.readUInt32BE(80) - params.event = common.EVENT_IDS[params.event] + + params.event = common.EVENT_IDS[msg.readUInt32BE(80)] if (!params.event) throw new Error('invalid event') // early return - params.ip = msg.readUInt32BE(84) // optional - params.ip = params.ip ? - ipLib.toString(params.ip) : - rinfo.address + + var ip = msg.readUInt32BE(84) // optional + params.ip = ip + ? ipLib.toString(ip) + : rinfo.address + params.key = msg.readUInt32BE(88) // TODO: what is this for? - params.numwant = msg.readUInt32BE(92) // optional + // never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than // 512 bytes which is not safe - params.numwant = Math.min(params.numwant || common.NUM_ANNOUNCE_PEERS, common.MAX_ANNOUNCE_PEERS) + params.numwant = Math.min( + msg.readUInt32BE(92) || common.NUM_ANNOUNCE_PEERS, // optional + common.MAX_ANNOUNCE_PEERS + ) + params.port = msg.readUInt16BE(96) || rinfo.port // optional params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets params.compact = 1 // udp is always compact } else if (params.action === common.ACTIONS.SCRAPE) { // scrape message - params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes - // TODO: support multiple info_hash scrape - if (msg.length > 36) { - throw new Error('multiple info_hash scrape not supported') - } + if (msg.length > 36) throw new Error('multiple info_hash scrape not supported') + + params.info_hash = [ msg.slice(16, 36).toString('hex') ] // 20 bytes + } else { throw new Error('Invalid action in UDP packet: ' + params.action) } diff --git a/server.js b/server.js index 5b31b93..a194f86 100644 --- a/server.js +++ b/server.js @@ -115,13 +115,11 @@ Server.prototype.close = function (cb) { } } -Server.prototype.getSwarm = function (binaryInfoHash) { +Server.prototype.getSwarm = function (infoHash) { var self = this - if (Buffer.isBuffer(binaryInfoHash)) binaryInfoHash = binaryInfoHash.toString('binary') - var swarm = self.torrents[binaryInfoHash] - if (!swarm) { - swarm = self.torrents[binaryInfoHash] = new Swarm(binaryInfoHash, this) - } + if (Buffer.isBuffer(infoHash)) infoHash = infoHash.toString('hex') + var swarm = self.torrents[infoHash] + if (!swarm) swarm = self.torrents[infoHash] = new Swarm(infoHash, this) return swarm } @@ -130,9 +128,7 @@ Server.prototype._onHttpRequest = function (req, res) { var params try { - params = parseHttpRequest(req, { - trustProxy: self._trustProxy - }) + params = parseHttpRequest(req, { trustProxy: self._trustProxy }) } catch (err) { debug('sent error %s', err.message) res.end(bencode.encode({ @@ -142,7 +138,7 @@ Server.prototype._onHttpRequest = function (req, res) { // even though it's an error for the client, it's just a warning for the server. // don't crash the server because a client sent bad data :) self.emit('warning', err) - + return } @@ -225,28 +221,12 @@ Server.prototype._onAnnounce = function (params, cb) { Server.prototype._onScrape = function (params, cb) { var self = this - if (typeof params.info_hash === 'string') { - params.info_hash = [ params.info_hash ] - } else if (params.info_hash == null) { + if (params.info_hash == null) { // if info_hash param is omitted, stats for all torrents are returned // TODO: make this configurable! params.info_hash = Object.keys(self.torrents) } - if (!Array.isArray(params.info_hash)) { - var err = new Error('invalid info_hash') - self.emit('warning', err) - return cb(err) - } - - var response = { - action: common.ACTIONS.SCRAPE, - files: {}, - flags: { - min_request_interval: self._intervalMs - } - } - series(params.info_hash.map(function (infoHash) { var swarm = self.getSwarm(infoHash) return function (cb) { @@ -261,8 +241,14 @@ Server.prototype._onScrape = function (params, cb) { }), function (err, results) { if (err) return cb(err) + var response = { + action: common.ACTIONS.SCRAPE, + files: {}, + flags: { min_request_interval: self._intervalMs } + } + results.forEach(function (result) { - response.files[result.infoHash] = { + response.files[common.hexToBinary(result.infoHash)] = { complete: result.complete, incomplete: result.incomplete, downloaded: result.complete // TODO: this only provides a lower-bound @@ -273,7 +259,6 @@ Server.prototype._onScrape = function (params, cb) { }) } - function makeUdpPacket (params) { switch (params.action) { case common.ACTIONS.CONNECT: diff --git a/test/server.js b/test/server.js index f6ddd8d..0dce512 100644 --- a/test/server.js +++ b/test/server.js @@ -2,9 +2,9 @@ var Client = require('../') var Server = require('../').Server var test = require('tape') -var infoHash = new Buffer('4cb67059ed6bd08362da625b3ae77f6f4a075705', 'hex') -var peerId = '01234567890123456789' -var peerId2 = '12345678901234567890' +var infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' +var peerId = new Buffer('01234567890123456789') +var peerId2 = new Buffer('12345678901234567890') var torrentLength = 50000 function serverTest (t, serverType) { @@ -52,7 +52,7 @@ function serverTest (t, serverType) { t.deepEqual(server.getSwarm(infoHash).peers['127.0.0.1:6881'], { ip: '127.0.0.1', port: 6881, - peerId: peerId + peerId: peerId.toString('hex') }) client.complete()