Skip to content

Commit 1c754a2

Browse files
committed
move parseHttpRequest(), parseUdpRequest() to lib/parse_{http,udp}.js
1 parent aea3c44 commit 1c754a2

File tree

4 files changed

+145
-134
lines changed

4 files changed

+145
-134
lines changed

lib/common.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
var querystring = require('querystring')
66

7+
exports.NUM_ANNOUNCE_PEERS = 50
8+
exports.MAX_ANNOUNCE_PEERS = 82
9+
710
exports.CONNECTION_ID = Buffer.concat([ toUInt32(0x417), toUInt32(0x27101980) ])
811
exports.ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 }
912
exports.EVENTS = { update: 0, completed: 1, started: 2, stopped: 3 }

lib/parse_http.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
var common = require('./common')
2+
3+
var REMOVE_IPV6_RE = /^::ffff:/
4+
5+
module.exports = parseHttpRequest
6+
7+
function parseHttpRequest (req, options) {
8+
var s = req.url.split('?')
9+
var params = common.querystringParse(s[1])
10+
11+
if (s[0] === '/announce') {
12+
params.action = common.ACTIONS.ANNOUNCE
13+
14+
params.peer_id = typeof params.peer_id === 'string' && common.binaryToUtf8(params.peer_id)
15+
params.port = Number(params.port)
16+
17+
if (typeof params.info_hash !== 'string') throw new Error('invalid info_hash')
18+
if (params.info_hash.length !== 20) throw new Error('invalid info_hash length')
19+
if (typeof params.peer_id !== 'string') throw new Error('invalid peer_id')
20+
if (params.peer_id.length !== 20) throw new Error('invalid peer_id length')
21+
if (!params.port) throw new Error('invalid port')
22+
23+
params.left = Number(params.left)
24+
params.compact = Number(params.compact)
25+
26+
params.ip = options.trustProxy
27+
? req.headers['x-forwarded-for'] || req.connection.remoteAddress
28+
: req.connection.remoteAddress.replace(REMOVE_IPV6_RE, '') // force ipv4
29+
params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets?
30+
31+
params.numwant = Math.min(
32+
Number(params.numwant) || common.NUM_ANNOUNCE_PEERS,
33+
common.MAX_ANNOUNCE_PEERS
34+
)
35+
36+
return params
37+
} else if (s[0] === '/scrape') { // unofficial scrape message
38+
params.action = common.ACTIONS.SCRAPE
39+
40+
if (typeof params.info_hash === 'string') {
41+
params.info_hash = [ params.info_hash ]
42+
}
43+
44+
if (params.info_hash) {
45+
if (!Array.isArray(params.info_hash)) throw new Error('invalid info_hash array')
46+
47+
params.info_hash = params.info_hash.map(function (infoHash) {
48+
if (infoHash.length !== 20) {
49+
throw new Error('invalid info_hash')
50+
}
51+
52+
return infoHash
53+
})
54+
}
55+
56+
return params
57+
} else {
58+
return null
59+
}
60+
}

lib/parse_udp.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
var bufferEqual = require('buffer-equal')
2+
var common = require('./common')
3+
4+
5+
module.exports = parseUdpRequest
6+
7+
function parseUdpRequest (msg, rinfo) {
8+
if (msg.length < 16) {
9+
throw new Error('received packet is too short')
10+
}
11+
12+
if (rinfo.family !== 'IPv4') {
13+
throw new Error('udp tracker does not support IPv6')
14+
}
15+
16+
var params = {
17+
connectionId: msg.slice(0, 8), // 64-bit
18+
action: msg.readUInt32BE(8),
19+
transactionId: msg.readUInt32BE(12)
20+
}
21+
22+
// TODO: randomize:
23+
if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) {
24+
throw new Error('received packet with invalid connection id')
25+
}
26+
27+
if (params.action === common.ACTIONS.CONNECT) {
28+
// No further params
29+
} else if (params.action === common.ACTIONS.ANNOUNCE) {
30+
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
31+
params.peer_id = msg.slice(36, 56).toString('utf8') // 20 bytes
32+
params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this?
33+
params.left = fromUInt64(msg.slice(64, 72))
34+
params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this?
35+
params.event = msg.readUInt32BE(80)
36+
params.event = common.EVENT_IDS[params.event]
37+
if (!params.event) throw new Error('invalid event') // early return
38+
params.ip = msg.readUInt32BE(84) // optional
39+
params.ip = params.ip ?
40+
ipLib.toString(params.ip) :
41+
params.ip = rinfo.address
42+
params.key = msg.readUInt32BE(88) // TODO: what is this for?
43+
params.numwant = msg.readUInt32BE(92) // optional
44+
// never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than
45+
// 512 bytes which is not safe
46+
params.numwant = Math.min(params.numwant || common.NUM_ANNOUNCE_PEERS, common.MAX_ANNOUNCE_PEERS)
47+
params.port = msg.readUInt16BE(96) || rinfo.port // optional
48+
params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets
49+
params.compact = 1 // udp is always compact
50+
51+
} else if (params.action === common.ACTIONS.SCRAPE) { // scrape message
52+
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
53+
54+
// TODO: support multiple info_hash scrape
55+
if (msg.length > 36) {
56+
throw new Error('multiple info_hash scrape not supported')
57+
}
58+
} else {
59+
return null
60+
}
61+
62+
return params
63+
}
64+
65+
// HELPER FUNCTIONS
66+
67+
var TWO_PWR_32 = (1 << 16) * 2
68+
69+
/**
70+
* Return the closest floating-point representation to the buffer value. Precision will be
71+
* lost for big numbers.
72+
*/
73+
function fromUInt64 (buf) {
74+
var high = buf.readUInt32BE(0) | 0 // force
75+
var low = buf.readUInt32BE(4) | 0
76+
var lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low
77+
78+
return high * TWO_PWR_32 + lowUnsigned
79+
}

server.js

Lines changed: 3 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module.exports = Server
2-
module.exports.parseHttpRequest = parseHttpRequest
32

43
var bencode = require('bencode')
5-
var bufferEqual = require('buffer-equal')
64
var common = require('./lib/common')
75
var debug = require('debug')('bittorrent-tracker')
86
var dgram = require('dgram')
@@ -13,12 +11,12 @@ var ipLib = require('ip')
1311
var portfinder = require('portfinder')
1412
var string2compact = require('string2compact')
1513

14+
var parseHttpRequest = require('./lib/parse_http')
15+
var parseUdpRequest = require('./lib/parse_udp')
16+
1617
// Use random port above 1024
1718
portfinder.basePort = Math.floor(Math.random() * 60000) + 1025
1819

19-
var NUM_ANNOUNCE_PEERS = 50
20-
var MAX_ANNOUNCE_PEERS = 82
21-
var REMOVE_IPV6_RE = /^::ffff:/
2220

2321
inherits(Server, EventEmitter)
2422

@@ -369,119 +367,6 @@ Server.prototype._getPeersCompact = function (swarm, numwant) {
369367
}
370368

371369

372-
function parseHttpRequest (req, options) {
373-
var s = req.url.split('?')
374-
var params = common.querystringParse(s[1])
375-
376-
if (s[0] === '/announce') {
377-
params.action = common.ACTIONS.ANNOUNCE
378-
379-
params.peer_id = typeof params.peer_id === 'string' && common.binaryToUtf8(params.peer_id)
380-
params.port = Number(params.port)
381-
382-
if (typeof params.info_hash !== 'string') throw new Error('invalid info_hash')
383-
if (params.info_hash.length !== 20) throw new Error('invalid info_hash length')
384-
if (typeof params.peer_id !== 'string') throw new Error('invalid peer_id')
385-
if (params.peer_id.length !== 20) throw new Error('invalid peer_id length')
386-
if (!params.port) throw new Error('invalid port')
387-
388-
params.left = Number(params.left)
389-
params.compact = Number(params.compact)
390-
391-
params.ip = options.trustProxy
392-
? req.headers['x-forwarded-for'] || req.connection.remoteAddress
393-
: req.connection.remoteAddress.replace(REMOVE_IPV6_RE, '') // force ipv4
394-
params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets?
395-
396-
params.numwant = Math.min(
397-
Number(params.numwant) || NUM_ANNOUNCE_PEERS,
398-
MAX_ANNOUNCE_PEERS
399-
)
400-
401-
return params
402-
} else if (s[0] === '/scrape') { // unofficial scrape message
403-
params.action = common.ACTIONS.SCRAPE
404-
405-
if (typeof params.info_hash === 'string') {
406-
params.info_hash = [ params.info_hash ]
407-
}
408-
409-
if (params.info_hash) {
410-
if (!Array.isArray(params.info_hash)) throw new Error('invalid info_hash array')
411-
412-
params.info_hash = params.info_hash.map(function (infoHash) {
413-
if (infoHash.length !== 20) {
414-
throw new Error('invalid info_hash')
415-
}
416-
417-
return infoHash
418-
})
419-
}
420-
421-
return params
422-
} else {
423-
return null
424-
}
425-
}
426-
427-
function parseUdpRequest (msg, rinfo) {
428-
if (msg.length < 16) {
429-
throw new Error('received packet is too short')
430-
}
431-
432-
if (rinfo.family !== 'IPv4') {
433-
throw new Error('udp tracker does not support IPv6')
434-
}
435-
436-
var params = {
437-
connectionId: msg.slice(0, 8), // 64-bit
438-
action: msg.readUInt32BE(8),
439-
transactionId: msg.readUInt32BE(12)
440-
}
441-
442-
// TODO: randomize:
443-
if (!bufferEqual(params.connectionId, common.CONNECTION_ID)) {
444-
throw new Error('received packet with invalid connection id')
445-
}
446-
447-
if (params.action === common.ACTIONS.CONNECT) {
448-
// No further params
449-
} else if (params.action === common.ACTIONS.ANNOUNCE) {
450-
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
451-
params.peer_id = msg.slice(36, 56).toString('utf8') // 20 bytes
452-
params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this?
453-
params.left = fromUInt64(msg.slice(64, 72))
454-
params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this?
455-
params.event = msg.readUInt32BE(80)
456-
params.event = common.EVENT_IDS[params.event]
457-
if (!params.event) throw new Error('invalid event') // early return
458-
params.ip = msg.readUInt32BE(84) // optional
459-
params.ip = params.ip ?
460-
ipLib.toString(params.ip) :
461-
params.ip = rinfo.address
462-
params.key = msg.readUInt32BE(88) // TODO: what is this for?
463-
params.numwant = msg.readUInt32BE(92) // optional
464-
// never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than
465-
// 512 bytes which is not safe
466-
params.numwant = Math.min(params.numwant || NUM_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS)
467-
params.port = msg.readUInt16BE(96) || rinfo.port // optional
468-
params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets
469-
params.compact = 1 // udp is always compact
470-
471-
} else if (params.action === common.ACTIONS.SCRAPE) { // scrape message
472-
params.info_hash = msg.slice(16, 36).toString('binary') // 20 bytes
473-
474-
// TODO: support multiple info_hash scrape
475-
if (msg.length > 36) {
476-
throw new Error('multiple info_hash scrape not supported')
477-
}
478-
} else {
479-
return null
480-
}
481-
482-
return params
483-
}
484-
485370
function makeUdpPacket (params) {
486371
switch (params.action) {
487372
case common.ACTIONS.CONNECT:
@@ -523,19 +408,3 @@ function makeUdpPacket (params) {
523408
throw new Error('Action not implemented: ' + params.action)
524409
}
525410
}
526-
527-
// HELPER FUNCTIONS
528-
529-
var TWO_PWR_32 = (1 << 16) * 2
530-
531-
/**
532-
* Return the closest floating-point representation to the buffer value. Precision will be
533-
* lost for big numbers.
534-
*/
535-
function fromUInt64 (buf) {
536-
var high = buf.readUInt32BE(0) | 0 // force
537-
var low = buf.readUInt32BE(4) | 0
538-
var lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low
539-
540-
return high * TWO_PWR_32 + lowUnsigned
541-
}

0 commit comments

Comments
 (0)