Compare commits

..

No commits in common. "master" and "v1.2.1" have entirely different histories.

10 changed files with 330 additions and 523 deletions

View File

@ -1,2 +0,0 @@
.travis.yml
test/

View File

@ -1,3 +1,4 @@
language: node_js language: node_js
node_js: node_js:
- lts/* - "0.11"
- "0.10"

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Feross Aboukhadijeh and WebTorrent, LLC Copyright (c) 2013 Feross Aboukhadijeh
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -1,44 +1,37 @@
# ut_metadata [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] # ut_metadata [![travis](http://img.shields.io/travis/feross/ut_metadata.svg)](https://travis-ci.org/feross/ut_metadata) [![npm](http://img.shields.io/npm/v/ut_metadata.svg)](https://npmjs.org/package/ut_metadata) [![gittip](http://img.shields.io/gittip/feross.svg)](https://www.gittip.com/feross/)
[travis-image]: https://img.shields.io/travis/webtorrent/ut_metadata/master.svg
[travis-url]: https://travis-ci.org/webtorrent/ut_metadata
[npm-image]: https://img.shields.io/npm/v/ut_metadata.svg
[npm-url]: https://npmjs.org/package/ut_metadata
[downloads-image]: https://img.shields.io/npm/dm/ut_metadata.svg
[downloads-url]: https://npmjs.org/package/ut_metadata
[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
[standard-url]: https://standardjs.com
### BitTorrent Extension for Peers to Send Metadata Files (BEP 9) ### BitTorrent Extension for Peers to Send Metadata Files (BEP 9)
JavaScript implementation of the [Extension for Peers to Send Metadata Files (BEP 9)](http://www.bittorrent.org/beps/bep_0009.html). Use with [bittorrent-protocol](https://www.npmjs.com/package/bittorrent-protocol). [![browser support](https://ci.testling.com/feross/ut_metadata.png)](https://ci.testling.com/feross/ut_metadata)
Node.js implementation of the [Extension for Peers to Send Metadata Files (BEP 9)](http://www.bittorrent.org/beps/bep_0009.html).
The purpose of this extension is to allow clients to join a swarm and complete a download without the need of downloading a .torrent file first. This extension instead allows clients to download the metadata from peers. It makes it possible to support magnet links, a link on a web page only containing enough information to join the swarm (the info hash). The purpose of this extension is to allow clients to join a swarm and complete a download without the need of downloading a .torrent file first. This extension instead allows clients to download the metadata from peers. It makes it possible to support magnet links, a link on a web page only containing enough information to join the swarm (the info hash).
Works in the browser with [browserify](http://browserify.org/)! This module is used by [WebTorrent](http://webtorrent.io). Works in the browser with [browserify](http://browserify.org/)! This module is used by [WebTorrent](http://webtorrent.io).
### install ## install
``` ```
npm install ut_metadata npm install ut_metadata
``` ```
### usage ## usage
This package should be used with [bittorrent-protocol](https://www.npmjs.com/package/bittorrent-protocol), which supports a plugin-like system for extending the protocol with additional functionality. This package should be used with [bittorrent-protocol](https://github.com/feross/bittorrent-protocol), which supports a plugin-like system for extending the protocol with additional functionality.
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
const Protocol = require('bittorrent-protocol') var Protocol = require('bittorrent-protocol')
const net = require('net') var net = require('net')
net.createServer(socket => { net.createServer(function (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', (infoHash, peerId) => { wire.on('handshake', function (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 +41,12 @@ net.createServer(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
const Protocol = require('bittorrent-protocol') var Protocol = require('bittorrent-protocol')
const net = require('net') var net = require('net')
const ut_metadata = require('ut_metadata') var ut_metadata = require('ut_metadata')
net.createServer(socket => { net.createServer(function (socket) {
const wire = new Protocol() var wire = new Protocol()
socket.pipe(wire).pipe(socket) socket.pipe(wire).pipe(socket)
// initialize the extension // initialize the extension
@ -65,7 +58,7 @@ net.createServer(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', metadata => { wire.ut_metadata.on('metadata', function (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,51 +68,43 @@ net.createServer(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', err => { wire.ut_metadata.on('warning', function (err) {
console.log(err.message) console.log(err.message)
}) })
// handle handshake // handle handshake
wire.on('handshake', (infoHash, peerId) => { wire.on('handshake', function (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'))
}) })
}).listen(6881) }).listen(6881)
``` ```
### api ## methods
#### `ut_metadata([metadata])` ### fetch
Initialize the extension. If you have the torrent metadata (Buffer), pass it into the
`ut_metadata` constructor so it's made available to the peer.
```js
const metadata = fs.readFileSync(__dirname + '/file.torrent')
wire.use(ut_metadata(metadata))
```
#### `ut_metadata.fetch()`
Ask the peer to send metadata. Ask the peer to send metadata.
#### `ut_metadata.cancel()` ```js
wire.ut_metadata.fetch()
```
### cancel
Stop asking the peer to send metadata. Stop asking the peer to send metadata.
#### `ut_metadata.setMetadata(metadata)` ```js
wire.ut_metadata.cancel()
```
Set the metadata. If you didn't have the metadata at the time `ut_metadata` was ### event: 'metadata'
initialized, but you end up getting it from another peer (or somewhere else), you should
call `setMetadata` so the metadata will be available to the peer.
#### `ut_metadata.on('metadata', function (metadata) {})`
Fired when metadata is available and verified to be correct. Called with a single Fired when metadata is available and verified to be correct. Called with a single
parameter of type Buffer. parameter of type Buffer.
```js ```js
wire.ut_metadata.on('metadata', metadata => { wire.ut_metadata.on('metadata', function (metadata) {
console.log(Buffer.isBuffer(metadata)) // true console.log(Buffer.isBuffer(metadata)) // true
}) })
``` ```
@ -128,19 +113,19 @@ Note: the event will not fire if the peer does not support ut_metadata, if they
don't have metadata yet either, if they repeatedly send invalid data, or if they don't have metadata yet either, if they repeatedly send invalid data, or if they
simply don't respond. simply don't respond.
#### `ut_metadata.on('warning', function (err) {})` ### event: 'warning'
Fired if: Fired if:
- the peer does not support ut_metadata - the peer does not support ut_metadata
- the peer doesn't have metadata yet - the peer doesn't have metadata yet
- the peer repeatedly sent invalid data - the repeatedly sent invalid data
```js ```js
wire.ut_metadata.on('warning', err => { wire.ut_metadata.on('warning', function (err) {
console.log(err.message) console.log(err.message)
}) })
``` ```
### license ## license
MIT. Copyright (c) [Feross Aboukhadijeh](https://feross.org) and [WebTorrent, LLC](https://webtorrent.io). MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).

427
index.js
View File

@ -1,244 +1,209 @@
/*! ut_metadata. MIT License. WebTorrent LLC <https://webtorrent.io/opensource> */ var BitField = require('bitfield')
const { EventEmitter } = require('events') var bncode = require('bncode')
const bencode = require('bencode') var EventEmitter = require('events').EventEmitter
const BitField = require('bitfield').default var inherits = require('inherits')
const debug = require('debug')('ut_metadata') var Rusha = require('rusha-browserify') // Fast SHA1 (works in browser)
const sha1 = require('simple-sha1')
const MAX_METADATA_SIZE = 1E7 // 10 MB var PIECE_LENGTH = 16 * 1024
const BITFIELD_GROW = 1E3
const PIECE_LENGTH = 1 << 14 // 16 KiB
module.exports = metadata => { function sha1 (buf) {
class utMetadata extends EventEmitter { return (new Rusha()).digestFromBuffer(buf)
constructor (wire) { }
super()
this._wire = wire module.exports = function (metadata) {
this._fetching = false inherits(ut_metadata, EventEmitter)
this._metadataComplete = false
this._metadataSize = null
// how many reject messages to tolerate before quitting
this._remainingRejects = null
// The largest torrent file that I know of is ~1-2MB, which is ~100 function ut_metadata (wire) {
// pieces. Therefore, cap the bitfield to 10x that (1000 pieces) so a EventEmitter.call(this)
// malicious peer can't make it grow to fill all memory.
this._bitfield = new BitField(0, { grow: BITFIELD_GROW })
if (Buffer.isBuffer(metadata)) { this._wire = wire
this.setMetadata(metadata)
}
}
onHandshake (infoHash, peerId, extensions) { this._metadataComplete = false
this._infoHash = infoHash this._metadataSize = null
} this._remainingRejects = null // how many reject messages to tolerate before quitting
this._fetching = false
this._bitfield = new BitField(0)
onExtendedHandshake (handshake) { if (Buffer.isBuffer(metadata)) {
if (!handshake.m || !handshake.m.ut_metadata) { this._gotMetadata(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
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 || !this._fetching) {
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 () {
if (!this._fetching) return
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 ut_metadata.prototype.onHandshake = function (infoHash, peerId, extensions) {
utMetadata.prototype.name = 'ut_metadata' this._infoHash = infoHash
}
return utMetadata ut_metadata.prototype.onExtendedHandshake = function (handshake) {
if (!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'))
}
this._metadataSize = handshake.metadata_size
this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH)
this._remainingRejects = this._numPieces * 2
if (this._fetching) {
this._requestPieces()
}
}
ut_metadata.prototype.onMessage = function (buf) {
var dict, trailer
try {
var str = buf.toString()
var trailerIndex = str.indexOf('ee') + 2
dict = bncode.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
}
}
// Expose high-level, friendly API (fetch/cancel)
ut_metadata.prototype.fetch = function () {
if (this._metadataComplete) {
return
}
this._fetching = true
if (this._metadataSize) {
this._requestPieces()
}
}
ut_metadata.prototype.cancel = function () {
this._fetching = false
}
ut_metadata.prototype._send = function (dict, trailer) {
var buf = bncode.encode(dict)
if (Buffer.isBuffer(trailer)) {
buf = Buffer.concat([buf, trailer])
}
this._wire.extended('ut_metadata', buf)
}
ut_metadata.prototype._request = function (piece) {
this._send({ msg_type: 0, piece: piece })
}
ut_metadata.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)
}
ut_metadata.prototype._reject = function (piece) {
this._send({ msg_type: 2, piece: piece })
}
ut_metadata.prototype._onRequest = function (piece) {
if (this._metadataComplete) {
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)
}
}
ut_metadata.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()
}
ut_metadata.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'))
}
}
ut_metadata.prototype._requestPieces = function () {
this.metadata = new Buffer(this._metadataSize)
for (var piece = 0; piece < this._numPieces; piece++) {
this._request(piece)
}
}
ut_metadata.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
// 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()
}
}
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
} }

View File

@ -1,60 +1,56 @@
{ {
"name": "ut_metadata", "name": "ut_metadata",
"description": "Extension for Peers to Send Metadata Files (BEP 9)", "description": "Extension for Peers to Send Metadata Files (BEP 9)",
"version": "3.5.2", "version": "1.2.1",
"author": { "author": {
"name": "WebTorrent LLC", "name": "Feross Aboukhadijeh",
"email": "feross@webtorrent.io", "email": "feross@feross.org",
"url": "https://webtorrent.io" "url": "http://feross.org/"
}, },
"bugs": { "bugs": {
"url": "https://github.com/webtorrent/ut_metadata/issues" "url": "https://github.com/feross/ut_metadata/issues"
}, },
"dependencies": { "dependencies": {
"bencode": "^2.0.1", "bncode": "^0.5.0",
"bitfield": "^4.0.0", "bitfield": "^0.2.0",
"debug": "^4.2.0", "inherits": "^2.0.1",
"simple-sha1": "^3.0.1" "rusha-browserify": "^0.7.3"
}, },
"devDependencies": { "devDependencies": {
"bittorrent-protocol": "^3.1.2", "tape": "2.x",
"brfs": "^2.0.2", "bittorrent-protocol": "^1.0.0",
"standard": "*", "parse-torrent": "^0.6.0"
"tape": "^5.0.1",
"webtorrent-fixtures": "^1.7.3"
}, },
"homepage": "http://webtorrent.io",
"keywords": [ "keywords": [
"Extension for Peers to Send Metadata Files",
"bep",
"bep 9",
"bep_0009",
"bittorrent",
"metadata",
"p2p",
"torrent", "torrent",
"ut_metadata" "bittorrent",
"ut_metadata",
"bep_0009",
"bep 9",
"bep",
"9",
"Extension for Peers to Send Metadata Files",
"metadata",
"p2p"
], ],
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/webtorrent/ut_metadata.git" "url": "git://github.com/feross/ut_metadata.git"
}, },
"scripts": { "scripts": {
"test": "standard && tape test/*.js" "test": "tape test/*.js"
}, },
"funding": [ "testling": {
{ "files": "test/*.js",
"type": "github", "browsers": [
"url": "https://github.com/sponsors/feross" "ie/9..latest",
}, "chrome/25..latest",
{ "firefox/20..latest",
"type": "patreon", "safari/6..latest",
"url": "https://www.patreon.com/feross" "opera/15.0..latest"
}, ]
{ }
"type": "consulting",
"url": "https://feross.org/support"
}
]
} }

View File

@ -1,14 +1,16 @@
const { leavesMetadata } = require('webtorrent-fixtures') var fs = require('fs')
const bencode = require('bencode') var Protocol = require('bittorrent-protocol')
const Protocol = require('bittorrent-protocol') var ut_metadata = require('../')
const test = require('tape') var test = require('tape')
const utMetadata = require('../')
test('wire.use(utMetadata())', t => { // Used in multiple tests
const wire = new Protocol() var metadata = fs.readFileSync(__dirname + '/torrents/leaves-magnet.torrent')
test('wire.use(ut_metadata())', function (t) {
var wire = new Protocol()
wire.pipe(wire) wire.pipe(wire)
wire.use(utMetadata()) wire.use(ut_metadata())
t.ok(wire.ut_metadata) t.ok(wire.ut_metadata)
t.ok(wire.ut_metadata.fetch) t.ok(wire.ut_metadata.fetch)
@ -17,18 +19,15 @@ test('wire.use(utMetadata())', t => {
t.end() t.end()
}) })
test('wire.use(utMetadata(metadata))', t => { test('wire.use(ut_metadata(metadata))', function (t) {
const wire = new Protocol() var wire = new Protocol()
wire.pipe(wire) wire.pipe(wire)
wire.use(utMetadata(leavesMetadata.torrent)) wire.use(ut_metadata(metadata))
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( t.equal(wire.ut_metadata.metadata, metadata)
wire.ut_metadata.metadata.toString('hex'),
bencode.encode(bencode.decode(leavesMetadata.torrent).info).toString('hex')
)
t.end() t.end()
}) })

View File

@ -1,39 +1,37 @@
const { leavesMetadata, sintel } = require('webtorrent-fixtures') var fs = require('fs')
const bencode = require('bencode') var parseTorrent = require('parse-torrent')
const Protocol = require('bittorrent-protocol') var Protocol = require('bittorrent-protocol')
const test = require('tape') var ut_metadata = require('../')
const utMetadata = require('../') var test = require('tape')
const id1 = Buffer.from('01234567890123456789') // Used in multiple tests
const id2 = Buffer.from('12345678901234567890') var metadata = fs.readFileSync(__dirname + '/torrents/leaves-magnet.torrent')
var parsedTorrent = parseTorrent(metadata)
var id1 = new Buffer('01234567890123456789')
var id2 = new Buffer('12345678901234567890')
test('fetch()', t => { test('fetch()', function (t) {
t.plan(3) t.plan(3)
const wire1 = new Protocol() var wire1 = new Protocol()
const wire2 = new Protocol() var wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1) wire1.pipe(wire2).pipe(wire1)
wire1.use(utMetadata(leavesMetadata.torrent)) // wire1 already has metadata wire1.use(ut_metadata(metadata)) // wire1 already has metadata
wire2.use(utMetadata()) // wire2 does not wire2.use(ut_metadata()) // wire2 does not
wire2.ut_metadata.fetch() wire2.ut_metadata.fetch()
wire2.ut_metadata.on('metadata', _metadata => { wire2.ut_metadata.on('metadata', function (_metadata) {
// got metadata! // got metadata!
t.equal( t.deepEqual(_metadata, metadata)
_metadata.toString('hex'),
bencode.encode({
info: bencode.decode(leavesMetadata.torrent).info
}).toString('hex')
)
}) })
wire2.on('handshake', (infoHash, peerId, extensions) => { wire2.on('handshake', function (infoHash, peerId, extensions) {
wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) wire2.handshake(parsedTorrent.infoHash, id2)
}) })
wire2.on('extended', ext => { wire2.on('extended', function (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') {
@ -46,40 +44,40 @@ test('fetch()', t => {
} }
}) })
wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) wire1.handshake(parsedTorrent.infoHash, id1)
}) })
test('fetch() from peer without metadata', t => { test('fetch() from peer without metadata', function (t) {
t.plan(2) t.plan(2)
const wire1 = new Protocol() var wire1 = new Protocol()
const wire2 = new Protocol() var wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1) wire1.pipe(wire2).pipe(wire1)
wire1.use(utMetadata()) // neither wire has metadata wire1.use(ut_metadata()) // neither wire has metadata
wire2.use(utMetadata()) wire2.use(ut_metadata())
wire2.ut_metadata.fetch() wire2.ut_metadata.fetch()
wire2.ut_metadata.on('metadata', () => { wire2.ut_metadata.on('metadata', function () {
t.fail('No "metadata" event should fire') t.fail('No "metadata" event should fire')
}) })
wire1.ut_metadata.onMessage = () => { wire1.ut_metadata.onMessage = function () {
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 // No messages should be sent because wire1 never sent metadata_size in the
// in the extended handshake, so he doesn't have metadata // extended handshake, so he doesn't have metadata
} }
wire2.ut_metadata.on('warning', () => { wire2.ut_metadata.on('warning', function (err) {
t.pass('got warning about peer missing metadata') t.pass('got warning about peer missing metadata')
}) })
wire2.on('handshake', (infoHash, peerId, extensions) => { wire2.on('handshake', function (infoHash, peerId, extensions) {
wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) wire2.handshake(parsedTorrent.infoHash, id2)
}) })
wire2.on('extended', ext => { wire2.on('extended', function (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') {
@ -89,163 +87,35 @@ test('fetch() from peer without metadata', t => {
} }
}) })
wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) wire1.handshake(parsedTorrent.infoHash, id1)
}) })
test('fetch when peer gets metadata later (setMetadata)', t => { test('discard invalid metadata', function (t) {
t.plan(3)
const wire1 = new Protocol()
const wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1)
wire1.use(utMetadata()) // wire1 starts without metadata
process.nextTick(() => {
// wire1 gets metadata later
wire1.ut_metadata.setMetadata(leavesMetadata.torrent)
process.nextTick(() => {
// wire2 does not start with metadata,
// but connects to wire1 after it gets metadata
wire2.use(utMetadata())
wire2.ut_metadata.fetch()
wire2.ut_metadata.on('metadata', _metadata => {
// got metadata!
t.equal(
_metadata.toString('hex'),
bencode.encode({
info: bencode.decode(leavesMetadata.torrent).info
}).toString('hex')
)
})
wire2.on('handshake', (infoHash, peerId, extensions) => {
wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2)
})
wire2.on('extended', ext => {
if (ext === 'handshake') {
t.pass('got extended handshake')
} else if (ext === 'ut_metadata') {
t.pass('got extended ut_metadata message')
// this is emitted for consistency's sake, but it's ignored
// by the user since the ut_metadata package handles the
// complexities internally
} else {
t.fail('unexpected handshake type')
}
})
wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1)
})
})
})
test('fetch() large torrent', t => {
t.plan(4)
const wire1 = new Protocol()
const wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1)
wire1.use(utMetadata(sintel.torrent)) // wire1 already has metadata
wire2.use(utMetadata()) // wire2 does not
wire2.ut_metadata.fetch()
wire2.ut_metadata.on('metadata', _metadata => {
// got metadata!
t.equal(
_metadata.toString('hex'),
bencode.encode({
info: bencode.decode(sintel.torrent).info
}).toString('hex')
)
})
wire2.on('handshake', (infoHash, peerId, extensions) => {
wire2.handshake(sintel.parsedTorrent.infoHash, id2)
})
wire2.on('extended', ext => {
if (ext === 'handshake') {
t.pass('got extended handshake')
} else if (ext === 'ut_metadata') {
// note: this should get called twice,
// once for each block of the sintel metadata
t.pass('got extended ut_metadata message')
// this is emitted for consistency's sake, but it's ignored
// by the user since the ut_metadata package handles the
// complexities internally
} else {
t.fail('unexpected handshake type')
}
})
wire1.handshake(sintel.parsedTorrent.infoHash, id1)
})
test('discard invalid metadata', t => {
t.plan(1) t.plan(1)
const wire1 = new Protocol() var wire1 = new Protocol()
const wire2 = new Protocol() var wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1) wire1.pipe(wire2).pipe(wire1)
const invalidMetadata = leavesMetadata.torrent.slice(0) var invalidMetadata = metadata.slice(0)
invalidMetadata[55] = 65 // mess up a byte in the info block invalidMetadata[0] = 99 // mess up the first byte of the metadata
wire1.use(utMetadata(invalidMetadata)) wire1.use(ut_metadata(invalidMetadata))
wire2.use(utMetadata()) wire2.use(ut_metadata())
wire2.ut_metadata.fetch() wire2.ut_metadata.fetch()
wire2.ut_metadata.on('metadata', () => { wire2.ut_metadata.on('metadata', function () {
t.fail('No "metadata" event should fire') t.fail('No "metadata" event should fire')
}) })
wire2.ut_metadata.on('warning', () => { wire2.ut_metadata.on('warning', function (err) {
t.pass('got warning because peer sent reject too much') t.pass('got warning because peer sent reject too much')
}) })
wire2.on('handshake', (infoHash, peerId, extensions) => { wire2.on('handshake', function (infoHash, peerId, extensions) {
wire2.handshake(leavesMetadata.parsedTorrent.infoHash, id2) wire2.handshake(parsedTorrent.infoHash, id2)
}) })
wire1.handshake(leavesMetadata.parsedTorrent.infoHash, id1) wire1.handshake(parsedTorrent.infoHash, id1)
})
test('stop receiving data after cancel', t => {
t.plan(1)
const wire1 = new Protocol()
const wire2 = new Protocol()
wire1.pipe(wire2).pipe(wire1)
wire1.use(utMetadata(sintel.torrent))
wire2.use(utMetadata())
wire2.ut_metadata.once('metadata', () => {
t.fail('No "metadata" event should fire')
})
wire2.once('handshake', (infoHash, peerId, extensions) => {
wire2.handshake(sintel.parsedTorrent.infoHash, id2)
wire2.ut_metadata.fetch()
})
wire2.on('extended', ext => {
if (ext === 'ut_metadata') {
wire2.ut_metadata.cancel()
}
})
wire1.handshake(sintel.parsedTorrent.infoHash, id1)
process.nextTick(() => t.pass('no metadata received'))
}) })

View File

@ -1,7 +0,0 @@
{
"name": "test",
"version": "0.0.0",
"browserify": {
"transform": ["brfs"]
}
}

Binary file not shown.