Skip to content

Commit 0bc88bc

Browse files
committed
server: split out parseUdpRequest()
1 parent 037a53a commit 0bc88bc

File tree

2 files changed

+101
-78
lines changed

2 files changed

+101
-78
lines changed

lib/common.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ var querystring = require('querystring')
77
exports.CONNECTION_ID = Buffer.concat([ toUInt32(0x417), toUInt32(0x27101980) ])
88
exports.ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 }
99
exports.EVENTS = { update: 0, completed: 1, started: 2, stopped: 3 }
10+
exports.EVENT_IDS = {
11+
0: 'update',
12+
1: 'completed',
13+
2: 'started',
14+
3: 'stopped'
15+
};
1016

1117
function toUInt32 (n) {
1218
var buf = new Buffer(4)

server.js

Lines changed: 95 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -296,75 +296,40 @@ Server.prototype._onHttpRequest = function (req, res) {
296296
Server.prototype._onUdpRequest = function (msg, rinfo) {
297297
var self = this
298298

299-
if (msg.length < 16) {
300-
return error('received packet is too short')
301-
}
302-
303-
if (rinfo.family !== 'IPv4') {
304-
return error('udp tracker does not support IPv6')
305-
}
306-
307-
var connectionId = msg.slice(0, 8) // 64-bit
308-
var action = msg.readUInt32BE(8)
309-
var transactionId = msg.readUInt32BE(12)
310-
311-
if (!bufferEqual(connectionId, common.CONNECTION_ID)) {
312-
return error('received packet with invalid connection id')
299+
var params
300+
try {
301+
params = parseUdpRequest(msg, rinfo)
302+
} catch (err) {
303+
console.error(err.stack)
304+
return error(err.message)
313305
}
314-
306+
315307
var socket = dgram.createSocket('udp4')
316308

317-
var infoHash, swarm
318-
if (action === common.ACTIONS.CONNECT) {
309+
var swarm
310+
if (params && params.request === 'connect') {
319311
send(Buffer.concat([
320312
common.toUInt32(common.ACTIONS.CONNECT),
321-
common.toUInt32(transactionId),
322-
connectionId
313+
common.toUInt32(params.transactionId),
314+
params.connectionId
323315
]))
324-
} else if (action === common.ACTIONS.ANNOUNCE) {
325-
infoHash = msg.slice(16, 36).toString('binary') // 20 bytes
326-
var peerId = msg.slice(36, 56).toString('utf8') // 20 bytes
327-
var downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this?
328-
var left = fromUInt64(msg.slice(64, 72))
329-
var uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this?
330-
var event = msg.readUInt32BE(80)
331-
var ip = msg.readUInt32BE(84) // optional
332-
var key = msg.readUInt32BE(88) // TODO: what is this for?
333-
var numwant = msg.readUInt32BE(92) // optional
334-
var port = msg.readUInt16BE(96) // optional
335-
336-
if (ip) {
337-
ip = ipLib.toString(ip)
338-
} else {
339-
ip = rinfo.address
340-
}
341-
342-
if (!port) {
343-
port = rinfo.port
344-
}
345-
346-
var addr = ip + ':' + port
347-
348-
swarm = self._getSwarm(infoHash)
349-
var peer = swarm.peers[addr]
350-
351-
// never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than
352-
// 512 bytes which is not safe
353-
numwant = Math.min(numwant || NUM_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS)
316+
} else if (params && params.request === 'announce') {
317+
swarm = self._getSwarm(params.info_hash)
318+
var peer = swarm.peers[params.addr]
354319

355320
var start = function () {
356321
if (peer) {
357322
debug('unexpected `started` event from peer that is already in swarm')
358323
return update() // treat as an update
359324
}
360-
if (left === 0) swarm.complete += 1
325+
if (params.left === 0) swarm.complete += 1
361326
else swarm.incomplete += 1
362-
peer = swarm.peers[addr] = {
363-
ip: ip,
364-
port: port,
365-
peerId: peerId
327+
peer = swarm.peers[params.addr] = {
328+
ip: params.ip,
329+
port: params.port,
330+
peerId: params.peer_id
366331
}
367-
self.emit('start', addr)
332+
self.emit('start', params.addr)
368333
}
369334

370335
var stop = function () {
@@ -374,8 +339,8 @@ Server.prototype._onUdpRequest = function (msg, rinfo) {
374339
}
375340
if (peer.complete) swarm.complete -= 1
376341
else swarm.incomplete -= 1
377-
swarm.peers[addr] = null
378-
self.emit('stop', addr)
342+
swarm.peers[params.addr] = null
343+
self.emit('stop', params.addr)
379344
}
380345

381346
var complete = function () {
@@ -390,61 +355,54 @@ Server.prototype._onUdpRequest = function (msg, rinfo) {
390355
swarm.complete += 1
391356
swarm.incomplete -= 1
392357
peer.complete = true
393-
self.emit('complete', addr)
358+
self.emit('complete', params.addr)
394359
}
395360

396361
var update = function () {
397362
if (!peer) {
398363
debug('unexpected `update` event from peer that is not in swarm')
399364
return start() // treat as a start
400365
}
401-
self.emit('update', addr)
366+
self.emit('update', params.addr)
402367
}
403368

404-
switch (event) {
405-
case common.EVENTS.started:
369+
switch (params.event) {
370+
case 'started':
406371
start()
407372
break
408-
case common.EVENTS.stopped:
373+
case 'stopped':
409374
stop()
410375
break
411-
case common.EVENTS.completed:
376+
case 'completed':
412377
complete()
413378
break
414-
case common.EVENTS.update: // update
379+
case 'update':
415380
update()
416381
break
417382
default:
418383
return error('invalid event') // early return
419384
}
420385

421-
if (left === 0 && peer) peer.complete = true
386+
if (params.left === 0 && peer) peer.complete = true
422387

423388
// send peers
424-
var peers = self._getPeersCompact(swarm, numwant)
389+
var peers = self._getPeersCompact(swarm, params.numwant)
425390

426391
send(Buffer.concat([
427392
common.toUInt32(common.ACTIONS.ANNOUNCE),
428-
common.toUInt32(transactionId),
393+
common.toUInt32(params.transactionId),
429394
common.toUInt32(self._intervalMs),
430395
common.toUInt32(swarm.incomplete),
431396
common.toUInt32(swarm.complete),
432397
peers
433398
]))
434399

435-
} else if (action === common.ACTIONS.SCRAPE) { // scrape message
436-
infoHash = msg.slice(16, 36).toString('binary') // 20 bytes
437-
438-
// TODO: support multiple info_hash scrape
439-
if (msg.length > 36) {
440-
error('multiple info_hash scrape not supported')
441-
}
442-
443-
swarm = self._getSwarm(infoHash)
400+
} else if (params && params.request === 'scrape') { // scrape message
401+
swarm = self._getSwarm(params.info_hash)
444402

445403
send(Buffer.concat([
446404
common.toUInt32(common.ACTIONS.SCRAPE),
447-
common.toUInt32(transactionId),
405+
common.toUInt32(params.transactionId),
448406
common.toUInt32(swarm.complete),
449407
common.toUInt32(swarm.complete), // TODO: this only provides a lower-bound
450408
common.toUInt32(swarm.incomplete)
@@ -464,7 +422,7 @@ Server.prototype._onUdpRequest = function (msg, rinfo) {
464422
debug('sent error %s', message)
465423
send(Buffer.concat([
466424
common.toUInt32(common.ACTIONS.ERROR),
467-
common.toUInt32(transactionId || 0),
425+
common.toUInt32(params.transactionId || 0),
468426
new Buffer(message, 'utf8')
469427
]))
470428
self.emit('warning', new Error(message))
@@ -553,6 +511,65 @@ function parseHttpRequest (req, options) {
553511
}
554512
}
555513

514+
function parseUdpRequest (msg, rinfo) {
515+
if (msg.length < 16) {
516+
throw new Error('received packet is too short')
517+
}
518+
519+
if (rinfo.family !== 'IPv4') {
520+
throw new Error('udp tracker does not support IPv6')
521+
}
522+
523+
var params = {
524+
connectionId: msg.slice(0, 8), // 64-bit
525+
action: msg.readUInt32BE(8),
526+
transactionId: msg.readUInt32BE(12)
527+
}
528+
529+
// TODO: randomize:
530+
if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) {
531+
throw new Error('received packet with invalid connection id')
532+
}
533+
534+
if (params.action === common.ACTIONS.CONNECT) {
535+
params.request = 'connect'
536+
} else if (params.action === common.ACTIONS.ANNOUNCE) {
537+
params.request = 'announce'
538+
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
539+
params.peer_id = msg.slice(36, 56).toString('utf8') // 20 bytes
540+
params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this?
541+
params.left = fromUInt64(msg.slice(64, 72))
542+
params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this?
543+
params.event = msg.readUInt32BE(80)
544+
params.event = common.EVENT_IDS[params.event]
545+
if (!params.event) throw new Error('invalid event') // early return
546+
params.ip = msg.readUInt32BE(84) // optional
547+
params.ip = params.ip ?
548+
ipLib.toString(params.ip) :
549+
params.ip = rinfo.address
550+
params.key = msg.readUInt32BE(88) // TODO: what is this for?
551+
params.numwant = msg.readUInt32BE(92) // optional
552+
// never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than
553+
// 512 bytes which is not safe
554+
params.numwant = Math.min(params.numwant || NUM_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS)
555+
params.port = msg.readUInt16BE(96) || rinfo.port // optional
556+
params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets
557+
558+
} else if (params.action === common.ACTIONS.SCRAPE) { // scrape message
559+
params.request = 'scrape'
560+
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
561+
562+
// TODO: support multiple info_hash scrape
563+
if (msg.length > 36) {
564+
throw new Error('multiple info_hash scrape not supported')
565+
}
566+
} else {
567+
return null
568+
}
569+
570+
return params
571+
}
572+
556573

557574
// HELPER FUNCTIONS
558575

0 commit comments

Comments
 (0)