продолжаем вносить все
This commit is contained in:
247
lib/wire.js
Normal file
247
lib/wire.js
Normal file
@ -0,0 +1,247 @@
|
||||
'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;
|
Reference in New Issue
Block a user