ut_metadata/index.js

181 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// TODO: add verification
var BitField = require('BitField')
var bncode = require('bncode')
var EventEmitter = require('events').EventEmitter
var inherits = require('inherits')
var PIECE_LENGTH = 16 * 1024
module.exports = function (metadata) {
inherits(ut_metadata, EventEmitter)
function ut_metadata (wire) {
EventEmitter.call(this)
this._wire = wire
this._metadataComplete = false
this._metadataSize = null
this._remainingRejects = null // how many reject messages to tolerate before quitting
this._fetching = false
this._bitfield = new BitField(0)
if (metadata) {
this.gotMetadata(metadata)
}
}
Object.defineProperty(ut_metadata.prototype, '_numPieces', {
get: function () {
return Math.ceil(this._metadataSize / PIECE_LENGTH)
}
})
ut_metadata.prototype.onHandshake = function () {
}
ut_metadata.prototype.onExtendedHandshake = function (handshake) {
this._metadataSize = handshake.metadata_size
if (this._metadataSize && this._fetching) {
this._requestPieces()
}
}
ut_metadata.prototype.onMessage = function (buf) {
var dict, trailer
try {
var str = buf.toString()
var trailerIndex = str.indexOf('ee') + 2
dict = bncode.decode(str.substring(0, trailerIndex))
trailer = buf.slice(trailerIndex)
} catch (err) {
this.emit('warning', new Error('Could not decode ut_metadata message'))
return
}
switch (dict.msg_type) {
case 0:
// ut_metadata request (from peer)
// example: { 'msg_type': 0, 'piece': 0 }
this._onRequest(dict.piece)
break
case 1:
// ut_metadata data (in response to our request)
// example: { 'msg_type': 1, 'piece': 0, 'total_size': 3425 }
this._onData(dict.piece, trailer, dict.total_size)
break
case 2:
// ut_metadata reject (peer doesn't have piece we requested)
// { 'msg_type': 2, 'piece': 0 }
this._onReject(dict.piece)
break
}
}
// Expose high-level, friendly API (fetch/cancel)
ut_metadata.prototype.fetch = function () {
if (this._metadataComplete) {
return
}
this._fetching = true
if (this._metadataSize) {
this._requestPieces()
}
}
ut_metadata.prototype.cancel = function () {
this._fetching = false
}
ut_metadata.prototype.gotMetadata = function (_metadata) {
this.metadata = _metadata
this._metadataComplete = true
this._metadataSize = this.metadata.length
this._wire.extendedHandshake.metadata_size = this._metadataSize
this.emit('metadata', this.metadata)
}
ut_metadata.prototype._send = function (dict, trailer) {
var buf = bncode.encode(dict)
if (Buffer.isBuffer(trailer)) {
buf = Buffer.concat([buf, trailer])
}
this._wire.extended('ut_metadata', buf)
}
ut_metadata.prototype._request = function (piece) {
this._send({ msg_type: 0, piece: piece })
}
ut_metadata.prototype._data = function (piece, buf, totalSize) {
var msg = { msg_type: 1, piece: piece }
if (typeof totalSize === 'number') {
msg.total_size = totalSize
}
this._send(msg, buf)
}
ut_metadata.prototype._reject = function (piece) {
this._send({ msg_type: 2, piece: piece })
}
ut_metadata.prototype._onRequest = function (piece) {
if (this._metadataComplete) {
var start = piece * PIECE_LENGTH
var end = start + PIECE_LENGTH
if (end > this._metadataSize) {
end = this._metadataSize
}
var buf = this.metadata.slice(start, end)
this._data(piece, buf, this._metadataSize)
}
}
ut_metadata.prototype._onData = function (piece, buf, totalSize) {
if (buf.length > PIECE_LENGTH) {
return
}
buf.copy(this.metadata, piece * PIECE_LENGTH)
this._bitfield.set(piece)
this._checkDone()
}
ut_metadata.prototype._onReject = function (piece) {
if (this._remainingRejects > 0 && this._fetching) {
// If we haven't been rejected too much, then try to request the piece again
this._request(piece)
this._remainingRejects -= 1
} else {
this.emit('warning', new Error('Peer sent "reject" too much'))
}
}
ut_metadata.prototype._requestPieces = function () {
this.metadata = new Buffer(this._metadataSize)
this._remainingRejects = this._numPieces
for (var piece = 0; piece < this._numPieces; piece++) {
this._request(piece)
}
}
ut_metadata.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
// TODO: verify
this.gotMetadata(this.metadata)
}
return ut_metadata
}