'use strict'; var stream = require('stream'); var crypto = require('crypto'); var util = require('util'); var BitField = require('bitfield'); var bencode = require('bencode'); var {Node} = require('./table'); var BT_RESERVED = new Buffer([0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01]); var BT_PROTOCOL = new Buffer('BitTorrent protocol'); var PIECE_LENGTH = Math.pow(2, 14); var MAX_METADATA_SIZE = 10000000; var BITFIELD_GROW = 1000; var EXT_HANDSHAKE_ID = 0; var BT_MSG_ID = 20; var Wire = function(infohash) { stream.Duplex.call(this); this._bitfield = new BitField(0, { grow: BITFIELD_GROW }); this._infohash = infohash; this._buffer = []; this._bufferSize = 0; this._next = null; this._nextSize = 0; this._metadata = null; this._metadataSize = null; this._numPieces = 0; this._ut_metadata = null; this._onHandshake(); } util.inherits(Wire, stream.Duplex); Wire.prototype._onMessageLength = function (buffer) { if (buffer.length >= 4) { var length = buffer.readUInt32BE(0); if (length > 0) { this._register(length, this._onMessage) } } }; Wire.prototype._onMessage = function (buffer) { this._register(4, this._onMessageLength) if (buffer[0] == BT_MSG_ID) { this._onExtended(buffer.readUInt8(1), buffer.slice(2)); } }; Wire.prototype._onExtended = function(ext, buf) { if (ext === 0) { try { this._onExtHandshake(bencode.decode(buf)); } catch (err) { this._fail(); } } else { this._onPiece(buf); } }; Wire.prototype._register = function (size, next) { this._nextSize = size; this._next = next; }; Wire.prototype.end = function() { stream.Duplex.prototype.end.apply(this, arguments); }; Wire.prototype._onHandshake = function() { this._register(1, function(buffer) { if (buffer.length == 0) { this.end(); return this._fail(); } var pstrlen = buffer.readUInt8(0); this._register(pstrlen + 48, function(handshake) { var protocol = handshake.slice(0, pstrlen); if (protocol.toString() !== BT_PROTOCOL.toString()) { this.end(); this._fail(); return; } handshake = handshake.slice(pstrlen); if ( !!(handshake[5] & 0x10) ) { this._register(4, this._onMessageLength); this._sendExtHandshake(); } else { this._fail(); } }.bind(this)); }.bind(this)); }; Wire.prototype._onExtHandshake = function(extHandshake) { if (!extHandshake.metadata_size || !extHandshake.m.ut_metadata || extHandshake.metadata_size > MAX_METADATA_SIZE) { this._fail(); return; } this._metadataSize = extHandshake.metadata_size; this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH); this._ut_metadata = extHandshake.m.ut_metadata; this._requestPieces(); } Wire.prototype._requestPieces = function() { this._metadata = new Buffer(this._metadataSize); for (var piece = 0; piece < this._numPieces; piece++) { this._requestPiece(piece); } }; Wire.prototype._requestPiece = function(piece) { var msg = Buffer.concat([ new Buffer([BT_MSG_ID]), new Buffer([this._ut_metadata]), bencode.encode({msg_type: 0, piece: piece}) ]); this._sendMessage(msg); }; Wire.prototype._sendPacket = function(packet) { this.push(packet); }; Wire.prototype._sendMessage = function(msg) { var buf = new Buffer(4); buf.writeUInt32BE(msg.length, 0); this._sendPacket(Buffer.concat([buf, msg])); }; Wire.prototype.sendHandshake = function() { var peerID = Node.generateID(); var packet = Buffer.concat([ new Buffer([BT_PROTOCOL.length]), BT_PROTOCOL, BT_RESERVED, this._infohash, peerID ]); this._sendPacket(packet); }; Wire.prototype._sendExtHandshake = function() { var msg = Buffer.concat([ new Buffer([BT_MSG_ID]), new Buffer([EXT_HANDSHAKE_ID]), bencode.encode({m: {ut_metadata: 1}}) ]); this._sendMessage(msg); }; Wire.prototype._onPiece = function(piece) { var dict, trailer; try { var str = piece.toString(); var trailerIndex = str.indexOf('ee') + 2; dict = bencode.decode(str.substring(0, trailerIndex)); trailer = piece.slice(trailerIndex); } catch (err) { this._fail(); return; } if (dict.msg_type != 1) { this._fail(); return; } if (trailer.length > PIECE_LENGTH) { this._fail(); return; } trailer.copy(this._metadata, dict.piece * PIECE_LENGTH); this._bitfield.set(dict.piece); this._checkDone(); }; Wire.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 } this._onDone(this._metadata); }; Wire.prototype._onDone = function(metadata) { try { var info = bencode.decode(metadata).info; if (info) { metadata = bencode.encode(info); } } catch (err) { this._fail(); return; } var infohash = crypto.createHash('sha1').update(metadata).digest('hex'); if (this._infohash.toString('hex') != infohash ) { this._fail(); return false; } this.emit('metadata', {info: bencode.decode(metadata, 'utf8')}, this._infohash); }; Wire.prototype._fail = function() { this.emit('fail'); }; Wire.prototype._write = function (buf, encoding, next) { this._bufferSize += buf.length; this._buffer.push(buf); while (this._bufferSize >= this._nextSize) { var buffer = Buffer.concat(this._buffer); this._bufferSize -= this._nextSize; this._buffer = this._bufferSize ? [buffer.slice(this._nextSize)] : []; this._next(buffer.slice(0, this._nextSize)); } next(null); } Wire.prototype._read = function() { // do nothing }; module.exports = Wire;