Permalink
Browse files

Parse tracker response, try connecting to peers.

  • Loading branch information...
1 parent 0c9fb30 commit 37936dfdb82cc692a0874e5015a947f4d72e02aa @jackpal jackpal committed May 19, 2010
Showing with 254 additions and 127 deletions.
  1. +3 −0 .gitignore
  2. +2 −2 README
  3. +18 −0 listener.js
  4. +32 −31 main.js
  5. +30 −0 peer.js
  6. +93 −56 torrent.js
  7. +76 −38 tracker.js
View
3 .gitignore
@@ -1,2 +1,5 @@
tests/a.torrent
tests/data
+.project
+.settings
+*.torrent
View
4 README
@@ -11,13 +11,13 @@ which is currently more of a bitTorrent seeder than a full bitTorrent client.
Implemented features:
+ Parse .torrent files.
+ Scan disk to compute good/bad pieces for data files.
-+ Request peer list from tracker to request.
++ Request peer list from tracker.
++ Parse tracker response.
TODO:
- UPNP open a port for listening for peers.
- Handle redirects from trackers.
- Create directories for multi-file torrents.
- Listen for peers.
-- Parse tracker response.
- Connect to peers.
- Exchange data with peers.
View
18 listener.js
@@ -0,0 +1,18 @@
+var net = require('net'),
+ peer = require('./peer'),
+ sys = require('sys');
+
+
+function create(port, hashes) {
+ var state = 0, server = net.createServer(function(stream) {
+ sys.log('createServer callback');
+ stream.setEncoding('binary');
+ stream.addListener('connect', function() {
+ sys.log('New connection');
+ // TODO: Tell tracker about this connection.
+ });
+ });
+ server.listen(port);
+}
+
+exports.create = create;
View
63 main.js
@@ -2,41 +2,42 @@ var sys = require('sys'),
torrent = require('./torrent');
function parseArgs(args) {
- var result = {destDir:'.'},
- torrentFiles = [],
- i, argLen, arg;
- for (i = 0, argLen = args.length; i < argLen; i += 1) {
- arg = args[i];
- if (arg.length == 0) {
- throw "Empty argument";
- }
- if (arg.charAt(0) == '-') {
- if (arg === '--destDir') {
- result.destDir = args[i+1];
- i += 1;
- } else {
- throw "Unknown flag " + arg;
- }
- } else {
- torrentFiles.push(arg);
- }
- }
+ var result = {destDir:'.'},
+ torrentFiles = [],
+ i, argLen, arg;
+ for (i = 0, argLen = args.length; i < argLen; i += 1) {
+ arg = args[i];
+ if (arg.length == 0) {
+ throw "Empty argument";
+ }
+ if (arg.charAt(0) == '-') {
+ if (arg === '--destDir') {
+ result.destDir = args[i+1];
+ i += 1;
+ } else {
+ throw "Unknown flag " + arg;
+ }
+ } else {
+ torrentFiles.push(arg);
+ }
+ }
result.files = torrentFiles;
return result;
}
function main() {
- var args = parseArgs(process.argv.slice(2)),
- torrentFiles = args.files,
- i, iLen, file;
- if (torrentFiles.length == 0) {
- throw "No torrent files specified.";
- } else {
- for (i = 0, iLen = torrentFiles.length; i < iLen; i += 1) {
- file = torrentFiles[i];
- torrent.startTorrent(file, args.destDir);
- }
- }
+ var args = parseArgs(process.argv.slice(2)),
+ torrentFiles = args.files,
+ i, iLen, file, aTorrent;
+ if (torrentFiles.length == 0) {
+ throw "No torrent files specified.";
+ } else {
+ for (i = 0, iLen = torrentFiles.length; i < iLen; i += 1) {
+ file = torrentFiles[i];
+ aTorrent = torrent.create(file, args.destDir);
+ aTorrent.start();
+ }
+ }
}
-main();
+main();
View
30 peer.js
@@ -0,0 +1,30 @@
+var net = require('net'),
+ sys = require('sys');
+
+function create(host, port) {
+ sys.log('peer.create ' + host + ':' + port);
+ var peer = {
+ host: host,
+ port: port,
+ exchangedHeader: false,
+ stream: net.createConnection(port, host),
+ connect: function() {
+ sys.log("Connection established to " + this.host + ':' + this.port);
+ this.stream.write('\x13BitTorrent protocol\0\0\0\0\0\0\0\0');
+ }
+ };
+
+ peer.stream.setEncoding('binary');
+ sys.log('peer.create 3 ' + host + ':' + port);
+ peer.stream.addListener('connect', function() {
+ sys.log('peer.stream connect ' + host + ':' + port);
+ peer.connect();
+ });
+ peer.stream.addListener('error', function(e) {
+ sys.log('peer.stream error ' + host + ':' + port + ' ' + e);
+ })
+ sys.log('peer.create 4 ' + host + ':' + port);
+ return peer;
+}
+
+exports.create = create;
View
149 torrent.js
@@ -1,63 +1,100 @@
-var sys = require('sys'),
- fs = require('fs'),
- bencode = require('./bencode'),
- filestore = require('./filestore'),
- tracker = require("./tracker"),
+var bencode = require('./bencode'),
crypto = require('crypto');
+ filestore = require('./filestore'),
+ fs = require('fs'),
+ listener = require('./listener'),
+ peer = require('./peer'),
+ sys = require('sys'),
+ tracker = require("./tracker");
-function log(msg) {
- sys.puts(msg);
-}
+function create(torrentPath, destDir) {
+ return {
+ torrentPath: torrentPath,
+ destDir: destDir,
+ listenerPort: 6881,
+ peers: {},
+ pingTracker: function () {
+ var that = this,
+ params = {
+ info_hash: this.metaInfo.info_hash,
+ peer_id: '01234567890123456789',
+ port: this.listenerPort,
+ uploaded: 0,
+ downloaded: 0,
+ left: this.store.left,
+ compact:1,
+ event:'started'
+ };
+ tracker.ping(this.trackerClient, params, function (error, response) {
+ var newPeers, numPeers, i, interval = 3600;
+ if (!error) {
+ interval = Math.max(interval, response.interval);
+ newPeers = response.peers;
+ numPeers = Math.floor(newPeers.length / 6);
+ sys.log('Tracker gave us ' + numPeers + ' peers.');
+ for (i = 0; i < numPeers; i++ ) {
+ that.addPeer(newPeers.substring(i*6,(i+1)*6));
+ }
+ }
+ that.pingTimer = setTimeout(function () {
+ that.pingTracker();
+ }, interval * 1000);
+ });
+ },
-function pingTracker(metaInfo, store, trackerClient) {
- var params = {
- info_hash: metaInfo.info_hash,
- peer_id: '01234567890123456789',
- port: 6881,
- uploaded: 0,
- downloaded: 0,
- left: store.left,
- compact:1,
- event:'started'
- };
- tracker.ping(trackerClient, params, function (error, response) {
- system.log('pingTracker callback ' + error + ' ' + JSON.stringify(response));
- });
-}
+ addPeer : function (peerAddress) {
+ if ( ! (peerAddress in this.peers) ) {
+ this.peers[peerAddress] = peer.create(
+ this.decodeHost(peerAddress),
+ this.decodePort(peerAddress));
+ }
+ },
-function computeHash(info) {
- var encoded = bencode.encode(info),
- hash = crypto.createHash('sha1');
- hash.update(encoded);
- return hash.digest('binary');
-}
+ computeHash: function (info) {
+ var encoded = bencode.encode(info),
+ hash = crypto.createHash('sha1');
+ hash.update(encoded);
+ return hash.digest('binary');
+ },
+
+ decodeHost: function (address) {
+ return address.charCodeAt(0) + '.' + address.charCodeAt(1) + '.' + address.charCodeAt(2) + '.' + address.charCodeAt(3);
+ },
+
+ decodePort: function (address) {
+ return (address.charCodeAt(4) << 8) + address.charCodeAt(5);
+ },
-function startTorrent(torrentPath, destDir) {
- var metaInfo, store, error2, trackerClient;
- sys.puts('Starting torrent ' + torrentPath);
- fs.readFile(torrentPath, 'binary', function startTorrentCallback(error, contents) {
- var aFileStore;
- if (error) {
- log('Could not open torrent file ' + torrentPath + ': ' + error);
- } else {
- metaInfo = bencode.decode(contents);
- if ('comment' in metaInfo) {
- sys.puts('Torrent \'' + metaInfo.comment + '\'');
- }
- metaInfo.info_hash = computeHash(metaInfo.info);
- store = filestore.create(metaInfo, destDir);
- sys.log('inspecting files');
- filestore.inspect(store,
- function inspectCallback(error) {
- if (error) {
- log('Could not inspect torrent files ' + error);
- } else {
- trackerClient = tracker.create(metaInfo);
- pingTracker(metaInfo, store, trackerClient);
- }
- });
- }
- });
+ start : function() {
+ var that = this;
+ sys.puts('Starting torrent ' + this.torrentPath);
+ fs.readFile(this.torrentPath, 'binary',
+ function startTorrentCallback(error, contents) {
+ if (error) {
+ sys.log('Could not open torrent file ' + that.torrentPath + ': ' + error);
+ } else {
+ that.metaInfo = bencode.decode(contents);
+ if ('comment' in that.metaInfo) {
+ sys.log('Torrent \'' + that.metaInfo.comment + '\'');
+ }
+ that.metaInfo.info_hash = that.computeHash(that.metaInfo.info);
+ that.store = filestore.create(that.metaInfo, that.destDir);
+ sys.log('inspecting files');
+ filestore.inspect(that.store,
+ function inspectCallback(error) {
+ if (error) {
+ sys.log('Could not inspect torrent files ' + error);
+ } else {
+ sys.log('finished inspecting files.');
+ listener.create(that.listenerPort, [that.metaInfo.info_hash]);
+ that.trackerClient = tracker.create(that.metaInfo);
+ that.pingTracker();
+ }
+ });
+ }
+ });
+ }
+ };
}
-exports.startTorrent = startTorrent;
+exports.create = create;
View
114 tracker.js
@@ -1,6 +1,7 @@
var sys = require('sys'),
- http = require('http'),
- url = require('url');
+ http = require('http'),
+ bencode = require('bencode'),
+ url = require('url');
function toHexDigit(n) {
return '0123456789abcdef'[n];
@@ -15,8 +16,8 @@ function escapeBinary(s) {
for (i = 0, len = s.length; i < len; i += 1) {
c = s.charAt(i);
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
- || (c == '.' || c == '-' || c == '_' || c == '~')) {
- result += c;
+ || (c == '.' || c == '-' || c == '_' || c == '~')) {
+ result += c;
} else {
cc = s.charCodeAt(i);
result += '%' + toHexDigit(0xf & (cc >> 4)) + toHexDigit(0xf & cc);
@@ -26,46 +27,83 @@ function escapeBinary(s) {
}
function queryStringify(params) {
- var result = '', key, first = true;
- for (key in params) {
- if (params.hasOwnProperty(key)) {
- if (first) {
- first = false;
- } else {
- result += '&';
- }
- result += key + '=' + escapeBinary(params[key]);
- }
- }
- return result;
+ var result = '', key, first = true;
+ for (key in params) {
+ if (params.hasOwnProperty(key)) {
+ if (first) {
+ first = false;
+ } else {
+ result += '&';
+ }
+ result += key + '=' + escapeBinary(params[key]);
+ }
+ }
+ return result;
}
function create(metaInfo) {
- var announce = metaInfo.announce,
- parsedUrl = url.parse(announce),
- port = parsedUrl.port ? parsedUrl.port : 80;
- return {metaInfo: metaInfo,
- tracker: http.createClient(port, parsedUrl.hostname),
- trackerRelativeUri: parsedUrl.pathname,
- host: parsedUrl.hostname,
- peers: {}};
+ var announce = metaInfo.announce,
+ parsedUrl = url.parse(announce),
+ port = parsedUrl.port ? parsedUrl.port : 80;
+ return {metaInfo: metaInfo,
+ port: port,
+ trackerRelativeUri: parsedUrl.pathname,
+ host: parsedUrl.hostname,
+ peers: {}};
+}
+
+// callback(exception, response, body)
+// Handles redirects, coalescing response.
+
+function httpRequestHelper(verb, host, port, path, headers, redirectLimit, callback) {
+ headers.host = host;
+ var client = http.createClient(port, host),
+ request = client.request(verb, path, headers);
+ request.addListener('response', function (response) {
+ var statusCode = response.statusCode,
+ body = '';
+ if (statusCode == 200) {
+ response.setEncoding('binary');
+ response.addListener('error', function (error) {
+ callback(error, response, body);
+ });
+ response.addListener('end', function () {
+ callback(null, response, body);
+ });
+ response.addListener('data', function (chunk) {
+ body += chunk;
+ });
+ } else if (statusCode >= 300 && statusCode <= 399) {
+ if (redirectLimit <= 0) {
+ callback('Too many redirects', response);
+ } else {
+ sys.log('redirect ' + statusCode + ' ' + JSON.stringify(body));
+ httpRequestHelper(verb, host, port, path, headers, redirectLimit-1, callback);
+ }
+ } else {
+ callback('error', response, body);
+ }
+ });
+ request.end();
}
+// callback(error, {response})
function ping(trackerClient, params, callback) {
- var path = trackerClient.trackerRelativeUri + '?' +
- queryStringify(params),
- request = trackerClient.tracker.request('GET', path,
- {'host': trackerClient.host});
- sys.log('path:' + path);
- request.addListener('response', function (response) {
- sys.puts('STATUS: ' + response.statusCode);
- sys.puts('HEADERS: ' + JSON.stringify(response.headers));
- response.setEncoding('binary');
- response.addListener('data', function (chunk) {
- sys.puts('BODY: ' + chunk);
- });
- });
- request.end();
+ var path = trackerClient.trackerRelativeUri + '?' +
+ queryStringify(params);
+ sys.log('pinging tracker');
+ httpRequestHelper('GET', trackerClient.host, trackerClient.port, path, {}, 10,
+ function (error, response, body) {
+ var result = {};
+ if (!error) {
+ try {
+ result = bencode.decode(body);
+ } catch (e) {
+ error = e;
+ }
+ }
+ callback(error, result);
+ });
}
exports.create = create;

0 comments on commit 37936df

Please sign in to comment.