248 lines
6.1 KiB
JavaScript
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;
|