'use strict' const dgram = require('dgram') const Emiter = require('events') const bencode = require('bencode') const {Table, Node} = require('./table') const Token = require('./token') const bootstraps = [{ address: 'router.bittorrent.com', port: 6881 }, { address: 'router.utorrent.com', port: 6881 }, { address: 'dht.transmissionbt.com', port: 6881 }, { address: 'dht.aelitis.com', port: 6881 }] function isValidPort(port) { return port > 0 && port < (1 << 16) } function generateTid() { return parseInt(Math.random() * 99).toString() } class Spider extends Emiter { constructor(client) { super() const options = arguments.length? arguments[0]: {} this.udp = dgram.createSocket('udp4') this.table = new Table(options.tableCaption || 1000) this.bootstraps = options.bootstraps || bootstraps this.token = new Token() this.client = client this.ignore = false; // ignore all requests this.walkInterval = 5; } send(message, address) { const data = bencode.encode(message) this.udp.send(data, 0, data.length, address.port, address.address) } findNode(id, address) { const message = { t: generateTid(), y: 'q', q: 'find_node', a: { id: id, target: Node.generateID() } } this.send(message, address) } join() { this.bootstraps.forEach((bootstrap) => { this.findNode(this.table.id, bootstrap) }) } walk() { if(!this.client || this.client.isIdle()) { if(!this.ignore) { const node = this.table.shift() if (node) { this.findNode(Node.neighbor(node.id, this.table.id), {address: node.address, port: node.port}) } } } setTimeout(()=>this.walk(), this.walkInterval) } onFoundNodes(data) { const nodes = Node.decodeNodes(data) nodes.forEach((node) => { if (node.id != this.table.id && isValidPort(node.port)) { this.table.add(node) } }) this.emit('nodes', nodes) } onFindNodeRequest(message, address) { const {t: tid, a: {id: nid, target: infohash}} = message if (tid === undefined || target.length != 20 || nid.length != 20) { return } this.send({ t: tid, y: 'r', r: { id: Node.neighbor(nid, this.table.id), nodes: Node.encodeNodes(this.table.first()) } }, address) } onGetPeersRequest(message, address) { const {t: tid, a: {id: nid, info_hash: infohash}} = message if (tid === undefined || infohash.length != 20 || nid.length != 20) { return } this.send({ t: tid, y: 'r', r: { id: Node.neighbor(nid, this.table.id), nodes: Node.encodeNodes(this.table.first()), token: this.token.token } }, address) this.emit('unensureHash', infohash.toString('hex').toUpperCase()) } onAnnouncePeerRequest(message, address) { let {t: tid, a: {info_hash: infohash, token: token, id: id, implied_port: implied, port: port}} = message if (!tid) return if (!this.token.isValid(token)) return port = (implied != undefined && implied != 0) ? address.port : (port || 0) if (!isValidPort(port)) return this.send({ t: tid, y: 'r', r: { id: Node.neighbor(id, this.table.id) } }, address) let addressPair = { address: address.address, port: port }; this.emit('ensureHash', infohash.toString('hex').toUpperCase(), addressPair) if(this.client && !this.ignore) { this.client.add(addressPair, infohash); } } onPingRequest(message, address) { this.send({ t: message.t, y: 'r', r: { id: Node.neighbor(message.a.id, this.table.id) } }, address) } parse(data, address) { try { const message = bencode.decode(data) if (message.y.toString() == 'r' && message.r.nodes) { this.onFoundNodes(message.r.nodes) } else if (message.y.toString() == 'q') { switch(message.q.toString()) { case 'get_peers': this.onGetPeersRequest(message, address) break case 'announce_peer': this.onAnnouncePeerRequest(message, address) break case 'find_node': this.onFindNodeRequest(message, address) break case 'ping': this.onPingRequest(message, address) break } } } catch (err) {} } listen(port) { this.udp.bind(port) this.udp.on('listening', () => { console.log(`Listen on ${this.udp.address().address}:${this.udp.address().port}`) }) this.udp.on('message', (data, addr) => { this.parse(data, addr) }) this.udp.on('error', (err) => {}) setInterval(() => { if(!this.client || this.client.isIdle()) { this.join() } }, 3000) this.join() this.walk() } } module.exports = Spider