diff --git a/index.js b/index.js index e761fa5..01f8b09 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,15 @@ -// TODO: add verification - var BitField = require('bitfield') var bncode = require('bncode') var EventEmitter = require('events').EventEmitter var inherits = require('inherits') +var Rusha = require('rusha-browserify') // Fast SHA1 (works in browser) var PIECE_LENGTH = 16 * 1024 +function sha1 (buf) { + return (new Rusha()).digestFromBuffer(buf) +} + module.exports = function (metadata) { inherits(ut_metadata, EventEmitter) @@ -27,13 +30,8 @@ module.exports = function (metadata) { } } - Object.defineProperty(ut_metadata.prototype, '_numPieces', { - get: function () { - return Math.ceil(this._metadataSize / PIECE_LENGTH) - } - }) - - ut_metadata.prototype.onHandshake = function () { + ut_metadata.prototype.onHandshake = function (infoHash, peerId, extensions) { + this._infoHash = infoHash } ut_metadata.prototype.onExtendedHandshake = function (handshake) { @@ -45,6 +43,9 @@ module.exports = function (metadata) { } this._metadataSize = handshake.metadata_size + this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH) + this._remainingRejects = this._numPieces * 2 + if (this._fetching) { this._requestPieces() } @@ -96,15 +97,6 @@ module.exports = function (metadata) { this._fetching = false } - ut_metadata.prototype._gotMetadata = function (_metadata) { - this.cancel() - this.metadata = _metadata - this._metadataComplete = true - this._metadataSize = this.metadata.length - this._wire.extendedHandshake.metadata_size = this._metadataSize - this.emit('metadata', this.metadata) - } - ut_metadata.prototype._send = function (dict, trailer) { var buf = bncode.encode(dict) if (Buffer.isBuffer(trailer)) { @@ -162,7 +154,6 @@ module.exports = function (metadata) { ut_metadata.prototype._requestPieces = function () { this.metadata = new Buffer(this._metadataSize) - this._remainingRejects = this._numPieces for (var piece = 0; piece < this._numPieces; piece++) { this._request(piece) @@ -179,9 +170,39 @@ module.exports = function (metadata) { } if (!done) return - // TODO: verify + // check hash + var info + try { + info = bncode.encode(bncode.decode(this.metadata).info) + } catch (err) { + // if buffer fails to decode, then data was corrupt + return this._failedMetadata() + } + if (sha1(info) === this._infoHash.toString('hex')) { + this._gotMetadata(this.metadata) + } else { + this._failedMetadata() + } - this._gotMetadata(this.metadata) + } + + ut_metadata.prototype._gotMetadata = function (_metadata) { + this.cancel() + this.metadata = _metadata + this._metadataComplete = true + this._metadataSize = this.metadata.length + this._wire.extendedHandshake.metadata_size = this._metadataSize + this.emit('metadata', this.metadata) + } + + ut_metadata.prototype._failedMetadata = function () { + this._bitfield = new BitField(0) // reset bitfield & try again + this._remainingRejects -= this._numPieces + if (this._remainingRejects > 0) { + this._requestPieces() + } else { + this.emit('warning', new Error('Peer sent invalid metadata')) + } } return ut_metadata diff --git a/package.json b/package.json index 1fb1683..fc74994 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "dependencies": { "bncode": "^0.5.0", "bitfield": "^0.2.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "rusha-browserify": "^0.7.3" }, "devDependencies": { "tape": "2.x", diff --git a/test/fetch.js b/test/fetch.js index 8965f24..b82e112 100644 --- a/test/fetch.js +++ b/test/fetch.js @@ -88,4 +88,34 @@ test('fetch() from peer without metadata', function (t) { }) wire1.handshake(parsedTorrent.infoHash, id1) -}) \ No newline at end of file +}) + +test('discard invalid metadata', function (t) { + t.plan(1) + + var wire1 = new Protocol() + var wire2 = new Protocol() + wire1.pipe(wire2).pipe(wire1) + + var invalidMetadata = metadata.slice(0) + invalidMetadata[0] = 99 // mess up the first byte of the metadata + + wire1.use(ut_metadata(invalidMetadata)) + wire2.use(ut_metadata()) + + wire2.ext('ut_metadata').fetch() + + wire2.ext('ut_metadata').on('metadata', function () { + t.fail('No "metadata" event should fire') + }) + + wire2.ext('ut_metadata').on('warning', function (err) { + t.pass('got warning because peer sent reject too much') + }) + + wire2.on('handshake', function (infoHash, peerId, extensions) { + wire2.handshake(parsedTorrent.infoHash, id2) + }) + + wire1.handshake(parsedTorrent.infoHash, id1) +})