├── .gitignore ├── AUTHORS ├── LICENSE ├── README ├── TODO ├── bin └── .gitkeep ├── lib ├── .gitkeep ├── bencode.js ├── dispatcher.js ├── formatters.js ├── handlers.js ├── pool_dummy.js ├── tracker.js └── utils.js └── test ├── .gitkeep ├── mocks.js ├── suite.js ├── test-bencode.js ├── test-dispatcher.js ├── test-formatters.js ├── test-handlers.js ├── test-pool.js └── test-utils.js /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pleax/node-tracker/286335a7fc0f88bbcbbdc4e0669677a2ad56d840/.gitignore -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Dmitry Lipovoi 2 | Fyodorov "bga" Alexander 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Dmitry Lipovoi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | BitTorrent Tracker implemented with node.js 2 | 3 | It was initially implemented in two days on Saint-Petersburg's HackDay #9 4 | event. Later code was cleaned up. 5 | 6 | It is still in raw prototype stage, but it seems that announces are handled 7 | correctly for both compact and default modes. 8 | 9 | Running 10 | 11 | $ node lib/tracker.js 12 | 13 | Running tests 14 | 15 | * all in batch 16 | 17 | $ node test/suite.js 18 | 19 | * or individually 20 | 21 | $ node test/test-{module_name}.js 22 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | * Validation of parameters in announce handler. E.g. info_hash should be 3 | exactly 20 bytes, etc., etc. 4 | 5 | * Config to store listening port, announce interval, etc. 6 | 7 | * Scrape handler. 8 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pleax/node-tracker/286335a7fc0f88bbcbbdc4e0669677a2ad56d840/bin/.gitkeep -------------------------------------------------------------------------------- /lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pleax/node-tracker/286335a7fc0f88bbcbbdc4e0669677a2ad56d840/lib/.gitkeep -------------------------------------------------------------------------------- /lib/bencode.js: -------------------------------------------------------------------------------- 1 | 2 | var b = exports; 3 | 4 | var encode = function(input) { 5 | var tokens = []; 6 | if (typeof input == "number") { 7 | tokens.push('i'); 8 | tokens.push(input.toString()); 9 | tokens.push('e'); 10 | } else if (typeof input == "string") { 11 | tokens.push(input.length.toString()); 12 | tokens.push(':'); 13 | tokens.push(input); 14 | } else if (input instanceof Array) { 15 | tokens.push('l'); 16 | for (var i = 0; i < input.length; i++) { 17 | tokens.push(encode(input[i])); 18 | } 19 | tokens.push('e'); 20 | } else if (typeof input == "object") { 21 | tokens.push('d'); 22 | var keys = []; 23 | for (var k in input) { 24 | keys.push(k); 25 | } 26 | keys.sort(); 27 | for (var i = 0; i < keys.length; i++) { 28 | var k = keys[i]; 29 | var v = input[k]; 30 | tokens.push(encode(k)); 31 | tokens.push(encode(v)); 32 | } 33 | tokens.push('e'); 34 | } else { 35 | throw new Error("Unknown type for bencode."); 36 | } 37 | return tokens.join(''); 38 | }; 39 | 40 | b.encode = encode; 41 | -------------------------------------------------------------------------------- /lib/dispatcher.js: -------------------------------------------------------------------------------- 1 | var url = require('url'); 2 | var qs = require('querystring'); 3 | 4 | var forHandlers = exports.forHandlers = function(handlers) { 5 | return function(request, response) { 6 | var tokens = url.parse(request.url); 7 | var params = qs.parse(tokens.query); 8 | 9 | var ctx = { 10 | params: params, 11 | url: tokens, 12 | request: request, 13 | response: response 14 | }; 15 | 16 | console.log("Ctx.url:", JSON.stringify(ctx.url)); 17 | console.log("Ctx.params:", JSON.stringify(ctx.params)); 18 | 19 | (handlers[ctx.url.pathname] || handle404)(ctx); 20 | }; 21 | }; 22 | 23 | var handle404 = function(ctx) { 24 | ctx.response.writeHead(404, { 'Content-Type': 'text/plain' }); 25 | ctx.response.end(); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/formatters.js: -------------------------------------------------------------------------------- 1 | 2 | var b = require('./bencode.js'); 3 | 4 | exports.announce = function(torrentInfo, wantedPeers, compact) { 5 | var peers = compact ? peersBinary : peersDictionary; 6 | return b.encode({ 7 | interval: 10, // TODO: hardcode must be replaced with configurable value 8 | complete: torrentInfo.complete, 9 | incomplete: torrentInfo.incomplete, 10 | peers: peers(wantedPeers) 11 | }); 12 | }; 13 | 14 | var peersDictionary = function(peers) { 15 | return peers.map(function(peer) { 16 | return { 17 | "peer id": peer.id, 18 | "ip": peer.ip, 19 | "port": peer.port 20 | }; 21 | }); 22 | }; 23 | 24 | var peersBinary = function(peers) { 25 | var tokens = []; 26 | peers.forEach(function(peer) { 27 | tokens.push(peerBinary(peer.ip, peer.port)); 28 | }); 29 | return tokens.join(''); 30 | }; 31 | 32 | var peerBinary = function(ip, port) { 33 | var tokens = []; 34 | 35 | var octets = ip.split('.'); 36 | if (octets.length != 4) return ""; 37 | 38 | octets.forEach(function(octet) { 39 | var val = parseInt(octet, 10); 40 | if (!isNaN(val)) tokens.push(val); 41 | }); 42 | if (tokens.length != 4) return ""; 43 | 44 | tokens.push((port & 0xff00) >> 8); 45 | tokens.push(port & 0xff); 46 | 47 | return String.fromCharCode.apply(tokens, tokens); 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /lib/handlers.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils.js'); 2 | var formatters = require('./formatters.js'); 3 | 4 | var announce = exports.announce = function(ctx) { 5 | console.log("Announce", JSON.stringify(ctx.url)); 6 | 7 | var infoHash = ctx.params['info_hash']; 8 | var peer = { 9 | id: ctx.params['peer_id'], 10 | ip: ctx.params['ip'] || ctx.request.connection.remoteAddress, 11 | port: parseInt(ctx.params['port'], 10) 12 | }; 13 | var metrics = { 14 | uploaded: parseInt(ctx.params['uploaded'], 10), 15 | downloaded: parseInt(ctx.params['downloaded'], 10), 16 | left: parseInt(ctx.params['left'], 10) 17 | }; 18 | var event = ctx.params['event'] || ''; 19 | var wants = parseInt(ctx.params['numwant'] || 50, 10); 20 | var compact = parseInt(ctx.params['compact'], 10) || false; 21 | 22 | var pool = announce.pool; 23 | console.log("Pool:", JSON.stringify(pool)); 24 | 25 | pool.update(infoHash, peer, metrics, event); 26 | var torrentInfo = pool.getInfo(infoHash); 27 | var wantedPeers = pool.getPeers(infoHash, peer, wants); 28 | 29 | console.log("TorrentInfo:", JSON.stringify(torrentInfo)); 30 | console.log("WantedPeers:", JSON.stringify(wantedPeers)); 31 | 32 | var responseText = formatters.announce(torrentInfo, wantedPeers, compact); 33 | 34 | ctx.response.writeHead(200, { 'Content-Type': 'text/plain' }); 35 | ctx.response.end(responseText, 'ascii'); 36 | }; 37 | 38 | // default stubbed pool implementation 39 | announce.pool = { 40 | update: utils.noop, 41 | getInfo: function() { return { complete: 0, incomplete: 0 }; }, 42 | getPeers: function() { return []; } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/pool_dummy.js: -------------------------------------------------------------------------------- 1 | 2 | var utils = require('./utils.js'); 3 | 4 | var Pool = exports.Pool = function() { 5 | this.torrents = {}; 6 | }; 7 | 8 | Pool.prototype.getInfo = function(infoHash) { 9 | var complete = 0; 10 | var incomplete = 0; 11 | var torrent = this.torrents[infoHash]; 12 | for (var peerId in (torrent && torrent.peers || {})) { 13 | var metrics = torrent.peers[peerId].metrics; 14 | var left = metrics && metrics.left || 0; 15 | if (left > 0) { 16 | incomplete += 1; 17 | } else { 18 | complete += 1; 19 | } 20 | } 21 | return { complete: complete, incomplete: incomplete }; 22 | }; 23 | 24 | Pool.prototype.getPeers = function(infoHash, peer, numWant) { 25 | var torrent = this.torrents[infoHash]; 26 | var wantedPeers = []; 27 | for (var peerId in (torrent && torrent.peers || {})) { 28 | if (wantedPeers.length >= numWant) break; 29 | if (peerId == peer.id) continue; 30 | var wantedPeer = torrent.peers[peerId]; 31 | wantedPeers.push({ id: peerId, ip: wantedPeer.ip, port: wantedPeer.port }); 32 | } 33 | return wantedPeers; 34 | }; 35 | 36 | Pool.prototype.update = function(infoHash, peerInfo, metricsInfo, event) { 37 | var torrent = this.torrents[infoHash]; 38 | if (torrent == null) { 39 | torrent = this.torrents[infoHash] = { peers: {} }; 40 | } 41 | 42 | if (event == 'stopped') { 43 | delete torrent.peers[peerInfo.id]; 44 | } else { 45 | var peer = torrent.peers[peerInfo.id]; 46 | if (peer == null) { 47 | peer = torrent.peers[peerInfo.id] = { metrics: {} }; 48 | } 49 | peer.ip = peerInfo.ip; 50 | peer.port = peerInfo.port; 51 | peer.metrics = { uploaded: metricsInfo.uploaded, downloaded: metricsInfo.downloaded, left: metricsInfo.left }; 52 | } 53 | 54 | // TODO: remove outdated peers and torrents without peers 55 | }; 56 | -------------------------------------------------------------------------------- /lib/tracker.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | var handlers = require('./handlers.js'); 4 | var dispatcher = require('./dispatcher.js'); 5 | 6 | var pool = new (require('./pool_dummy.js').Pool)(); 7 | 8 | handlers.announce.pool = pool; 9 | 10 | var dispatch = dispatcher.forHandlers({ 11 | "/announce": handlers.announce 12 | }); 13 | 14 | http.createServer(function(request, response) { 15 | console.log("Incoming request to url", request.url); 16 | dispatch(request, response); 17 | }).listen(8080); 18 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | exports.noop = function() {}; 3 | 4 | exports.returnThis = function() { return this; }; 5 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pleax/node-tracker/286335a7fc0f88bbcbbdc4e0669677a2ad56d840/test/.gitkeep -------------------------------------------------------------------------------- /test/mocks.js: -------------------------------------------------------------------------------- 1 | var url = require('url'); 2 | var qs = require('querystring'); 3 | var utils = require('../lib/utils.js'); 4 | 5 | var mockRequest = exports.mockRequest = function(options) { 6 | var options = options || {}; 7 | return { 8 | url: options.url || '/', 9 | connection: options.connection 10 | }; 11 | }; 12 | 13 | var mockResponse = exports.mockResponse = function(options) { 14 | var options = options || {}; 15 | return { 16 | writeHead: options.writeHead || utils.returnThis, 17 | write: options.write || utils.returnThis, 18 | end: options.end || utils.returnThis 19 | }; 20 | }; 21 | 22 | var mockContext = exports.mockContext = function(requestOpts, responseOpts) { 23 | var responseOpts = responseOpts || {}; 24 | var responseOpts = responseOpts || {}; 25 | 26 | var request = mockRequest(requestOpts); 27 | var response = mockResponse(responseOpts); 28 | 29 | var tokens = url.parse(request.url); 30 | var params = qs.parse(tokens.query); 31 | 32 | return { 33 | params: params, 34 | url: tokens, 35 | request: request, 36 | response: response 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | require('./test-bencode.js'); 2 | require('./test-utils.js'); 3 | require('./test-dispatcher.js'); 4 | require('./test-formatters.js'); 5 | require('./test-handlers.js'); 6 | require('./test-pool.js'); 7 | -------------------------------------------------------------------------------- /test/test-bencode.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var b = require('../lib/bencode.js'); 4 | 5 | (function() { 6 | assert.equal(b.encode(10), "i10e"); 7 | })(); 8 | 9 | (function() { 10 | assert.equal(b.encode(0), "i0e"); 11 | })(); 12 | 13 | (function() { 14 | assert.equal(b.encode("string"), "6:string"); 15 | })(); 16 | 17 | (function() { 18 | assert.equal(b.encode(""), "0:"); 19 | })(); 20 | 21 | (function() { 22 | assert.equal(b.encode("\x01string\x02"), "8:\x01string\x02"); 23 | })(); 24 | 25 | (function() { 26 | assert.equal(b.encode([1, 2, 3]), "li1ei2ei3ee"); 27 | })(); 28 | 29 | (function() { 30 | assert.equal(b.encode(["abc", 2, 3]), "l3:abci2ei3ee"); 31 | })(); 32 | 33 | (function() { 34 | assert.equal(b.encode(["abc", [1, 2], 3]), "l3:abcli1ei2eei3ee"); 35 | })(); 36 | 37 | (function() { 38 | assert.equal(b.encode({ "d-o": { k: "v" } }), "d3:d-od1:k1:vee"); 39 | })(); 40 | 41 | (function() { 42 | assert.equal(b.encode({ 0: " - ", k: "abc", b: 42, c: [1, "def"], "d-o": { k: "v" } }), 43 | "d1:03: - 1:bi42e1:cli1e3:defe3:d-od1:k1:ve1:k3:abce"); 44 | })(); 45 | -------------------------------------------------------------------------------- /test/test-dispatcher.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var dispatcher = require('../lib/dispatcher.js'); 4 | var utils = require('../lib/utils.js'); 5 | var mocks = require('./mocks.js'); 6 | 7 | (function() { 8 | var executed = false; 9 | var handleAnnounce = function() { executed = true }; 10 | var dispatch = dispatcher.forHandlers({ 11 | "/announce": handleAnnounce 12 | }); 13 | dispatch(mocks.mockRequest({ url: '/announce?info_hash=12345678901234567890' }), mocks.mockResponse()); 14 | assert.ok(executed); 15 | })(); 16 | 17 | (function() { 18 | var executed = false; 19 | var dispatch = dispatcher.forHandlers({}); 20 | var request = mocks.mockRequest({ url: '/unknown' }); 21 | var response = mocks.mockResponse({ 22 | writeHead: function(code) { if (code == 404) executed = true; } 23 | }); 24 | dispatch(request, response); 25 | assert.ok(executed); 26 | })(); 27 | -------------------------------------------------------------------------------- /test/test-formatters.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var b = require('../lib/bencode.js'); 4 | var formatters = require('../lib/formatters.js'); 5 | 6 | (function() { 7 | var torrentInfo = { 8 | complete: 0, 9 | incomplete: 0 10 | }; 11 | var wantedPeers = []; 12 | assert.equal(formatters.announce(torrentInfo, wantedPeers), b.encode({ 13 | interval: 10, 14 | complete: 0, 15 | incomplete: 0, 16 | peers: [] 17 | })); 18 | })(); 19 | 20 | (function() { 21 | var torrentInfo = { 22 | complete: 10, 23 | incomplete: 30 24 | }; 25 | var wantedPeers = []; 26 | assert.equal(formatters.announce(torrentInfo, wantedPeers), b.encode({ 27 | interval: 10, 28 | complete: 10, 29 | incomplete: 30, 30 | peers: [] 31 | })); 32 | })(); 33 | 34 | (function() { 35 | var torrentInfo = { 36 | complete: 10, 37 | incomplete: 30 38 | }; 39 | var wantedPeers = [ 40 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6337 } 41 | ]; 42 | assert.equal(formatters.announce(torrentInfo, wantedPeers), b.encode({ 43 | interval: 10, 44 | complete: 10, 45 | incomplete: 30, 46 | peers: [ 47 | { "peer id": "peerId-8901234567890", ip: "192.0.32.10", port: 6337 } 48 | ] 49 | })); 50 | })(); 51 | 52 | (function() { 53 | var torrentInfo = { 54 | complete: 10, 55 | incomplete: 30 56 | }; 57 | var wantedPeers = [ 58 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6337 }, 59 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6339 } 60 | ]; 61 | assert.equal(formatters.announce(torrentInfo, wantedPeers), b.encode({ 62 | interval: 10, 63 | complete: 10, 64 | incomplete: 30, 65 | peers: [ 66 | { "peer id": "peerId-8901234567890", ip: "192.0.32.10", port: 6337 }, 67 | { "peer id": "peerId-8901234567890", ip: "192.0.32.10", port: 6339 } 68 | ] 69 | })); 70 | })(); 71 | 72 | 73 | (function() { 74 | var torrentInfo = { 75 | complete: 0, 76 | incomplete: 0 77 | }; 78 | var wantedPeers = []; 79 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 80 | interval: 10, 81 | complete: 0, 82 | incomplete: 0, 83 | peers: "" 84 | })); 85 | })(); 86 | 87 | (function() { 88 | var torrentInfo = { 89 | complete: 10, 90 | incomplete: 30 91 | }; 92 | var wantedPeers = []; 93 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 94 | interval: 10, 95 | complete: 10, 96 | incomplete: 30, 97 | peers: "" 98 | })); 99 | })(); 100 | 101 | (function() { 102 | var torrentInfo = { 103 | complete: 10, 104 | incomplete: 30 105 | }; 106 | var wantedPeers = [ 107 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6337 } 108 | ]; 109 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 110 | interval: 10, 111 | complete: 10, 112 | incomplete: 30, 113 | peers: "\xc0\x00\x20\x0a\x18\xc1" 114 | })); 115 | })(); 116 | 117 | (function() { 118 | var torrentInfo = { 119 | complete: 10, 120 | incomplete: 30 121 | }; 122 | var wantedPeers = [ 123 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6337 }, 124 | { id: "peerId-8901234567890", ip: "192.0.32.10", port: 6339 } 125 | ]; 126 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 127 | interval: 10, 128 | complete: 10, 129 | incomplete: 30, 130 | peers: "\xc0\x00\x20\x0a\x18\xc1\xc0\x00\x20\x0a\x18\xc3" 131 | })); 132 | })(); 133 | 134 | (function() { 135 | var torrentInfo = { 136 | complete: 10, 137 | incomplete: 30 138 | }; 139 | var wantedPeers = [ 140 | { id: "peerId-8901234567890", ip: "example.com", port: 6337 } 141 | ]; 142 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 143 | interval: 10, 144 | complete: 10, 145 | incomplete: 30, 146 | peers: "" 147 | })); 148 | })(); 149 | 150 | (function() { 151 | var torrentInfo = { 152 | complete: 10, 153 | incomplete: 30 154 | }; 155 | var wantedPeers = [ 156 | { id: "peerId-8901234567890", ip: "a.b.example.com", port: 6337 } 157 | ]; 158 | assert.equal(formatters.announce(torrentInfo, wantedPeers, true), b.encode({ 159 | interval: 10, 160 | complete: 10, 161 | incomplete: 30, 162 | peers: "" 163 | })); 164 | })(); 165 | 166 | -------------------------------------------------------------------------------- /test/test-handlers.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var handlers = require('../lib/handlers.js'); 3 | var formatters = require('../lib/formatters.js'); 4 | var utils = require('../lib/utils.js'); 5 | var mocks = require('./mocks.js'); 6 | 7 | var generateAnnounceUrl = function(options) { 8 | var options = options || {}; 9 | var tokens = []; 10 | tokens.push("info_hash=" + (options.infoHash || "12345678901234567890")); 11 | tokens.push("peer_id=" + (options.peerId || "peerId-8901234567890")); 12 | tokens.push("port=" + (options.port || 6883)); 13 | tokens.push("uploaded=" + (options.uploaded || 0)); 14 | tokens.push("downloaded=" + (options.downloaded || 0)); 15 | tokens.push("left=" + (options.left || 0)); 16 | if (options.compact != null) { 17 | tokens.push("compact=" + (options.compact ? '1' : '0')); 18 | } 19 | if (options.event) { 20 | tokens.push("event=" + options.event); 21 | } 22 | if (options.ip) { 23 | tokens.push("ip=" + options.ip); 24 | } 25 | if (options.numWant) { 26 | tokens.push("numwant=" + options.numWant); 27 | } 28 | return "/announce?" + tokens.join('&'); 29 | }; 30 | 31 | var mockPool = function(options) { 32 | var options = options || {}; 33 | return { 34 | update: options.update || utils.noop, 35 | getInfo: options.getInfo || function() { 36 | return { complete: 0, incomplete: 0 }; 37 | }, 38 | getPeers: options.getPeers || function() { return []; } 39 | }; 40 | }; 41 | 42 | (function() { 43 | var resposeFinished = false; 44 | var ctx = mocks.mockContext({ 45 | url: generateAnnounceUrl(), 46 | connection: { remoteAddress: "192.0.32.10" } 47 | }, { 48 | end: function() { resposeFinished = true; } 49 | }); 50 | handlers.announce.pool = mockPool(); 51 | handlers.announce(ctx); 52 | assert.ok(resposeFinished); 53 | })(); 54 | 55 | (function() { 56 | var resposeCodeOk = false; 57 | var ctx = mocks.mockContext({ 58 | url: generateAnnounceUrl(), 59 | connection: { remoteAddress: "192.0.32.10" } 60 | }, { 61 | writeHead: function(code) { if (code == 200) resposeCodeOk = true; } 62 | }); 63 | handlers.announce.pool = mockPool(); 64 | handlers.announce(ctx); 65 | assert.ok(resposeCodeOk); 66 | })(); 67 | 68 | (function() { 69 | var rightEncoding = true; 70 | var validateEncoding = function(data, enc) { if (enc != 'ascii') rightEncoding = false; } 71 | var ctx = mocks.mockContext({ 72 | url: generateAnnounceUrl(), 73 | connection: { remoteAddress: "192.0.32.10" } 74 | }, { 75 | write: validateEncoding, 76 | end: validateEncoding 77 | }); 78 | handlers.announce.pool = mockPool(); 79 | handlers.announce(ctx); 80 | assert.ok(rightEncoding); 81 | })(); 82 | 83 | (function() { 84 | var updateFired = false; 85 | var ctx = mocks.mockContext({ 86 | url: generateAnnounceUrl(), 87 | connection: { remoteAddress: "192.0.32.10" } 88 | }); 89 | handlers.announce.pool = mockPool({ 90 | update: function() { updateFired = true; } 91 | }); 92 | handlers.announce(ctx); 93 | assert.ok(updateFired); 94 | })(); 95 | 96 | (function() { 97 | var infoRequested = false; 98 | var ctx = mocks.mockContext({ 99 | url: generateAnnounceUrl(), 100 | connection: { remoteAddress: "192.0.32.10" } 101 | }); 102 | handlers.announce.pool = mockPool({ 103 | getInfo: function() { infoRequested = true; return { complete: 0, incomplete: 0 }; } 104 | }); 105 | handlers.announce(ctx); 106 | assert.ok(infoRequested); 107 | })(); 108 | 109 | (function() { 110 | var peersRequested = false; 111 | var ctx = mocks.mockContext({ 112 | url: generateAnnounceUrl(), 113 | connection: { remoteAddress: "192.0.32.10" } 114 | }); 115 | handlers.announce.pool = mockPool({ 116 | getPeers: function() { peersRequested = true; return []; } 117 | }); 118 | handlers.announce(ctx); 119 | assert.ok(peersRequested); 120 | })(); 121 | 122 | (function() { 123 | var responseText = ""; 124 | var ctx = mocks.mockContext({ 125 | url: generateAnnounceUrl(), 126 | connection: { remoteAddress: "192.0.32.10" } 127 | }, { 128 | write: function(data, enc) { responseText += data; }, 129 | end: function(data, enc) { if (data) responseText += data; } 130 | }); 131 | var pool = handlers.announce.pool = mockPool({ 132 | getPeers: function() { 133 | return [ 134 | { id: "peer-stub-1-34567890", ip: "192.0.32.10", port: 6337 }, 135 | { id: "peer-stub-2-34567890", ip: "192.0.32.10", port: 6339 } 136 | ]; 137 | } 138 | }); 139 | handlers.announce(ctx); 140 | assert.equal(responseText, formatters.announce(pool.getInfo(), pool.getPeers())); 141 | })(); 142 | 143 | (function() { 144 | var responseText = ""; 145 | var ctx = mocks.mockContext({ 146 | url: generateAnnounceUrl({ compact: true }), 147 | connection: { remoteAddress: "192.0.32.10" } 148 | }, { 149 | write: function(data, enc) { responseText += data; }, 150 | end: function(data, enc) { if (data) responseText += data; } 151 | }); 152 | var pool = handlers.announce.pool = mockPool({ 153 | getPeers: function() { 154 | return [ 155 | { id: "peer-stub-1-34567890", ip: "192.0.32.10", port: 6337 }, 156 | { id: "peer-stub-2-34567890", ip: "192.0.32.10", port: 6339 } 157 | ]; 158 | } 159 | }); 160 | handlers.announce(ctx); 161 | assert.equal(responseText, formatters.announce(pool.getInfo(), pool.getPeers(), true)); 162 | })(); 163 | -------------------------------------------------------------------------------- /test/test-pool.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Pool = require('../lib/pool_dummy.js').Pool; 3 | 4 | // fixtures... kinda :) 5 | 6 | var infoHash = "00000000000000000001"; 7 | var peer = { 8 | id: "peerId-0000000000001", 9 | ip: "192.0.32.10", 10 | port: 6337 11 | }; 12 | var peer2 = { 13 | id: 'TorrentPeer-42-2', 14 | ip: 'peer2.example.com', 15 | port: 6881 16 | }; 17 | 18 | // pool with no peers returns zero counts for any new infoHash 19 | (function() { 20 | var pool = new Pool(); 21 | assert.deepEqual(pool.getInfo(infoHash), { complete: 0, incomplete: 0 }); 22 | })(); 23 | 24 | // counts peer as leecher if left > 0 25 | (function() { 26 | var pool = new Pool(); 27 | pool.update(infoHash, peer, { left: 10 }); 28 | assert.deepEqual(pool.getInfo(infoHash), { complete: 0, incomplete: 1 }); 29 | })(); 30 | 31 | // counts peer as seeder if left == 0 32 | (function() { 33 | var pool = new Pool(); 34 | pool.update(infoHash, peer, { left: 0 }); 35 | assert.deepEqual(pool.getInfo(infoHash), { complete: 1, incomplete: 0 }); 36 | })(); 37 | 38 | // changes status of peer from incomplete to complete 39 | (function() { 40 | var pool = new Pool(); 41 | pool.update(infoHash, peer, { left: 10 }); 42 | assert.deepEqual(pool.getInfo(infoHash), { complete: 0, incomplete: 1 }); 43 | pool.update(infoHash, peer, { left: 0 }); 44 | assert.deepEqual(pool.getInfo(infoHash), { complete: 1, incomplete: 0 }); 45 | })(); 46 | 47 | // removes peer on 'stopped' event 48 | (function() { 49 | var pool = new Pool(); 50 | pool.update(infoHash, peer, { left: 0 }); 51 | assert.deepEqual(pool.getInfo(infoHash), { complete: 1, incomplete: 0 }); 52 | pool.update(infoHash, peer, { left: 0 }, 'stopped'); 53 | assert.deepEqual(pool.getInfo(infoHash), { complete: 0, incomplete: 0 }); 54 | })(); 55 | 56 | // remove peer on 'stopped' event doesn't touch other peers 57 | (function() { 58 | var pool = new Pool(); 59 | pool.update(infoHash, peer, { left: 0 }); 60 | assert.deepEqual(pool.getInfo(infoHash), { complete: 1, incomplete: 0 }); 61 | pool.update(infoHash, peer2, { left: 0 }); 62 | assert.deepEqual(pool.getInfo(infoHash), { complete: 2, incomplete: 0 }); 63 | pool.update(infoHash, peer, { left: 0 }, 'stopped'); 64 | assert.deepEqual(pool.getInfo(infoHash), { complete: 1, incomplete: 0 }); 65 | })(); 66 | 67 | // returns empty list of peers for any new infoHash 68 | (function() { 69 | var pool = new Pool(); 70 | assert.deepEqual(pool.getPeers(infoHash, peer, 10), []); 71 | })(); 72 | 73 | // returns some active peers for given infoHash 74 | (function() { 75 | var pool = new Pool(); 76 | pool.update(infoHash, peer, { left: 0 }); 77 | pool.update(infoHash, peer2, { left: 0 }); 78 | assert.ok(pool.getPeers(infoHash, peer, 10).length > 0); 79 | })(); 80 | 81 | // doesn't return client which requests peers 82 | (function() { 83 | var pool = new Pool(); 84 | pool.update(infoHash, peer, { left: 0 }); 85 | pool.update(infoHash, peer2, { left: 0 }); 86 | assert.ok(pool.getPeers(infoHash, peer, 10).every(function(p) { 87 | return p.id != peer.id; 88 | })); 89 | })(); 90 | 91 | // returns not more than peers for given infoHash 92 | (function() { 93 | var pool = new Pool(); 94 | for (var i = 0; i < 30; i++) { 95 | pool.update(infoHash, { id: "peer-" + i, ip: "192.0.32.10", port: 6881 }, { left: 0 }); 96 | } 97 | assert.ok(pool.getPeers(infoHash, peer, 5).length <= 5); 98 | })(); 99 | -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var utils = require('../lib/utils.js'); 3 | 4 | (function() { 5 | var that = { key: "value" }; 6 | assert.equal(utils.returnThis.call(that, "any", "arguments", "should", "be", "accepted"), that); 7 | })(); 8 | 9 | (function() { 10 | var that = { key: "value", returnThis: utils.returnThis }; 11 | assert.equal(that.returnThis("any", "arguments", "should", "be", "accepted"), that); 12 | })(); 13 | --------------------------------------------------------------------------------