UNPKG

@firaenix/bittorrent-protocol

Version:

Simple, robust, BitTorrent peer wire protocol implementation

1,058 lines (1,057 loc) 272 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.EventExtension = exports.Extension = void 0; var tiny_typed_emitter_1 = require("tiny-typed-emitter"); var Extension = /** @class */ (function () { function Extension(wire) { var _this = this; this.sendExtendedMessage = function (data) { _this.wire.extended(_this.name, data); }; this.wire = wire; } return Extension; }()); exports.Extension = Extension; var EventExtension = /** @class */ (function (_super) { __extends(EventExtension, _super); function EventExtension(wire) { var _this = _super.call(this) || this; _this.sendExtendedMessage = function (data) { _this.wire.extended(_this.name, data); }; _this.wire = wire; return _this; } return EventExtension; }(tiny_typed_emitter_1.TypedEmitter)); exports.EventExtension = EventExtension; },{"tiny-typed-emitter":40}],2:[function(require,module,exports){ (function (Buffer){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spread = (this && this.__spread) || function () { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Wire = void 0; var unordered_array_remove_1 = __importDefault(require("unordered-array-remove")); var bencode_1 = __importDefault(require("bencode")); var bitfield_1 = __importDefault(require("bitfield")); var debug_1 = __importDefault(require("debug")); var randombytes_1 = __importDefault(require("randombytes")); var speedometer_1 = __importDefault(require("speedometer")); var readable_stream_1 = __importDefault(require("readable-stream")); var PeerMessages_1 = require("./models/PeerMessages"); var PieceRequest_1 = require("./models/PieceRequest"); var ParseRequest_1 = require("./models/ParseRequest"); var debug = debug_1.default('firaenix-bittorrent-protocol'); var BITFIELD_GROW = 400000; var KEEP_ALIVE_TIMEOUT = 55000; var Wire = /** @class */ (function (_super) { __extends(Wire, _super); function Wire(name) { var _this = _super.call(this) || this; _this._parseRequests = []; _this._handshakeSuccess = false; _this._extendedHandshakeSuccess = false; _this.parseStream = function () { var parser = _this._parseRequests.shift(); while (!parser) { _this._debug('Waiting for parser'); return; } _this._debug('Waiting for', parser === null || parser === void 0 ? void 0 : parser.parserSize, 'bytes for', parser === null || parser === void 0 ? void 0 : parser.parserName); if (_this._buffer.length < (parser === null || parser === void 0 ? void 0 : parser.parserSize)) { _this._parseRequests.unshift(parser); return; } var buffer = _this._buffer.slice(0, parser === null || parser === void 0 ? void 0 : parser.parserSize); _this._buffer = _this._buffer.slice(parser === null || parser === void 0 ? void 0 : parser.parserSize); _this._debug('Sending', parser.parserName, parser === null || parser === void 0 ? void 0 : parser.parserSize, 'bytes'); parser === null || parser === void 0 ? void 0 : parser.callback(buffer); return _this.parseStream(); }; /** * Handle the first 4 bytes of a message, to determine the length of bytes that must be * waited for in order to have the whole message. * @param {Buffer} buffer */ _this._onMessageLength = function (buffer) { var length = buffer.readUInt32BE(0); if (length > 0) { _this._parse(length, 'onMessage', _this._onMessage); } else { _this._onKeepAlive(); _this._parse(4, 'onMessageLength', _this._onMessageLength); } }; /** * Handle a message from the remote peer. * @param {Buffer} buffer */ _this._onMessage = function (buffer) { return __awaiter(_this, void 0, void 0, function () { var messageFlag, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: this._parse(4, '_onMessage.onMessageLength', this._onMessageLength); messageFlag = buffer[0]; _a = messageFlag; switch (_a) { case PeerMessages_1.MessageFlags.Choke: return [3 /*break*/, 1]; case PeerMessages_1.MessageFlags.Unchoke: return [3 /*break*/, 3]; case PeerMessages_1.MessageFlags.Interested: return [3 /*break*/, 5]; case PeerMessages_1.MessageFlags.NotInterested: return [3 /*break*/, 7]; case PeerMessages_1.MessageFlags.Have: return [3 /*break*/, 9]; case PeerMessages_1.MessageFlags.Bitfield: return [3 /*break*/, 11]; case PeerMessages_1.MessageFlags.Request: return [3 /*break*/, 13]; case PeerMessages_1.MessageFlags.Piece: return [3 /*break*/, 15]; case PeerMessages_1.MessageFlags.Cancel: return [3 /*break*/, 17]; case 9: return [3 /*break*/, 19]; case PeerMessages_1.MessageFlags.Extended: return [3 /*break*/, 20]; } return [3 /*break*/, 21]; case 1: return [4 /*yield*/, this._onChoke()]; case 2: return [2 /*return*/, _b.sent()]; case 3: return [4 /*yield*/, this._onUnchoke()]; case 4: return [2 /*return*/, _b.sent()]; case 5: return [4 /*yield*/, this._onInterested()]; case 6: return [2 /*return*/, _b.sent()]; case 7: return [4 /*yield*/, this._onUninterested()]; case 8: return [2 /*return*/, _b.sent()]; case 9: return [4 /*yield*/, this._onHave(buffer.readUInt32BE(1))]; case 10: return [2 /*return*/, _b.sent()]; case 11: return [4 /*yield*/, this._onBitField(buffer.slice(1))]; case 12: return [2 /*return*/, _b.sent()]; case 13: return [4 /*yield*/, this._onRequest(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9))]; case 14: return [2 /*return*/, _b.sent()]; case 15: return [4 /*yield*/, this._onPiece(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.slice(9))]; case 16: return [2 /*return*/, _b.sent()]; case 17: return [4 /*yield*/, this._onCancel(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9))]; case 18: return [2 /*return*/, _b.sent()]; case 19: return [2 /*return*/, this._onPort(buffer.readUInt16BE(1))]; case 20: return [2 /*return*/, this._onExtended(buffer.readUInt8(1), buffer.slice(2))]; case 21: this._debug('got unknown message'); return [2 /*return*/, this.emit('unknown_message', buffer)]; } }); }); }; _this.wireName = name; _this._debugId = name || randombytes_1.default(4).toString('hex'); _this._debug('new wire'); _this.peerId = undefined; // remote peer id (hex string) _this.peerIdBuffer = undefined; // remote peer id (buffer) _this.type = null; // connection type ('webrtc', 'tcpIncoming', 'tcpOutgoing', 'webSeed') _this.amChoking = true; // are we choking the peer? _this.amInterested = false; // are we interested in the peer? _this.peerChoking = true; // is the peer choking us? _this.peerInterested = false; // is the peer interested in us? // The largest torrent that I know of (the Geocities archive) is ~641 GB and has // ~41,000 pieces. Therefore, cap bitfield to 10x larger (400,000 bits) to support all // possible torrents but prevent malicious peers from growing bitfield to fill memory. _this.peerPieces = new bitfield_1.default(0, { grow: BITFIELD_GROW }); _this.peerExtensions = { dht: false, extended: false }; _this.requests = []; // outgoing _this.peerRequests = []; // incoming _this.extendedMapping = {}; // number -> string, ex: 1 -> 'ut_metadata' _this.peerExtendedMapping = {}; // string -> number, ex: 9 -> 'ut_metadata' // The extended handshake to send, minus the "m" field, which gets automatically // filled from `this.extendedMapping` _this.extendedHandshake = { m: {}, exts: {} }; _this.peerExtendedHandshake = { m: {}, exts: {} }; // remote peer's extended handshake _this._ext = {}; // string -> function, ex 'ut_metadata' -> ut_metadata() _this._nextExt = 1; _this.uploaded = 0; _this.downloaded = 0; _this.uploadSpeed = speedometer_1.default(); _this.downloadSpeed = speedometer_1.default(); _this._keepAliveInterval = undefined; _this._timeout = undefined; _this._timeoutMs = 0; _this.destroyed = false; // was the wire ended by calling `destroy`? _this._finished = false; _this._buffer = Buffer.alloc(0); // incomplete message data _this.once('finish', function () { return _this._onFinish(); }); _this._parseHandshake(); return _this; } Wire.prototype.once = function (event, listener) { return _super.prototype.once.call(this, event, listener); }; Wire.prototype.on = function (event, listener) { return _super.prototype.on.call(this, event, listener); }; Wire.prototype.off = function (event, listener) { return _super.prototype.off.call(this, event, listener); }; Wire.prototype.emit = function (event) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return _super.prototype.emit.apply(this, __spread([event], args)); }; /** * Set whether to send a "keep-alive" ping (sent every 55s) * @param {boolean} enable */ Wire.prototype.setKeepAlive = function (enable) { var _this = this; this._debug('setKeepAlive %s', enable); clearInterval(this._keepAliveInterval); if (enable === false) return; this._keepAliveInterval = setInterval(function () { _this.keepAlive(); }, KEEP_ALIVE_TIMEOUT); }; /** * Set the amount of time to wait before considering a request to be "timed out" * @param {number} ms * @param {boolean=} unref (should the timer be unref'd? default: false) */ Wire.prototype.setTimeout = function (ms, unref) { this._debug('setTimeout ms=%d unref=%s', ms, unref); this._clearTimeout(); this._timeoutMs = ms; this._timeoutUnref = !!unref; this._updateTimeout(); }; Wire.prototype.destroy = function () { if (this.destroyed) { return this; } this.destroyed = true; this._debug('destroy'); this.emit('close'); this.end(); return this; }; Wire.prototype.end = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } this._debug('end'); this._onUninterested(); this._onChoke(); _super.prototype.end.apply(this, __spread(args)); }; /** * Use the specified protocol extension. * @param {function} Extension */ Wire.prototype.use = function (newExtension) { var ext = this._nextExt; var handler = newExtension(this); var name = handler.name; if (!name) { throw new Error('Extension class requires a "name" property'); } this._debug('use extension.name=%s', name); // eslint-disable-next-line @typescript-eslint/no-empty-function function noop() { } if (typeof handler.onHandshake !== 'function') { handler.onHandshake = noop; } if (typeof handler.onExtendedHandshake !== 'function') { handler.onExtendedHandshake = noop; } if (typeof handler.onMessage !== 'function') { handler.onMessage = noop; } this.extendedMapping[ext] = name; this._ext[name] = handler; this[name] = handler; this._nextExt += 1; }; // // OUTGOING MESSAGES // /** * Message "keep-alive": <len=0000> */ Wire.prototype.keepAlive = function () { this._debug('keep-alive'); this._push(PeerMessages_1.MessageBuffers.MESSAGE_KEEP_ALIVE); }; /** * Message: "handshake" <pstrlen><pstr><reserved><info_hash><peer_id> * @param {Buffer|string} infoHash (as Buffer or *hex* string) * @param {Buffer|string} peerId * @param {Object} extensions */ Wire.prototype.handshake = function (infoHash, peerId, extensions) { var infoHashBuffer; var peerIdBuffer; if (typeof infoHash === 'string') { infoHash = infoHash.toLowerCase(); infoHashBuffer = Buffer.from(infoHash, 'hex'); } else { infoHashBuffer = infoHash; infoHash = infoHashBuffer.toString('hex'); } if (typeof peerId === 'string') { peerIdBuffer = Buffer.from(peerId, 'hex'); } else { peerIdBuffer = peerId; peerId = peerIdBuffer.toString('hex'); } if (!infoHashBuffer || !infoHashBuffer.length) { var err = new Error('infoHash must be specified'); this.emit('error', err); throw err; } if (infoHashBuffer.length > 255) { var err = new Error("infoHash must be smaller than 255 bytes long, it is currently " + infoHashBuffer.length); this.emit('error', err); throw err; } if (peerIdBuffer.length !== 20) { var err = new Error("peerId MUST have length 20, length is " + peerIdBuffer.length); this.emit('error', err); throw err; } this._debug('handshake i=%s p=%s exts=%o', infoHash, peerId, extensions); var reserved = Buffer.from(PeerMessages_1.MessageBuffers.MESSAGE_RESERVED); // enable extended message reserved[5] |= 0x10; if (extensions && extensions.dht) reserved[7] |= 1; // Prepend length of infoHash as a single byte - dont care about overflows, for now must be only 255 bits large to represent a 255 byte hash maximum length. // Going off the assumption that by the time you need 255 bytes for a hash, we will move to a more effective hashing algorithm. this._push(Buffer.concat([PeerMessages_1.MessageBuffers.MESSAGE_PROTOCOL, reserved, Buffer.from([infoHashBuffer.length]), PeerMessages_1.MessageBuffers.INFOHASH_SPLIT, infoHashBuffer, peerIdBuffer])); this._handshakeSent = true; if (this.peerExtensions.extended && !this._extendedHandshakeSent) { // Peer's handshake indicated support already // (incoming connection) this._sendExtendedHandshake(); } }; /* Peer supports BEP-0010, send extended handshake. * * This comes after the 'handshake' event to give the user a chance to populate * `this.extendedHandshake` and `this.extendedMapping` before the extended handshake * is sent to the remote peer. */ Wire.prototype._sendExtendedHandshake = function () { var msg = __assign(__assign({}, Object.assign({}, this.extendedHandshake)), { m: {}, exts: {} }); for (var ext in this.extendedMapping) { var name_1 = this.extendedMapping[ext]; var extension = this._ext[name_1]; msg.m[name_1] = Number(ext); msg.exts[name_1] = __assign({}, extension.extraFields); } // Send extended handshake this.extended(0, bencode_1.default.encode(msg)); this._extendedHandshakeSent = true; }; /** * Message "choke": <len=0001><id=0> */ Wire.prototype.choke = function () { if (this.amChoking) return; this.amChoking = true; this._debug('choke'); while (this.peerRequests.length) { this.peerRequests.pop(); } this._push(PeerMessages_1.MessageBuffers.MESSAGE_CHOKE); }; /** * Message "unchoke": <len=0001><id=1> */ Wire.prototype.unchoke = function () { if (!this.amChoking) return; this.amChoking = false; this._debug('unchoke'); this._push(PeerMessages_1.MessageBuffers.MESSAGE_UNCHOKE); }; /** * Message "interested": <len=0001><id=2> */ Wire.prototype.interested = function () { if (this.amInterested) return; this.amInterested = true; this._debug('interested'); this._push(PeerMessages_1.MessageBuffers.MESSAGE_INTERESTED); }; /** * Message "uninterested": <len=0001><id=3> */ Wire.prototype.uninterested = function () { if (!this.amInterested) return; this.amInterested = false; this._debug('uninterested'); this._push(PeerMessages_1.MessageBuffers.MESSAGE_UNINTERESTED); }; /** * Message "have": <len=0005><id=4><piece index> * @param {number} index */ Wire.prototype.have = function (index) { this._debug('have %d', index); this._message(PeerMessages_1.MessageFlags.Have, [index], null); }; /** * Message "bitfield": <len=0001+X><id=5><bitfield> * @param {BitField|Buffer} bitfield */ Wire.prototype.bitfield = function (bitfield) { this._debug('bitfield'); if (!Buffer.isBuffer(bitfield)) bitfield = bitfield.buffer; this._message(PeerMessages_1.MessageFlags.Bitfield, [], bitfield); }; /** * Callback will be resolved when onPiece(index, offset, length, buffer) is called or something fails when requesting. * * NOTE: index,offset,length are used as a key to look up the callback later. * * So make sure you specify the length correctly or you will never get your callback. * * If the other party sends the same index and offset but a buffer of a different length, you will not recieve your callback. * * Message "request": <len=0013><id=6><index><begin><length> * @param {number} index * @param {number} offset * @param {number} length * @param {function} cb */ Wire.prototype.request = function (index, offset, length, cb) { if (!cb) cb = function () { }; if (this._finished) { return cb(new Error('wire is closed'), undefined); } if (this.peerChoking) { return cb(new Error('peer is choking'), undefined); } if (this._handshakeSuccess === false) { return cb(new Error("peer hasn't finished handshaking"), undefined); } if (this._nextExt > 1 && this._extendedHandshakeSuccess === false) { return cb(new Error("peer hasn't finished extended handshaking"), undefined); } this._debug('request index=%d offset=%d length=%d', index, offset, length); this.requests.push(new PieceRequest_1.PieceRequest(index, offset, length, cb)); this._updateTimeout(); this._message(PeerMessages_1.MessageFlags.Request, [index, offset, length], null); }; /** * Message "piece": <len=0009+X><id=7><index><begin><block> * @param {number} index * @param {number} offset * @param {Buffer} buffer */ Wire.prototype.piece = function (index, offset, buffer) { this._debug('piece index=%d offset=%d', index, offset); this.uploaded += buffer.length; this.uploadSpeed(buffer.length); this.emit('upload', buffer.length); this._message(PeerMessages_1.MessageFlags.Piece, [index, offset], buffer); }; /** * Message "cancel": <len=0013><id=8><index><begin><length> * @param {number} index * @param {number} offset * @param {number} length */ Wire.prototype.cancel = function (index, offset, length) { this._debug('cancel index=%d offset=%d length=%d', index, offset, length); this._callback(this._pull(this.requests, index, offset, length), new Error('request was cancelled'), null); this._message(PeerMessages_1.MessageFlags.Cancel, [index, offset, length], null); }; /** * Message: "port" <len=0003><id=9><listen-port> * @param {Number} port */ Wire.prototype.port = function (port) { this._debug('port %d', port); var message = Buffer.from(PeerMessages_1.MessageBuffers.MESSAGE_PORT); message.writeUInt16BE(port, 5); this._push(message); }; /** * Message: "extended" <len=0005+X><id=20><ext-number><payload> * @param {number|string} ext * @param {Object} obj */ Wire.prototype.extended = function (ext, obj) { this._debug('extended ext=%s', ext); if (typeof ext === 'string' && this.peerExtendedMapping[ext]) { ext = this.peerExtendedMapping[ext]; } if (typeof ext === 'number') { var extId = Buffer.from([ext]); var buf = Buffer.isBuffer(obj) ? obj : bencode_1.default.encode(obj); this._message(PeerMessages_1.MessageFlags.Extended, [], Buffer.concat([extId, buf])); } else { throw new Error("Unrecognized extension: " + ext); } }; /** * Duplex stream method. Called whenever the remote peer stream wants data. No-op * since we'll just push data whenever we get it. */ Wire.prototype._read = function () { }; /** * Send a message to the remote peer. */ Wire.prototype._message = function (id, numbers, data) { var dataLength = data ? data.length : 0; var buffer = Buffer.allocUnsafe(5 + 4 * numbers.length); buffer.writeUInt32BE(buffer.length + dataLength - 4, 0); buffer[4] = id; for (var i = 0; i < numbers.length; i++) { buffer.writeUInt32BE(numbers[i], 5 + 4 * i); } this._push(buffer); if (data) this._push(data); }; Wire.prototype._push = function (data) { if (this._finished) return; return this.push(data); }; // // INCOMING MESSAGES // Wire.prototype._onKeepAlive = function () { this._debug('got keep-alive'); this.emit('keep-alive'); }; Wire.prototype._onHandshake = function (infoHashBuffer, peerIdBuffer, extensions) { var infoHash = infoHashBuffer.toString('hex'); var peerId = peerIdBuffer.toString('hex'); this._debug('got handshake i=%s p=%s exts=%o', infoHash, peerId, extensions); this.peerId = peerId; this.peerIdBuffer = peerIdBuffer; this.peerExtensions = extensions; this.emit('handshake', infoHash, peerId, extensions); for (var name_2 in this._ext) { this._ext[name_2].onHandshake(infoHash, peerId, extensions); } if (extensions.extended && this._handshakeSent && !this._extendedHandshakeSent) { // outgoing connection this._sendExtendedHandshake(); } this._handshakeSuccess = true; }; Wire.prototype._onChoke = function () { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this.peerChoking = true; this._debug('got choke'); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onChoke) === null || _a === void 0 ? void 0 : _a.call(x); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.emit('choke'); while (this.requests.length) { this._callback(this.requests.pop(), new Error('peer is choking'), null); } return [2 /*return*/]; } }); }); }; Wire.prototype._onUnchoke = function () { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this.peerChoking = false; this._debug('got unchoke'); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onUnchoke) === null || _a === void 0 ? void 0 : _a.call(x); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.emit('unchoke'); return [2 /*return*/]; } }); }); }; Wire.prototype._onInterested = function () { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this.peerInterested = true; this._debug('got interested'); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onInterested) === null || _a === void 0 ? void 0 : _a.call(x); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.emit('interested'); return [2 /*return*/]; } }); }); }; Wire.prototype._onUninterested = function () { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this.peerInterested = false; this._debug('got uninterested'); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onUninterested) === null || _a === void 0 ? void 0 : _a.call(x); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.emit('uninterested'); return [2 /*return*/]; } }); }); }; Wire.prototype._onHave = function (index) { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.peerPieces.get(index)) return [2 /*return*/]; this._debug('got have %d', index); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onHave) === null || _a === void 0 ? void 0 : _a.call(x, index); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.peerPieces.set(index, true); this.emit('have', index); return [2 /*return*/]; } }); }); }; Wire.prototype._onBitField = function (buffer) { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this.peerPieces = new bitfield_1.default(buffer); this._debug('got bitfield'); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onBitField) === null || _a === void 0 ? void 0 : _a.call(x, buffer); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.emit('bitfield', this.peerPieces); return [2 /*return*/]; } }); }); }; Wire.prototype._onRequest = function (index, offset, length) { return __awaiter(this, void 0, void 0, function () { var extensionCalls, respond, request; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.amChoking) { return [2 /*return*/]; } this._debug('got request index=%d offset=%d length=%d', index, offset, length); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onRequest) === null || _a === void 0 ? void 0 : _a.call(x, index, offset, length); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this._debug('Extensions have resolved'); respond = function (err, buffer) { // below request var gets hoisted above this function. // eslint-disable-next-line @typescript-eslint/no-use-before-define if (request !== _this._pull(_this.peerRequests, index, offset, length)) { return; } if (err) { _this._debug('error satisfying request index=%d offset=%d length=%d (%s)', index, offset, length, err.message); return; } if (buffer === null || buffer === undefined) { _this._debug('the requested piece has no buffer associated with it, return empty buffer index=%d offset=%d length=%d (%s)', index, offset, length); buffer = Buffer.alloc(0); } _this.piece(index, offset, buffer); }; request = new PieceRequest_1.PieceRequest(index, offset, length, respond); this.peerRequests.push(request); this.emit('request', index, offset, length, respond); return [2 /*return*/]; } }); }); }; Wire.prototype._onPiece = function (index, offset, buffer) { return __awaiter(this, void 0, void 0, function () { var extensionOnPieces, resolvedRequest, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); extensionOnPieces = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onPiece) === null || _a === void 0 ? void 0 : _a.call(x, index, offset, buffer); }); return [4 /*yield*/, Promise.all(extensionOnPieces)]; case 1: _a.sent(); this._debug('got piece index=%d offset=%d', index, offset); resolvedRequest = this._pull(this.requests, index, offset, buffer.length); this._debug('got resolved request for index=%d offset=%d', index, offset, resolvedRequest); this._callback(resolvedRequest, null, buffer); this.downloaded += buffer.length; this.downloadSpeed(buffer.length); this.emit('download', buffer.length); this.emit('piece', index, offset, buffer); return [3 /*break*/, 3]; case 2: error_1 = _a.sent(); console.error(error_1); this._debug('An error occurred when recieving a piece, destroying the connection', error_1); this.destroy(); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; Wire.prototype._onCancel = function (index, offset, length) { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this._debug('got cancel index=%d offset=%d length=%d', index, offset, length); extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onCancel) === null || _a === void 0 ? void 0 : _a.call(x, index, offset, length); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this._pull(this.peerRequests, index, offset, length); this.emit('cancel', index, offset, length); return [2 /*return*/]; } }); }); }; Wire.prototype._onPort = function (port) { this._debug('got port %d', port); this.emit('port', port); }; Wire.prototype.onExtendedMessage = function (extensionId, buf) { if (!this.extendedMapping[extensionId]) { throw new Error("Could not find extension with the given id: " + extensionId); } // get friendly name for extension var extensionName = this.extendedMapping[extensionId]; if (!this._ext[extensionName]) { throw new Error("Could not find extension with the given name: " + extensionName); } // there is an registered extension handler, so call it this._ext[extensionName].onMessage(buf); this._debug('got extended message ext=%s', extensionName); this.emit('extension_message', extensionName, buf); }; Wire.prototype.onExtendedHandshake = function (buf) { var _a; var info; try { info = bencode_1.default.decode(buf); } catch (err) { this._debug('ignoring invalid extended handshake: %s', err.message || err); return; } if (!info) { return; } this.peerExtendedHandshake = info; // Find any extensions that require the other peer to have it too. for (var name_3 in this._ext) { if (this._ext[name_3] && this._ext[name_3].requirePeer && !((_a = this.peerExtendedHandshake.m) === null || _a === void 0 ? void 0 : _a[name_3])) { this._debug('Destroying connection, peer doesnt have same extension.', name_3); this.destroy(); this.emit('missing_extension', name_3); return; } } if (typeof info.m === 'object') { for (var name_4 in info.m) { this.peerExtendedMapping[name_4] = Number(info.m[name_4].toString()); } } for (var name_5 in this._ext) { if (this.peerExtendedMapping[name_5]) { this._ext[name_5].onExtendedHandshake(this.peerExtendedHandshake); } } this._debug('got extended handshake'); this.emit('extended_handshake', 'handshake', this.peerExtendedHandshake); this._extendedHandshakeSuccess = true; }; Wire.prototype._onExtended = function (extensionId, buf) { // Which extension this message came from was not specified, this is a handshake if (extensionId === 0) { return this.onExtendedHandshake(buf); } return this.onExtendedMessage(extensionId, buf); }; Wire.prototype._onTimeout = function () { this._debug('request timed out'); this._callback(this.requests.shift(), new Error('request has timed out'), null); this.emit('timeout'); }; /** * Duplex stream method. Called whenever the remote peer has data for us. Data that the * remote peer sends gets buffered (i.e. not actually processed) until the right number * of bytes have arrived, determined by the last call to `this._parse(number, callback)`. * Once enough bytes have arrived to process the message, the callback function * (i.e. `this._parser`) gets called with the full buffer of data. * @param {Buffer} data * @param {string} encoding * @param {function} cb */ Wire.prototype._write = function (data, encoding, cb) { this._buffer = Buffer.concat([this._buffer, data]); this._debug('Data pushing', data, data.length); this.parseStream(); // Keep collecting data for the next request cb(null); }; Wire.prototype._callback = function (request, err, buffer) { this._debug('calling request callback', request); if (!request) { this._debug('No request was specified'); return; } this._clearTimeout(); if (!this.peerChoking && !this._finished) { this._updateTimeout(); } request.callback(err, buffer); }; Wire.prototype._clearTimeout = function () { if (!this._timeout) return; clearTimeout(this._timeout); this._timeout = undefined; }; Wire.prototype._updateTimeout = function () { var _this = this; if (!this._timeoutMs || !this.requests.length || this._timeout) return; this._timeout = setTimeout(function () { return _this._onTimeout(); }, this._timeoutMs); if (this._timeoutUnref && this._timeout.unref) this._timeout.unref(); }; /** * Takes a number of bytes that the local peer is waiting to receive from the remote peer * in order to parse a complete message, and a callback function to be called once enough * bytes have arrived. * @param {number} size * @param {function} parser */ Wire.prototype._parse = function (size, name, parser) { this._parseRequests.push(new ParseRequest_1.ParseRequest(size, name, parser)); this._debug('Parse Requests in queue: ', this._parseRequests.length); }; Wire.prototype._parseHandshake = function () { var _this = this; this._parse(1, 'getBittorrentProtocolIdentifier', function (bittorrentProtocolIdent) { var protocolStringLength = bittorrentProtocolIdent.readUInt8(0); _this._debug('Is start of Bittorrent Protocol?', protocolStringLength === PeerMessages_1.MessageBuffers.MESSAGE_PROTOCOL[0], bittorrentProtocolIdent.toString()); _this._parse(protocolStringLength, 'getHandshake', function (handshake) { _this._debug('HANDSHAKE BUFFER', handshake.toString(), handshake); var protocol = handshake.slice(0, protocolStringLength); if (protocol.toString() !== 'BitTorrent protocol') { _this._debug('Error: wire not speaking BitTorrent protocol (%s)', protocol.toString()); _this.end(); return; } // GET RESERVED _this._parse(PeerMessages_1.MessageBuffers.MESSAGE_RESERVED.length, 'getReserved', function (reservedFlags) { _this._debug('Reserved flags', reservedFlags); var dht = !!(reservedFlags[7] & 0x01); // see bep_0005 var extended = !!(reservedFlags[5] & 0x10); // see bep_0010 _this._parse(PeerMessages_1.MessageParams.INFOHASH_SIZE_LENGTH, 'getInfoHashSizeBuf', function (infoHashSizeBuf) { _this._debug('infoHashSizeBuf', infoHashSizeBuf); var infoHashSize = infoHashSizeBuf.readUInt8(0); _this._debug('InfoHash Size:', infoHashSize); // Make sure that the following character is : _this._parse(PeerMessages_1.MessageBuffers.INFOHASH_SPLIT.length, 'getColonSeparator', function (colonChar) { if (!colonChar.equals(PeerMessages_1.MessageBuffers.INFOHASH_SPLIT)) { throw new Error('Invalid handshake, must be infohash_size:infohash. Missing colon'); } // Infohash size + peerId size _this._parse(infoHashSize + PeerMessages_1.MessageParams.PEER_ID_LENGTH, 'getPeerIdLength', function (infoHashAndPeerId) { // handshake = handshake.slice(pstrlen); _this._onHandshake(infoHashAndPeerId.slice(0, infoHashSize), infoHashAndPeerId.slice(infoHashSize, infoHashSize + PeerMessages_1.MessageParams.PEER_ID_LENGTH), { dht: dht, extended: extended }); _this._parse(4, '_parseHandshake.getMessageLength', _this._onMessageLength); }); }); }); }); }); }); }; Wire.prototype._onFinish = function () { return __awaiter(this, void 0, void 0, function () { var extensionCalls; return __generator(this, function (_a) { switch (_a.label) { case 0: this._finished = true; extensionCalls = Object.values(this._ext).map(function (x) { var _a; return (_a = x.onFinish) === null || _a === void 0 ? void 0 : _a.call(x); }); return [4 /*yield*/, Promise.all(extensionCalls)]; case 1: _a.sent(); this.push(null); // stream cannot be half open, so signal the end of it while (this.read()) { } // consume and discard the rest of the stream data clearInterval(this._keepAliveInterval); this._parse(Number.MAX_VALUE, 'dispose', function () { }); while (this.peerRequests.length) { this.peerRequests.pop(); } while (this.requests.length) { this._callback(this.requests.pop(), new Error('wire was closed'), null); } return [2 /*return*/]; } }); }); }; Wire.prototype._debug = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } debug.apply(void 0, __spread(["[" + Date.now() + "][" + this._debugId + "]"], args)); }; /** * Retrieves the first entity from the requests array that matches the index, offset and length given. * * Often used in conjunction with _callback to call back to waiting piece requests * @param requests * @param pieceIdx * @param offset * @param length */ Wire.prototype._pull = function (requests, pieceIdx, offset, length) { for (var i = 0; i < requests.length; i++) { var req = requests[i]; if (req.piece === pieceIdx && req.offset === offset && req.length === length) { unordered_array_remove_1.default(requests, i); ret