handle some more edge cases, code style, add todos

This commit is contained in:
Feross Aboukhadijeh 2014-03-27 21:02:03 -07:00
parent 5c09dcd55f
commit e5b516f987

View File

@ -14,13 +14,8 @@ var dgram = require('dgram')
var parseUrl = require('url').parse var parseUrl = require('url').parse
var CONNECTION_ID = Buffer.concat([ toUInt32(0x417), toUInt32(0x27101980) ]) var CONNECTION_ID = Buffer.concat([ toUInt32(0x417), toUInt32(0x27101980) ])
var CONNECT = toUInt32(0) var ACTIONS = { CONNECT: 0, ANNOUNCE: 1 }
var ANNOUNCE = toUInt32(1) var EVENTS = { completed: 1, started: 2, stopped: 3 }
var EVENTS = {
completed: 1,
started: 2,
stopped: 3
}
inherits(Client, EventEmitter) inherits(Client, EventEmitter)
@ -31,9 +26,13 @@ function Client (peerId, port, torrent, opts) {
self._opts = opts || {} self._opts = opts || {}
// required // required
self._peerId = peerId self._peerId = Buffer.isBuffer(peerId)
? peerId
: new Buffer(torrent.infoHash, 'utf8')
self._port = port self._port = port
self._infoHash = torrent.infoHash self._infoHash = Buffer.isBuffer(torrent.infoHash)
? torrent.infoHash
: new Buffer(torrent.infoHash, 'hex')
self._torrentLength = torrent.length self._torrentLength = torrent.length
self._announce = torrent.announce self._announce = torrent.announce
@ -142,9 +141,10 @@ Client.prototype._requestHttp = function (announceUrl, opts) {
} }
Client.prototype._requestUdp = function (announceUrl, opts) { Client.prototype._requestUdp = function (announceUrl, opts) {
var parsed = parseUrl(announceUrl)
var socket = dgram.createSocket('udp4')
var self = this var self = this
var parsedUrl = parseUrl(announceUrl)
var socket = dgram.createSocket('udp4')
var transactionId = new Buffer(hat(32), 'hex')
var timeout = setTimeout(function () { var timeout = setTimeout(function () {
socket.close() socket.close()
@ -155,8 +155,12 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
}) })
socket.on('message', function (message, rinfo) { socket.on('message', function (message, rinfo) {
var action = message.readUInt32BE(0)
if (message.length < 8 || message.readUInt32BE(4) !== transactionId.readUInt32BE(0)) {
return self.emit('error', new Error('tracker sent back invalid transaction id'))
}
var action = message.readUInt32BE(0)
switch (action) { switch (action) {
case 0: case 0:
if (message.length < 16) { if (message.length < 16) {
@ -170,49 +174,58 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
return self.emit('error', new Error('invalid announce message')) return self.emit('error', new Error('invalid announce message'))
} }
// TODO: this should be stored per tracker, not globally for all trackers
var interval = message.readUInt32BE(8)
if (interval && !self._opts.interval && self._intervalMs !== 0) {
// use the interval the tracker recommends, UNLESS the user manually specifies an
// interval they want to use
self.setInterval(interval * 1000)
}
self.emit('update', { self.emit('update', {
announce: announceUrl, announce: announceUrl,
complete: message.readUInt32BE(16), complete: message.readUInt32BE(16),
incomplete: message.readUInt32BE(12) incomplete: message.readUInt32BE(12)
}) })
for (var i = 20; i < message.length; i += 6) { compact2string.multi(message.slice(20)).forEach(function (addr) {
self.emit('peer', compact2string(message.slice(i, i+6))) self.emit('peer', addr)
} })
clearTimeout(timeout) clearTimeout(timeout)
socket.close() socket.close()
} }
}) })
function send (message) {
socket.send(message, 0, message.length, parsedUrl.port, parsedUrl.hostname)
}
function announce (connectionId, opts) { function announce (connectionId, opts) {
opts = opts || {} opts = opts || {}
transactionId = new Buffer(hat(32), 'hex')
send(Buffer.concat([ send(Buffer.concat([
connectionId, connectionId,
ANNOUNCE, toUInt32(ACTIONS.ANNOUNCE),
new Buffer(hat(32), 'hex'), transactionId,
new Buffer(self._infoHash, 'hex'), self._infoHash,
new Buffer(self._peerId, 'utf-8'), self._peerId,
toUInt32(0), toUInt32(opts.downloaded || 0), // fromUint32(0) to expand this to 64bit toUInt32(0), toUInt32(opts.downloaded || 0), // 64bit
toUInt32(0), toUInt32(opts.left || 0), toUInt32(0), toUInt32(opts.left || 0), // 64bit
toUInt32(0), toUInt32(opts.uploaded || 0), toUInt32(0), toUInt32(opts.uploaded || 0), // 64bit
toUInt32(EVENTS[opts.event] || 0), toUInt32(EVENTS[opts.event] || 0),
toUInt32(0), toUInt32(0), // ip address (optional)
toUInt32(0), toUInt32(0), // key (optional)
toUInt32(self._numWant), toUInt32(self._numWant),
toUInt16(self._port || 0) toUInt16(self._port || 0)
])) ]))
} }
function send (message) {
socket.send(message, 0, message.length, parsed.port, parsed.hostname)
}
send(Buffer.concat([ send(Buffer.concat([
CONNECTION_ID, CONNECTION_ID,
CONNECT, toUInt32(ACTIONS.CONNECT),
new Buffer(hat(32), 'hex') transactionId
])) ]))
} }
@ -234,6 +247,7 @@ Client.prototype._handleResponse = function (data, announceUrl) {
self.emit('warning', warning); self.emit('warning', warning);
} }
// TODO: this should be stored per tracker, not globally for all trackers
var interval = data.interval || data['min interval'] var interval = data.interval || data['min interval']
if (interval && !self._opts.interval && self._intervalMs !== 0) { if (interval && !self._opts.interval && self._intervalMs !== 0) {
// use the interval the tracker recommends, UNLESS the user manually specifies an // use the interval the tracker recommends, UNLESS the user manually specifies an
@ -241,6 +255,7 @@ Client.prototype._handleResponse = function (data, announceUrl) {
self.setInterval(interval * 1000) self.setInterval(interval * 1000)
} }
// TODO: this should be stored per tracker, not globally for all trackers
var trackerId = data['tracker id'] var trackerId = data['tracker id']
if (trackerId) { if (trackerId) {
// If absent, do not discard previous trackerId value // If absent, do not discard previous trackerId value
@ -255,8 +270,7 @@ Client.prototype._handleResponse = function (data, announceUrl) {
if (Buffer.isBuffer(data.peers)) { if (Buffer.isBuffer(data.peers)) {
// tracker returned compact response // tracker returned compact response
var addrs = compact2string.multi(data.peers) compact2string.multi(data.peers).forEach(function (addr) {
addrs.forEach(function (addr) {
self.emit('peer', addr) self.emit('peer', addr)
}) })
} else if (Array.isArray(data.peers)) { } else if (Array.isArray(data.peers)) {
@ -463,15 +477,9 @@ function toUInt32 (n) {
} }
function bytewiseEncodeURIComponent (buf) { function bytewiseEncodeURIComponent (buf) {
if (!Buffer.isBuffer(buf)) {
buf = new Buffer(buf, 'hex')
}
return encodeURIComponent(buf.toString('binary')) return encodeURIComponent(buf.toString('binary'))
} }
function bytewiseDecodeURIComponent (str) { function bytewiseDecodeURIComponent (str) {
if (Buffer.isBuffer(str)) {
str = str.toString('utf8')
}
return (new Buffer(decodeURIComponent(str), 'binary').toString('hex')) return (new Buffer(decodeURIComponent(str), 'binary').toString('hex'))
} }