diff options
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | .npmignore | 1 | ||||
-rw-r--r-- | index.js | 32 | ||||
-rw-r--r-- | lib/bittorrent-dht/.npmignore | 1 | ||||
-rw-r--r-- | lib/bittorrent-dht/README.md | 4 | ||||
-rw-r--r-- | lib/bittorrent-dht/choose-best.js | 34 | ||||
-rw-r--r-- | lib/bittorrent-dht/example.js | 27 | ||||
-rw-r--r-- | lib/bittorrent-dht/index.js | 153 | ||||
-rw-r--r-- | lib/bittorrent-dht/package.json | 19 | ||||
-rw-r--r-- | package.json | 7 |
10 files changed, 264 insertions, 21 deletions
@@ -1,3 +1,6 @@ +bundle.js +tmp + lib-cov *.seed *.log @@ -12,6 +15,4 @@ logs results npm-debug.log -node_modules - -tmp
\ No newline at end of file +node_modules
\ No newline at end of file @@ -1,2 +1 @@ node_modules -bundle.js
\ No newline at end of file @@ -1,5 +1,21 @@ var isChromeApp = !!(window.chrome && chrome.app && chrome.app.runtime) +if (isChromeApp) { + console.log('This is a Chrome App') +} + + +var DHT = require('./lib/bittorrent-dht') +var leaves = 'D2474E86C95B19B8BCFDB92BC12C9D44667CFA36' + +var dht = new DHT(leaves) +dht.on('peer', function (peer) { + console.log(peer) +}) +dht.findPeers(300) + + +// Send UDP packet to echo server // var socket = require('./socket') // var sock = new socket.UDPSocket('localhost', 54244) @@ -9,19 +25,3 @@ var isChromeApp = !!(window.chrome && chrome.app && chrome.app.runtime) // sock.write('hello') // }) - - - -// require the core node events module -var EventEmitter = require('events').EventEmitter - -//create a new event emitter -var emitter = new EventEmitter() - -// set up a listener for the event -emitter.on('pizza', function(message){ - console.log(message); -}); - -// emit an event -emitter.emit('pizza', 'pizza is extremely yummy');
\ No newline at end of file diff --git a/lib/bittorrent-dht/.npmignore b/lib/bittorrent-dht/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/lib/bittorrent-dht/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/lib/bittorrent-dht/README.md b/lib/bittorrent-dht/README.md new file mode 100644 index 0000000..7708f9e --- /dev/null +++ b/lib/bittorrent-dht/README.md @@ -0,0 +1,4 @@ +# bittorrent-dht + +a lightweight implementation of the BitTorrent DHT + diff --git a/lib/bittorrent-dht/choose-best.js b/lib/bittorrent-dht/choose-best.js new file mode 100644 index 0000000..6a56e6a --- /dev/null +++ b/lib/bittorrent-dht/choose-best.js @@ -0,0 +1,34 @@ + +var DHT = require('./'); +var async = require('async'); + +// 8217,4622,7171,4338,10334,2897,4903 -> 42482 +// 5275,2300,7371,2982,2250,3455,2788 -> 26421 + +var globalStart = Date.now(); +async.each( + [ + "99cf7a5ae561b7a55072eb07645277b6df0ef902", + "2b96d97e1de415f0f308f1cf70e9a18567f4a7f9", + //"ee08430b97ef922bbf123aebf07b43fed29e992d" + ], + function(hash, callback) { + var dht = new DHT(new Buffer(hash, 'hex')); + dht.findPeers(300); + + var peers = [], start = Date.now(), i = 0; + dht.on('peer', function(peer) { + //console.log(hash, ++i); // DEBUG + peers.push(peer); + + if (peers.length != 300) return; + console.log("\n\n\nready: "+(Date.now() - start)+"\n\n"); // 2 - 3 seconds + //setTimeout(callback, 4000); + callback(); + }); + }, + function(err) + { + console.log("Everything finished "+(Date.now() - globalStart)); + } +); diff --git a/lib/bittorrent-dht/example.js b/lib/bittorrent-dht/example.js new file mode 100644 index 0000000..d6fd336 --- /dev/null +++ b/lib/bittorrent-dht/example.js @@ -0,0 +1,27 @@ + +var DHT = require('./'); +var async = require('async'); + +async.eachSeries( +//async.each( + [ + 'e756b6ed7f1f647db2ea7e153e2fdc6226218a1f', + // 'e756b6ed7f1f647db2ea7e153e2fdc6226218a1f', + '948cd498ab5acdc0a61dee8b012eb93ae231b2ff', + ], + function(hash, callback) { + var dht = new DHT(new Buffer(hash, 'hex')); + dht.findPeers(300); + + var peers = [], start = Date.now(), i = 0; + dht.on('peer', function(peer) { + //console.log(hash, ++i); // DEBUG + peers.push(peer); + + if (peers.length != 300) return; + console.log("\n\n\nready: "+(Date.now() - start)+"\n\n"); // 2 - 3 seconds + //setTimeout(callback, 4000); + callback(); + }); + } +); diff --git a/lib/bittorrent-dht/index.js b/lib/bittorrent-dht/index.js new file mode 100644 index 0000000..7ff51f8 --- /dev/null +++ b/lib/bittorrent-dht/index.js @@ -0,0 +1,153 @@ +module.exports = DHT + +var EventEmitter = require('events').EventEmitter +var is = require('core-util-is') // added in Node 0.12 + +// TODO? +var bncode = require('bncode') +var crypto = require('crypto') +var bagpipe = require('bagpipe') +var fs = require('fs') +var compact2string = require('compact2string') + +var CONNECTION_TIMEOUT = 10000 +var HANDSHAKE_TIMEOUT = 5000 +var RECONNECT = 5000 +var MAX_NODES = 5000 +var MAX_PARALLEL = 10 +var BOOTSTRAP_NODES = [ + 'dht.transmissionbt.com:6881', + 'router.bittorrent.com:6881', + 'router.utorrent.com:6881' +] + +var randomId = function() { + var bytes = crypto.randomBytes(2000) + var offset = 0 + return function() { + var id = bytes.slice(offset, offset + 20) + offset = (offset + 20) % bytes.length + return id + } +}() + +var parseNodeInfo = function(compact) { + try { + var nodes = [] + for (var i = 0; i < compact.length; i += 26) { + nodes.push(compact2string(compact.slice(i+20, i+26))) + } + return nodes + } catch(err) { + return [] + } +} + +var parsePeerInfo = function(list) { + try { + return list.map(compact2string) + } catch (err) { + return [] + } +} + +var socket, requestId = 0, initialNodes = [], pendingRequests = { } + +/** + * Create a new DHT + * @param {string|Buffer} infoHash + */ +function DHT (infoHash) { + var self = this + EventEmitter.call(self) + + if (is.isString(infoHash)) { + infoHash = new Buffer(infoHash, 'hex') + } else if (!is.isBuffer(infoHash)) { + throw new Error('DHT() requires string or buffer infoHash') + } + + var node = function(addr) { + initialNodes.push(addr) + if (self.nodes[addr]) return + if (self.missing) return self.query(addr) + if (self.queue.length < 50) self.queue.push(addr) + } + var peer = function(addr) { + if (self.peers[addr]) return + self.peers[addr] = true + self.missing = Math.max(0, self.missing-1) + process.nextTick(function() { self.emit('peer', addr) }) // if the query is satisfied now, the socket must be closed before a new query is started + } + + this.nodes = {} + this.peers = {} + //this.queue = [].concat(BOOTSTRAP_NODES) + this.queue = initialNodes.length ? [].concat(initialNodes.slice( initialNodes.length - MAX_PARALLEL )) : [].concat(BOOTSTRAP_NODES) + //this.queue = initialNodes.length ? [].concat(initialNodes/*.slice( initialNodes.length - 10 )*/) : [].concat(BOOTSTRAP_NODES) + this.nodesCount = 0 + this.missing = 0 + this.infoHash = infoHash + this.nodeId = randomId() + this.requestId = ++requestId + this.message = bncode.encode({t:this.requestId.toString(),y:'q',q:'get_peers',a:{id:this.nodeId,info_hash:this.infoHash}}) + this.parallelLimit = new bagpipe(MAX_PARALLEL) + + pendingRequests[self.requestId] = 1 + + this.stop = function() + { + delete pendingRequests[self.requestId] + if (Object.keys(pendingRequests).length) return + self.socket && self.socket.close() + self.socket = socket = null + } + + this.socket = socket = socket || dgram.createSocket('udp4') + this.socket.on('message', function(message, remote) { + self.nodes[remote.address+':'+remote.port] = true + + try { + message = bncode.decode(message) + } catch (err) { + return + } + + if (! (message.t.toString() == self.requestId)) + return + + var r = message && message.r + var nodes = r && r.nodes || [] + var values = r && r.values || [] + + parsePeerInfo(values).forEach(peer) + parseNodeInfo(nodes).forEach(node) + + if (! self.missing) self.stop() + }) +} + +DHT.prototype.__proto__ = EventEmitter.prototype + +DHT.prototype.query = function(addr) { + if (Object.keys(this.nodes).length > MAX_NODES) return + console.log(addr) + + var self = this, + sendMessage = function(cb) { self.socket && self.socket.send(self.message, 0, self.message.length, addr.split(':')[1], addr.split(':')[0], cb) } + this.parallelLimit.push(sendMessage, function() { }) +} + +DHT.prototype.findPeers = function(num, timeout) { + this.missing += (num || 1) + while (this.queue.length) this.query(this.queue.pop()) + timeout && setTimeout(this.stop, timeout) +} + +DHT.prototype.close = function() { + this.socket.close() +} + +DHT.prototype.__defineGetter__('peersFound', function() { return Object.keys(this.peers).length }) +DHT.prototype.__defineGetter__('nodesFound', function() { return Object.keys(this.nodes).length }) +DHT.prototype.__defineGetter__('queued', function() { return 0 }) //TODO diff --git a/lib/bittorrent-dht/package.json b/lib/bittorrent-dht/package.json new file mode 100644 index 0000000..e906554 --- /dev/null +++ b/lib/bittorrent-dht/package.json @@ -0,0 +1,19 @@ +{ + "name": "bittorrent-dht", + "version": "0.1.1", + "description": "a lightweight, optimized implementation of the BitTorrent DHT", + "dependencies": { + "bncode": "~0.2.3", + "compact2string": "~1.0.0", + "bagpipe": "0.3.0", + "async": "0.2.9" + }, + "readme": "# bittorrent-dht\n\na lightweight implementation of the BitTorrent DHT\n\n", + "readmeFilename": "README.md", + "_id": "bittorrent-dht@0.1.1", + "dist": { + "shasum": "bbe79897a7354e251e0289dfadcc71f17fabc495" + }, + "_resolved": "git://github.com/bmcmahen/bittorrent-dht.git#9d5a6278ac51cdc6a601428747079f7b589740ef", + "_from": "bittorrent-dht@git://github.com/bmcmahen/bittorrent-dht.git" +} diff --git a/package.json b/package.json index adc5f67..fdd9efc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,12 @@ "main": "index.js", "dependencies": { "itty-bitty-torrent": "~0.1.2", - "browserify": "~2.34.1" + "browserify": "~2.34.1", + "core-util-is": "~1.0.0", + "bncode": "~0.2.3", + "bagpipe": "~0.3.5", + "compact2string": "~1.0.0", + "async": "~0.2.9" }, "devDependencies": {}, "scripts": { |