commit
						a9826a9612
					
				
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @ -30,15 +30,15 @@ This package should be used with [bittorrent-protocol](https://www.npmjs.com/pac | |||||||
| Say you're already using `bittorrent-protocol`. Your code might look something like this: | Say you're already using `bittorrent-protocol`. Your code might look something like this: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var Protocol = require('bittorrent-protocol') | const Protocol = require('bittorrent-protocol') | ||||||
| var net = require('net') | const net = require('net') | ||||||
| 
 | 
 | ||||||
| net.createServer(function (socket) { | net.createServer(socket => { | ||||||
|   var wire = new Protocol() |   var wire = new Protocol() | ||||||
|   socket.pipe(wire).pipe(socket) |   socket.pipe(wire).pipe(socket) | ||||||
| 
 | 
 | ||||||
|   // handle handshake |   // handle handshake | ||||||
|   wire.on('handshake', function (infoHash, peerId) { |   wire.on('handshake', (infoHash, peerId) => { | ||||||
|     wire.handshake(new Buffer('my info hash'), new Buffer('my peer id')) |     wire.handshake(new Buffer('my info hash'), new Buffer('my peer id')) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
| @ -48,12 +48,12 @@ net.createServer(function (socket) { | |||||||
| To add support for BEP 9, simply modify your code like this: | To add support for BEP 9, simply modify your code like this: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var Protocol = require('bittorrent-protocol') | const Protocol = require('bittorrent-protocol') | ||||||
| var net = require('net') | const net = require('net') | ||||||
| var ut_metadata = require('ut_metadata') | const ut_metadata = require('ut_metadata') | ||||||
| 
 | 
 | ||||||
| net.createServer(function (socket) { | net.createServer(socket => { | ||||||
|   var wire = new Protocol() |   const wire = new Protocol() | ||||||
|   socket.pipe(wire).pipe(socket) |   socket.pipe(wire).pipe(socket) | ||||||
| 
 | 
 | ||||||
|   // initialize the extension |   // initialize the extension | ||||||
| @ -65,7 +65,7 @@ net.createServer(function (socket) { | |||||||
|   wire.ut_metadata.fetch() |   wire.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|   // 'metadata' event will fire when the metadata arrives and is verified to be correct! |   // 'metadata' event will fire when the metadata arrives and is verified to be correct! | ||||||
|   wire.ut_metadata.on('metadata', function (metadata) { |   wire.ut_metadata.on('metadata', metadata => { | ||||||
|     // got metadata! |     // got metadata! | ||||||
| 
 | 
 | ||||||
|     // Note: the event will not fire if the peer does not support ut_metadata, if they |     // Note: the event will not fire if the peer does not support ut_metadata, if they | ||||||
| @ -75,12 +75,12 @@ net.createServer(function (socket) { | |||||||
| 
 | 
 | ||||||
|   // optionally, listen to the 'warning' event if you want to know that metadata is |   // optionally, listen to the 'warning' event if you want to know that metadata is | ||||||
|   // probably not going to arrive for one of the above reasons. |   // probably not going to arrive for one of the above reasons. | ||||||
|   wire.ut_metadata.on('warning', function (err) { |   wire.ut_metadata.on('warning', err => { | ||||||
|     console.log(err.message) |     console.log(err.message) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   // handle handshake |   // handle handshake | ||||||
|   wire.on('handshake', function (infoHash, peerId) { |   wire.on('handshake', (infoHash, peerId) => { | ||||||
|     wire.handshake(new Buffer('my info hash'), new Buffer('my peer id')) |     wire.handshake(new Buffer('my info hash'), new Buffer('my peer id')) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
| @ -95,7 +95,7 @@ Initialize the extension. If you have the torrent metadata (Buffer), pass it int | |||||||
| `ut_metadata` constructor so it's made available to the peer. | `ut_metadata` constructor so it's made available to the peer. | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var metadata = fs.readFileSync(__dirname + '/file.torrent') | const metadata = fs.readFileSync(__dirname + '/file.torrent') | ||||||
| wire.use(ut_metadata(metadata)) | wire.use(ut_metadata(metadata)) | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -119,7 +119,7 @@ Fired when metadata is available and verified to be correct. Called with a singl | |||||||
| parameter of type Buffer. | parameter of type Buffer. | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| wire.ut_metadata.on('metadata', function (metadata) { | wire.ut_metadata.on('metadata', metadata => { | ||||||
|   console.log(Buffer.isBuffer(metadata)) // true |   console.log(Buffer.isBuffer(metadata)) // true | ||||||
| }) | }) | ||||||
| ``` | ``` | ||||||
| @ -136,7 +136,7 @@ Fired if: | |||||||
|  - the peer repeatedly sent invalid data |  - the peer repeatedly sent invalid data | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| wire.ut_metadata.on('warning', function (err) { | wire.ut_metadata.on('warning', err => { | ||||||
|   console.log(err.message) |   console.log(err.message) | ||||||
| }) | }) | ||||||
| ``` | ``` | ||||||
|  | |||||||
							
								
								
									
										454
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										454
									
								
								index.js
									
									
									
									
									
								
							| @ -1,240 +1,244 @@ | |||||||
| var bencode = require('bencode') | const { EventEmitter } = require('events') | ||||||
| var BitField = require('bitfield') | const bencode = require('bencode') | ||||||
| var debug = require('debug')('ut_metadata') | const BitField = require('bitfield') | ||||||
| var EventEmitter = require('events').EventEmitter | const debug = require('debug')('ut_metadata') | ||||||
| var inherits = require('inherits') | const sha1 = require('simple-sha1') | ||||||
| var sha1 = require('simple-sha1') |  | ||||||
| 
 | 
 | ||||||
| var MAX_METADATA_SIZE = 10000000 // 10MB
 | const MAX_METADATA_SIZE = 1E7 // 10 MB
 | ||||||
| var BITFIELD_GROW = 1000 | const BITFIELD_GROW = 1E3 | ||||||
| var PIECE_LENGTH = 16 * 1024 | const PIECE_LENGTH = 1 << 14 // 16 KiB
 | ||||||
| 
 | 
 | ||||||
| module.exports = function (metadata) { | module.exports = metadata => { | ||||||
|   inherits(utMetadata, EventEmitter) |   class utMetadata extends EventEmitter { | ||||||
|  |     constructor (wire) { | ||||||
|  |       super() | ||||||
| 
 | 
 | ||||||
|   function utMetadata (wire) { |       this._wire = wire | ||||||
|     EventEmitter.call(this) |  | ||||||
| 
 | 
 | ||||||
|     this._wire = wire |       this._fetching = false | ||||||
|  |       this._metadataComplete = false | ||||||
|  |       this._metadataSize = null | ||||||
|  |       // how many reject messages to tolerate before quitting
 | ||||||
|  |       this._remainingRejects = null | ||||||
| 
 | 
 | ||||||
|     this._metadataComplete = false |       // The largest torrent file that I know of is ~1-2MB, which is ~100
 | ||||||
|     this._metadataSize = null |       // pieces. Therefore, cap the bitfield to 10x that (1000 pieces) so a
 | ||||||
|     this._remainingRejects = null // how many reject messages to tolerate before quitting
 |       // malicious peer can't make it grow to fill all memory.
 | ||||||
|     this._fetching = false |       this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) | ||||||
| 
 | 
 | ||||||
|     // The largest .torrent file that I know of is ~1-2MB, which is ~100 pieces.
 |       if (Buffer.isBuffer(metadata)) { | ||||||
|     // Therefore, cap the bitfield to 10x that (1000 pieces) so a malicious peer can't
 |         this.setMetadata(metadata) | ||||||
|     // make it grow to fill all memory.
 |       } | ||||||
|     this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) |     } | ||||||
| 
 | 
 | ||||||
|     if (Buffer.isBuffer(metadata)) { |     onHandshake (infoHash, peerId, extensions) { | ||||||
|       this.setMetadata(metadata) |       this._infoHash = infoHash | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     onExtendedHandshake (handshake) { | ||||||
|  |       if (!handshake.m || !handshake.m.ut_metadata) { | ||||||
|  |         return this.emit('warning', new Error('Peer does not support ut_metadata')) | ||||||
|  |       } | ||||||
|  |       if (!handshake.metadata_size) { | ||||||
|  |         return this.emit('warning', new Error('Peer does not have metadata')) | ||||||
|  |       } | ||||||
|  |       if (typeof handshake.metadata_size !== 'number' || | ||||||
|  |           MAX_METADATA_SIZE < handshake.metadata_size || | ||||||
|  |           handshake.metadata_size <= 0) { | ||||||
|  |         return this.emit('warning', new Error('Peer gave invalid metadata size')) | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this._metadataSize = handshake.metadata_size | ||||||
|  |       this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH) | ||||||
|  |       this._remainingRejects = this._numPieces * 2 | ||||||
|  | 
 | ||||||
|  |       if (this._fetching) { | ||||||
|  |         this._requestPieces() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     onMessage (buf) { | ||||||
|  |       let dict | ||||||
|  |       let trailer | ||||||
|  |       try { | ||||||
|  |         const str = buf.toString() | ||||||
|  |         const trailerIndex = str.indexOf('ee') + 2 | ||||||
|  |         dict = bencode.decode(str.substring(0, trailerIndex)) | ||||||
|  |         trailer = buf.slice(trailerIndex) | ||||||
|  |       } catch (err) { | ||||||
|  |         // drop invalid messages
 | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       switch (dict.msg_type) { | ||||||
|  |         case 0: | ||||||
|  |           // ut_metadata request (from peer)
 | ||||||
|  |           // example: { 'msg_type': 0, 'piece': 0 }
 | ||||||
|  |           this._onRequest(dict.piece) | ||||||
|  |           break | ||||||
|  |         case 1: | ||||||
|  |           // ut_metadata data (in response to our request)
 | ||||||
|  |           // example: { 'msg_type': 1, 'piece': 0, 'total_size': 3425 }
 | ||||||
|  |           this._onData(dict.piece, trailer, dict.total_size) | ||||||
|  |           break | ||||||
|  |         case 2: | ||||||
|  |           // ut_metadata reject (peer doesn't have piece we requested)
 | ||||||
|  |           // { 'msg_type': 2, 'piece': 0 }
 | ||||||
|  |           this._onReject(dict.piece) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Ask the peer to send metadata. | ||||||
|  |      * @public | ||||||
|  |      */ | ||||||
|  |     fetch () { | ||||||
|  |       if (this._metadataComplete) { | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       this._fetching = true | ||||||
|  |       if (this._metadataSize) { | ||||||
|  |         this._requestPieces() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Stop asking the peer to send metadata. | ||||||
|  |      * @public | ||||||
|  |      */ | ||||||
|  |     cancel () { | ||||||
|  |       this._fetching = false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     setMetadata (metadata) { | ||||||
|  |       if (this._metadataComplete) return true | ||||||
|  |       debug('set metadata') | ||||||
|  | 
 | ||||||
|  |       // if full torrent dictionary was passed in, pull out just `info` key
 | ||||||
|  |       try { | ||||||
|  |         const info = bencode.decode(metadata).info | ||||||
|  |         if (info) { | ||||||
|  |           metadata = bencode.encode(info) | ||||||
|  |         } | ||||||
|  |       } catch (err) {} | ||||||
|  | 
 | ||||||
|  |       // check hash
 | ||||||
|  |       if (this._infoHash && this._infoHash !== sha1.sync(metadata)) { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.cancel() | ||||||
|  | 
 | ||||||
|  |       this.metadata = metadata | ||||||
|  |       this._metadataComplete = true | ||||||
|  |       this._metadataSize = this.metadata.length | ||||||
|  |       this._wire.extendedHandshake.metadata_size = this._metadataSize | ||||||
|  | 
 | ||||||
|  |       this.emit('metadata', bencode.encode({ | ||||||
|  |         info: bencode.decode(this.metadata) | ||||||
|  |       })) | ||||||
|  | 
 | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _send (dict, trailer) { | ||||||
|  |       let buf = bencode.encode(dict) | ||||||
|  |       if (Buffer.isBuffer(trailer)) { | ||||||
|  |         buf = Buffer.concat([buf, trailer]) | ||||||
|  |       } | ||||||
|  |       this._wire.extended('ut_metadata', buf) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _request (piece) { | ||||||
|  |       this._send({ msg_type: 0, piece }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _data (piece, buf, totalSize) { | ||||||
|  |       const msg = { msg_type: 1, piece } | ||||||
|  |       if (typeof totalSize === 'number') { | ||||||
|  |         msg.total_size = totalSize | ||||||
|  |       } | ||||||
|  |       this._send(msg, buf) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _reject (piece) { | ||||||
|  |       this._send({ msg_type: 2, piece }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _onRequest (piece) { | ||||||
|  |       if (!this._metadataComplete) { | ||||||
|  |         this._reject(piece) | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       const start = piece * PIECE_LENGTH | ||||||
|  |       let end = start + PIECE_LENGTH | ||||||
|  |       if (end > this._metadataSize) { | ||||||
|  |         end = this._metadataSize | ||||||
|  |       } | ||||||
|  |       const buf = this.metadata.slice(start, end) | ||||||
|  |       this._data(piece, buf, this._metadataSize) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _onData (piece, buf, totalSize) { | ||||||
|  |       if (buf.length > PIECE_LENGTH) { | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       buf.copy(this.metadata, piece * PIECE_LENGTH) | ||||||
|  |       this._bitfield.set(piece) | ||||||
|  |       this._checkDone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _onReject (piece) { | ||||||
|  |       if (this._remainingRejects > 0 && this._fetching) { | ||||||
|  |         // If we haven't been rejected too much,
 | ||||||
|  |         // then try to request the piece again
 | ||||||
|  |         this._request(piece) | ||||||
|  |         this._remainingRejects -= 1 | ||||||
|  |       } else { | ||||||
|  |         this.emit('warning', new Error('Peer sent "reject" too much')) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _requestPieces () { | ||||||
|  |       this.metadata = Buffer.alloc(this._metadataSize) | ||||||
|  |       for (let piece = 0; piece < this._numPieces; piece++) { | ||||||
|  |         this._request(piece) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _checkDone () { | ||||||
|  |       let done = true | ||||||
|  |       for (let piece = 0; piece < this._numPieces; piece++) { | ||||||
|  |         if (!this._bitfield.get(piece)) { | ||||||
|  |           done = false | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!done) return | ||||||
|  | 
 | ||||||
|  |       // attempt to set metadata -- may fail sha1 check
 | ||||||
|  |       const success = this.setMetadata(this.metadata) | ||||||
|  | 
 | ||||||
|  |       if (!success) { | ||||||
|  |         this._failedMetadata() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _failedMetadata () { | ||||||
|  |       // reset bitfield & try again
 | ||||||
|  |       this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) | ||||||
|  |       this._remainingRejects -= this._numPieces | ||||||
|  |       if (this._remainingRejects > 0) { | ||||||
|  |         this._requestPieces() | ||||||
|  |       } else { | ||||||
|  |         this.emit('warning', new Error('Peer sent invalid metadata')) | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Name of the bittorrent-protocol extension
 |   // Name of the bittorrent-protocol extension
 | ||||||
|   utMetadata.prototype.name = 'ut_metadata' |   utMetadata.prototype.name = 'ut_metadata' | ||||||
| 
 | 
 | ||||||
|   utMetadata.prototype.onHandshake = function (infoHash, peerId, extensions) { |  | ||||||
|     this._infoHash = infoHash |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype.onExtendedHandshake = function (handshake) { |  | ||||||
|     if (!handshake.m || !handshake.m.ut_metadata) { |  | ||||||
|       return this.emit('warning', new Error('Peer does not support ut_metadata')) |  | ||||||
|     } |  | ||||||
|     if (!handshake.metadata_size) { |  | ||||||
|       return this.emit('warning', new Error('Peer does not have metadata')) |  | ||||||
|     } |  | ||||||
|     if (typeof handshake.metadata_size !== 'number' || |  | ||||||
|         MAX_METADATA_SIZE < handshake.metadata_size || |  | ||||||
|         handshake.metadata_size <= 0) { |  | ||||||
|       return this.emit('warning', new Error('Peer gave invalid metadata size')) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this._metadataSize = handshake.metadata_size |  | ||||||
|     this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH) |  | ||||||
|     this._remainingRejects = this._numPieces * 2 |  | ||||||
| 
 |  | ||||||
|     if (this._fetching) { |  | ||||||
|       this._requestPieces() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype.onMessage = function (buf) { |  | ||||||
|     var dict, trailer |  | ||||||
|     try { |  | ||||||
|       var str = buf.toString() |  | ||||||
|       var trailerIndex = str.indexOf('ee') + 2 |  | ||||||
|       dict = bencode.decode(str.substring(0, trailerIndex)) |  | ||||||
|       trailer = buf.slice(trailerIndex) |  | ||||||
|     } catch (err) { |  | ||||||
|       // drop invalid messages
 |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (dict.msg_type) { |  | ||||||
|       case 0: |  | ||||||
|         // ut_metadata request (from peer)
 |  | ||||||
|         // example: { 'msg_type': 0, 'piece': 0 }
 |  | ||||||
|         this._onRequest(dict.piece) |  | ||||||
|         break |  | ||||||
|       case 1: |  | ||||||
|         // ut_metadata data (in response to our request)
 |  | ||||||
|         // example: { 'msg_type': 1, 'piece': 0, 'total_size': 3425 }
 |  | ||||||
|         this._onData(dict.piece, trailer, dict.total_size) |  | ||||||
|         break |  | ||||||
|       case 2: |  | ||||||
|         // ut_metadata reject (peer doesn't have piece we requested)
 |  | ||||||
|         // { 'msg_type': 2, 'piece': 0 }
 |  | ||||||
|         this._onReject(dict.piece) |  | ||||||
|         break |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Ask the peer to send metadata. |  | ||||||
|    * @public |  | ||||||
|    */ |  | ||||||
|   utMetadata.prototype.fetch = function () { |  | ||||||
|     if (this._metadataComplete) { |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     this._fetching = true |  | ||||||
|     if (this._metadataSize) { |  | ||||||
|       this._requestPieces() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Stop asking the peer to send metadata. |  | ||||||
|    * @public |  | ||||||
|    */ |  | ||||||
|   utMetadata.prototype.cancel = function () { |  | ||||||
|     this._fetching = false |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype.setMetadata = function (metadata) { |  | ||||||
|     if (this._metadataComplete) return true |  | ||||||
|     debug('set metadata') |  | ||||||
| 
 |  | ||||||
|     // if full torrent dictionary was passed in, pull out just `info` key
 |  | ||||||
|     try { |  | ||||||
|       var info = bencode.decode(metadata).info |  | ||||||
|       if (info) { |  | ||||||
|         metadata = bencode.encode(info) |  | ||||||
|       } |  | ||||||
|     } catch (err) {} |  | ||||||
| 
 |  | ||||||
|     // check hash
 |  | ||||||
|     if (this._infoHash && this._infoHash !== sha1.sync(metadata)) { |  | ||||||
|       return false |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.cancel() |  | ||||||
| 
 |  | ||||||
|     this.metadata = metadata |  | ||||||
|     this._metadataComplete = true |  | ||||||
|     this._metadataSize = this.metadata.length |  | ||||||
|     this._wire.extendedHandshake.metadata_size = this._metadataSize |  | ||||||
| 
 |  | ||||||
|     this.emit('metadata', bencode.encode({ info: bencode.decode(this.metadata) })) |  | ||||||
| 
 |  | ||||||
|     return true |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._send = function (dict, trailer) { |  | ||||||
|     var buf = bencode.encode(dict) |  | ||||||
|     if (Buffer.isBuffer(trailer)) { |  | ||||||
|       buf = Buffer.concat([buf, trailer]) |  | ||||||
|     } |  | ||||||
|     this._wire.extended('ut_metadata', buf) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._request = function (piece) { |  | ||||||
|     this._send({ msg_type: 0, piece: piece }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._data = function (piece, buf, totalSize) { |  | ||||||
|     var msg = { msg_type: 1, piece: piece } |  | ||||||
|     if (typeof totalSize === 'number') { |  | ||||||
|       msg.total_size = totalSize |  | ||||||
|     } |  | ||||||
|     this._send(msg, buf) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._reject = function (piece) { |  | ||||||
|     this._send({ msg_type: 2, piece: piece }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._onRequest = function (piece) { |  | ||||||
|     if (!this._metadataComplete) { |  | ||||||
|       this._reject(piece) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     var start = piece * PIECE_LENGTH |  | ||||||
|     var end = start + PIECE_LENGTH |  | ||||||
|     if (end > this._metadataSize) { |  | ||||||
|       end = this._metadataSize |  | ||||||
|     } |  | ||||||
|     var buf = this.metadata.slice(start, end) |  | ||||||
|     this._data(piece, buf, this._metadataSize) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._onData = function (piece, buf, totalSize) { |  | ||||||
|     if (buf.length > PIECE_LENGTH) { |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     buf.copy(this.metadata, piece * PIECE_LENGTH) |  | ||||||
|     this._bitfield.set(piece) |  | ||||||
|     this._checkDone() |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._onReject = function (piece) { |  | ||||||
|     if (this._remainingRejects > 0 && this._fetching) { |  | ||||||
|       // If we haven't been rejected too much, then try to request the piece again
 |  | ||||||
|       this._request(piece) |  | ||||||
|       this._remainingRejects -= 1 |  | ||||||
|     } else { |  | ||||||
|       this.emit('warning', new Error('Peer sent "reject" too much')) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._requestPieces = function () { |  | ||||||
|     this.metadata = Buffer.alloc(this._metadataSize) |  | ||||||
|     for (var piece = 0; piece < this._numPieces; piece++) { |  | ||||||
|       this._request(piece) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._checkDone = function () { |  | ||||||
|     var done = true |  | ||||||
|     for (var piece = 0; piece < this._numPieces; piece++) { |  | ||||||
|       if (!this._bitfield.get(piece)) { |  | ||||||
|         done = false |  | ||||||
|         break |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (!done) return |  | ||||||
| 
 |  | ||||||
|     // attempt to set metadata -- may fail sha1 check
 |  | ||||||
|     var success = this.setMetadata(this.metadata) |  | ||||||
| 
 |  | ||||||
|     if (!success) { |  | ||||||
|       this._failedMetadata() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utMetadata.prototype._failedMetadata = function () { |  | ||||||
|     // reset bitfield & try again
 |  | ||||||
|     this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) |  | ||||||
|     this._remainingRejects -= this._numPieces |  | ||||||
|     if (this._remainingRejects > 0) { |  | ||||||
|       this._requestPieces() |  | ||||||
|     } else { |  | ||||||
|       this.emit('warning', new Error('Peer sent invalid metadata')) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return utMetadata |   return utMetadata | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,7 +14,6 @@ | |||||||
|     "bencode": "^2.0.0", |     "bencode": "^2.0.0", | ||||||
|     "bitfield": "^2.0.0", |     "bitfield": "^2.0.0", | ||||||
|     "debug": "^3.1.0", |     "debug": "^3.1.0", | ||||||
|     "inherits": "^2.0.1", |  | ||||||
|     "simple-sha1": "^2.0.0" |     "simple-sha1": "^2.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| var bencode = require('bencode') | const { leavesMetadata } = require('webtorrent-fixtures') | ||||||
| var fixtures = require('webtorrent-fixtures') | const bencode = require('bencode') | ||||||
| var Protocol = require('bittorrent-protocol') | const Protocol = require('bittorrent-protocol') | ||||||
| var test = require('tape') | const test = require('tape') | ||||||
| var utMetadata = require('../') | const utMetadata = require('../') | ||||||
| 
 | 
 | ||||||
| test('wire.use(utMetadata())', function (t) { | test('wire.use(utMetadata())', t => { | ||||||
|   var wire = new Protocol() |   const wire = new Protocol() | ||||||
|   wire.pipe(wire) |   wire.pipe(wire) | ||||||
| 
 | 
 | ||||||
|   wire.use(utMetadata()) |   wire.use(utMetadata()) | ||||||
| @ -17,15 +17,18 @@ test('wire.use(utMetadata())', function (t) { | |||||||
|   t.end() |   t.end() | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('wire.use(utMetadata(metadata))', function (t) { | test('wire.use(utMetadata(metadata))', t => { | ||||||
|   var wire = new Protocol() |   const wire = new Protocol() | ||||||
|   wire.pipe(wire) |   wire.pipe(wire) | ||||||
| 
 | 
 | ||||||
|   wire.use(utMetadata(fixtures.leavesMetadata.torrent)) |   wire.use(utMetadata(leavesMetadata.torrent)) | ||||||
| 
 | 
 | ||||||
|   t.ok(wire.ut_metadata) |   t.ok(wire.ut_metadata) | ||||||
|   t.ok(wire.ut_metadata.fetch) |   t.ok(wire.ut_metadata.fetch) | ||||||
|   t.ok(wire.ut_metadata.cancel) |   t.ok(wire.ut_metadata.cancel) | ||||||
|   t.equal(wire.ut_metadata.metadata.toString('hex'), bencode.encode(bencode.decode(fixtures.leavesMetadata.torrent).info).toString('hex')) |   t.equal( | ||||||
|  |     wire.ut_metadata.metadata.toString('hex'), | ||||||
|  |     bencode.encode(bencode.decode(leavesMetadata.torrent).info).toString('hex') | ||||||
|  |   ) | ||||||
|   t.end() |   t.end() | ||||||
| }) | }) | ||||||
|  | |||||||
							
								
								
									
										143
									
								
								test/fetch.js
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								test/fetch.js
									
									
									
									
									
								
							| @ -1,34 +1,39 @@ | |||||||
| var bencode = require('bencode') | const { leavesMetadata, sintel } = require('webtorrent-fixtures') | ||||||
| var fixtures = require('webtorrent-fixtures') | const bencode = require('bencode') | ||||||
| var Protocol = require('bittorrent-protocol') | const Protocol = require('bittorrent-protocol') | ||||||
| var test = require('tape') | const test = require('tape') | ||||||
| var utMetadata = require('../') | const utMetadata = require('../') | ||||||
| 
 | 
 | ||||||
| var id1 = Buffer.from('01234567890123456789') | const id1 = Buffer.from('01234567890123456789') | ||||||
| var id2 = Buffer.from('12345678901234567890') | const id2 = Buffer.from('12345678901234567890') | ||||||
| 
 | 
 | ||||||
| test('fetch()', function (t) { | test('fetch()', t => { | ||||||
|   t.plan(3) |   t.plan(3) | ||||||
| 
 | 
 | ||||||
|   var wire1 = new Protocol() |   const wire1 = new Protocol() | ||||||
|   var wire2 = new Protocol() |   const wire2 = new Protocol() | ||||||
|   wire1.pipe(wire2).pipe(wire1) |   wire1.pipe(wire2).pipe(wire1) | ||||||
| 
 | 
 | ||||||
|   wire1.use(utMetadata(fixtures.leavesMetadata.torrent)) // wire1 already has metadata
 |   wire1.use(utMetadata(leavesMetadata.torrent)) // wire1 already has metadata
 | ||||||
|   wire2.use(utMetadata()) // wire2 does not
 |   wire2.use(utMetadata()) // wire2 does not
 | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.fetch() |   wire2.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('metadata', function (_metadata) { |   wire2.ut_metadata.on('metadata', _metadata => { | ||||||
|     // got metadata!
 |     // got metadata!
 | ||||||
|     t.equal(_metadata.toString('hex'), bencode.encode({ info: bencode.decode(fixtures.leavesMetadata.torrent).info }).toString('hex')) |     t.equal( | ||||||
|  |       _metadata.toString('hex'), | ||||||
|  |       bencode.encode({ | ||||||
|  |         info: bencode.decode(leavesMetadata.torrent).info | ||||||
|  |       }).toString('hex') | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('handshake', function (infoHash, peerId, extensions) { |   wire2.on('handshake', (infoHash, peerId, extensions) => { | ||||||
|     wire2.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id2) |     wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('extended', function (ext) { |   wire2.on('extended', ext => { | ||||||
|     if (ext === 'handshake') { |     if (ext === 'handshake') { | ||||||
|       t.pass('got extended handshake') |       t.pass('got extended handshake') | ||||||
|     } else if (ext === 'ut_metadata') { |     } else if (ext === 'ut_metadata') { | ||||||
| @ -41,14 +46,14 @@ test('fetch()', function (t) { | |||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire1.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id1) |   wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('fetch() from peer without metadata', function (t) { | test('fetch() from peer without metadata', t => { | ||||||
|   t.plan(2) |   t.plan(2) | ||||||
| 
 | 
 | ||||||
|   var wire1 = new Protocol() |   const wire1 = new Protocol() | ||||||
|   var wire2 = new Protocol() |   const wire2 = new Protocol() | ||||||
|   wire1.pipe(wire2).pipe(wire1) |   wire1.pipe(wire2).pipe(wire1) | ||||||
| 
 | 
 | ||||||
|   wire1.use(utMetadata()) // neither wire has metadata
 |   wire1.use(utMetadata()) // neither wire has metadata
 | ||||||
| @ -56,25 +61,25 @@ test('fetch() from peer without metadata', function (t) { | |||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.fetch() |   wire2.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('metadata', function () { |   wire2.ut_metadata.on('metadata', () => { | ||||||
|     t.fail('No "metadata" event should fire') |     t.fail('No "metadata" event should fire') | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire1.ut_metadata.onMessage = function () { |   wire1.ut_metadata.onMessage = () => { | ||||||
|     t.fail('No messages should be sent to wire1') |     t.fail('No messages should be sent to wire1') | ||||||
|     // No messages should be sent because wire1 never sent metadata_size in the
 |     // No messages should be sent because wire1 never sent metadata_size
 | ||||||
|     // extended handshake, so he doesn't have metadata
 |     // in the extended handshake, so he doesn't have metadata
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('warning', function () { |   wire2.ut_metadata.on('warning', () => { | ||||||
|     t.pass('got warning about peer missing metadata') |     t.pass('got warning about peer missing metadata') | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('handshake', function (infoHash, peerId, extensions) { |   wire2.on('handshake', (infoHash, peerId, extensions) => { | ||||||
|     wire2.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id2) |     wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('extended', function (ext) { |   wire2.on('extended', ext => { | ||||||
|     if (ext === 'handshake') { |     if (ext === 'handshake') { | ||||||
|       t.pass('got extended handshake') |       t.pass('got extended handshake') | ||||||
|     } else if (ext === 'ut_metadata') { |     } else if (ext === 'ut_metadata') { | ||||||
| @ -84,36 +89,44 @@ test('fetch() from peer without metadata', function (t) { | |||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire1.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id1) |   wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('fetch when peer gets metadata later (setMetadata)', function (t) { | test('fetch when peer gets metadata later (setMetadata)', t => { | ||||||
|   t.plan(3) |   t.plan(3) | ||||||
| 
 | 
 | ||||||
|   var wire1 = new Protocol() |   const wire1 = new Protocol() | ||||||
|   var wire2 = new Protocol() |   const wire2 = new Protocol() | ||||||
|  | 
 | ||||||
|   wire1.pipe(wire2).pipe(wire1) |   wire1.pipe(wire2).pipe(wire1) | ||||||
| 
 | 
 | ||||||
|   wire1.use(utMetadata()) // wire1 starts without metadata
 |   wire1.use(utMetadata()) // wire1 starts without metadata
 | ||||||
| 
 | 
 | ||||||
|   process.nextTick(function () { |   process.nextTick(() => { | ||||||
|     wire1.ut_metadata.setMetadata(fixtures.leavesMetadata.torrent) // wire1 gets metadata later
 |     // wire1 gets metadata later
 | ||||||
|  |     wire1.ut_metadata.setMetadata(leavesMetadata.torrent) | ||||||
| 
 | 
 | ||||||
|     process.nextTick(function () { |     process.nextTick(() => { | ||||||
|       // wire2 does not start with metadata, but connects to wire1 after it gets metadata
 |       // wire2 does not start with metadata,
 | ||||||
|  |       // but connects to wire1 after it gets metadata
 | ||||||
|       wire2.use(utMetadata()) |       wire2.use(utMetadata()) | ||||||
|       wire2.ut_metadata.fetch() |       wire2.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|       wire2.ut_metadata.on('metadata', function (_metadata) { |       wire2.ut_metadata.on('metadata', _metadata => { | ||||||
|         // got metadata!
 |         // got metadata!
 | ||||||
|         t.equal(_metadata.toString('hex'), bencode.encode({ info: bencode.decode(fixtures.leavesMetadata.torrent).info }).toString('hex')) |         t.equal( | ||||||
|  |           _metadata.toString('hex'), | ||||||
|  |           bencode.encode({ | ||||||
|  |             info: bencode.decode(leavesMetadata.torrent).info | ||||||
|  |           }).toString('hex') | ||||||
|  |         ) | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       wire2.on('handshake', function (infoHash, peerId, extensions) { |       wire2.on('handshake', (infoHash, peerId, extensions) => { | ||||||
|         wire2.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id2) |         wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       wire2.on('extended', function (ext) { |       wire2.on('extended', ext => { | ||||||
|         if (ext === 'handshake') { |         if (ext === 'handshake') { | ||||||
|           t.pass('got extended handshake') |           t.pass('got extended handshake') | ||||||
|         } else if (ext === 'ut_metadata') { |         } else if (ext === 'ut_metadata') { | ||||||
| @ -126,37 +139,43 @@ test('fetch when peer gets metadata later (setMetadata)', function (t) { | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       wire1.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id1) |       wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('fetch() large torrent', function (t) { | test('fetch() large torrent', t => { | ||||||
|   t.plan(4) |   t.plan(4) | ||||||
| 
 | 
 | ||||||
|   var wire1 = new Protocol() |   const wire1 = new Protocol() | ||||||
|   var wire2 = new Protocol() |   const wire2 = new Protocol() | ||||||
|   wire1.pipe(wire2).pipe(wire1) |   wire1.pipe(wire2).pipe(wire1) | ||||||
| 
 | 
 | ||||||
|   wire1.use(utMetadata(fixtures.sintel.torrent)) // wire1 already has metadata
 |   wire1.use(utMetadata(sintel.torrent)) // wire1 already has metadata
 | ||||||
|   wire2.use(utMetadata()) // wire2 does not
 |   wire2.use(utMetadata()) // wire2 does not
 | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.fetch() |   wire2.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('metadata', function (_metadata) { |   wire2.ut_metadata.on('metadata', _metadata => { | ||||||
|     // got metadata!
 |     // got metadata!
 | ||||||
|     t.equal(_metadata.toString('hex'), bencode.encode({ info: bencode.decode(fixtures.sintel.torrent).info }).toString('hex')) |     t.equal( | ||||||
|  |       _metadata.toString('hex'), | ||||||
|  |       bencode.encode({ | ||||||
|  |         info: bencode.decode(sintel.torrent).info | ||||||
|  |       }).toString('hex') | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('handshake', function (infoHash, peerId, extensions) { |   wire2.on('handshake', (infoHash, peerId, extensions) => { | ||||||
|     wire2.handshake(fixtures.sintel.parsedTorrent.infoHash, id2) |     wire2.handshake(sintel.parsedTorrent.infoHash, id2) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('extended', function (ext) { |   wire2.on('extended', ext => { | ||||||
|     if (ext === 'handshake') { |     if (ext === 'handshake') { | ||||||
|       t.pass('got extended handshake') |       t.pass('got extended handshake') | ||||||
|     } else if (ext === 'ut_metadata') { |     } else if (ext === 'ut_metadata') { | ||||||
|       // note: this should get called twice, once for each block of the sintel metadata
 |       // note: this should get called twice,
 | ||||||
|  |       // once for each block of the sintel metadata
 | ||||||
|       t.pass('got extended ut_metadata message') |       t.pass('got extended ut_metadata message') | ||||||
| 
 | 
 | ||||||
|       // this is emitted for consistency's sake, but it's ignored
 |       // this is emitted for consistency's sake, but it's ignored
 | ||||||
| @ -167,17 +186,17 @@ test('fetch() large torrent', function (t) { | |||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire1.handshake(fixtures.sintel.parsedTorrent.infoHash, id1) |   wire1.handshake(sintel.parsedTorrent.infoHash, id1) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| test('discard invalid metadata', function (t) { | test('discard invalid metadata', t => { | ||||||
|   t.plan(1) |   t.plan(1) | ||||||
| 
 | 
 | ||||||
|   var wire1 = new Protocol() |   const wire1 = new Protocol() | ||||||
|   var wire2 = new Protocol() |   const wire2 = new Protocol() | ||||||
|   wire1.pipe(wire2).pipe(wire1) |   wire1.pipe(wire2).pipe(wire1) | ||||||
| 
 | 
 | ||||||
|   var invalidMetadata = fixtures.leavesMetadata.torrent.slice(0) |   const invalidMetadata = leavesMetadata.torrent.slice(0) | ||||||
|   invalidMetadata[55] = 65 // mess up a byte in the info block
 |   invalidMetadata[55] = 65 // mess up a byte in the info block
 | ||||||
| 
 | 
 | ||||||
|   wire1.use(utMetadata(invalidMetadata)) |   wire1.use(utMetadata(invalidMetadata)) | ||||||
| @ -185,17 +204,17 @@ test('discard invalid metadata', function (t) { | |||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.fetch() |   wire2.ut_metadata.fetch() | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('metadata', function () { |   wire2.ut_metadata.on('metadata', () => { | ||||||
|     t.fail('No "metadata" event should fire') |     t.fail('No "metadata" event should fire') | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.ut_metadata.on('warning', function () { |   wire2.ut_metadata.on('warning', () => { | ||||||
|     t.pass('got warning because peer sent reject too much') |     t.pass('got warning because peer sent reject too much') | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire2.on('handshake', function (infoHash, peerId, extensions) { |   wire2.on('handshake', (infoHash, peerId, extensions) => { | ||||||
|     wire2.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id2) |     wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   wire1.handshake(fixtures.leavesMetadata.parsedTorrent.infoHash, id1) |   wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) | ||||||
| }) | }) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Feross Aboukhadijeh
						Feross Aboukhadijeh