rats-search/lib/wire.js
2016-12-31 06:32:56 +03:00

248 lines
6.1 KiB
JavaScript

'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;