/**
* Created : May 27, 2012
*
* @author pquiring
*/
import java.net.*;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import javaforce.*;
/** The real "server" part of torrents that keeps "track" of clients. */
public class TorrentTracker extends Thread {
public static void main(String args[]) {
if (args.length == 0) {
System.out.println("Usage:tracker [--port=PORT] torrents...");
System.out.println(" Default port = 6969");
System.exit(1);
}
JFLog.init(".jftorrentTracker.log", true);
for(int a=0;a<args.length;a++) {
if (args[a].startsWith("--port=")) {
port = JF.atoi(args[a].substring(7));
} else {
addTorrent(args[a]);
}
}
new TorrentTracker().start();
}
private ServerSocket ss;
private static int port = 6969;
private boolean active = true;
private static ArrayList<Torrent> torrents = new ArrayList<Torrent>();
private static class Torrent {
byte[] info_hash;
ArrayList<byte[]> peers = new ArrayList<byte[]>();
Object peersLock = new Object();
int complete;
int incomplete;
}
public static void addTorrent(String file) {
Torrent torrent = new Torrent();
//read file and get info_hash
MetaFile metaFile = new MetaFile();
try {
FileInputStream fis = new FileInputStream(file);
byte metaData[] = JF.readAll(fis);
fis.close();
metaFile.read(metaData);
Object info = metaFile.getTag(new String[] {"d", "s:info"});
torrent.info_hash = SHA1sum(Arrays.copyOfRange(metaData, metaFile.tagBegin, metaFile.tagEnd+1));
torrents.add(torrent);
} catch (Exception e) {
JFLog.log(e);
}
}
public static byte[] SHA1sum(byte[] data) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return md.digest(data);
}
public void run() {
try {
ss = new ServerSocket(port);
JFLog.log("TorrentTracker Listening on port " + port);
while (active) {
Socket s = ss.accept();
Client client = new Client(s);
client.start();
}
} catch (Exception e) {
JFLog.log(e);
}
}
private byte[] decode_info_hash(String hashStr) {
byte hash[] = new byte[20];
int pos = 0;
char hashChars[] = hashStr.toCharArray();
for(int a=0;a<20;a++) {
if (hashChars[pos] == '%') {
pos++;
String hexStr = "" + hashChars[pos++] + hashChars[pos++];
hash[a] = (byte)Integer.valueOf(hexStr, 16).intValue();
} else {
hash[a] = (byte)hashChars[pos++];
}
}
return hash;
}
private class Client extends Thread {
private Socket s;
private InputStream is;
private OutputStream os;
public Client(Socket s) {
this.s = s;
}
public void run() {
process();
try { s.close(); } catch (Exception e) {}
}
public void process() {
try {
is = s.getInputStream();
os = s.getOutputStream();
//read until we get a double CR+LF
byte[] req = new byte[1024];
int pos = 0;
int read;
while (true) {
read = is.read(req, pos, 1024-pos);
if (read == 0) continue;
if (read == -1) return;
pos += read;
if (pos < 4) continue;
if (new String(req, pos-4, 4).equals("\r\n\r\n")) break;
}
String lns[] = new String(req, 0, pos).split("\r\n");
if (!lns[0].startsWith("GET ")) throw new Exception("bad request - no GET");
//GET /?args
int idxArgsStart = lns[0].indexOf("?");
int idxArgsEnd = lns[0].indexOf(" ", idxArgsStart);
String args[] = lns[0].substring(idxArgsStart+1, idxArgsEnd).split("&");
String info_hashStr = null, peer_id = null, portStr = null, compact = null, numwantStr = null, event = null;
String uploaded = null, downloaded = null, left = null;
for(int a=0;a<args.length;a++) {
int idx = args[a].indexOf("=");
if (idx == -1) continue;
if (args[a].startsWith("info_hash=")) info_hashStr = args[a].substring(idx+1);
if (args[a].startsWith("peer_id=")) peer_id = args[a].substring(idx+1);
if (args[a].startsWith("port=")) portStr = args[a].substring(idx+1);
if (args[a].startsWith("compact=")) compact = args[a].substring(idx+1);
if (args[a].startsWith("numwant=")) numwantStr = args[a].substring(idx+1);
if (args[a].startsWith("event=")) event = args[a].substring(idx+1);
if (args[a].startsWith("uploaded=")) uploaded = args[a].substring(idx+1);
if (args[a].startsWith("downloaded=")) downloaded = args[a].substring(idx+1);
if (args[a].startsWith("left=")) left = args[a].substring(idx+1);
}
byte peer[] = new byte[6];
InetAddress ia = s.getInetAddress();
byte ip[] = ia.getAddress();
System.arraycopy(ip, 0, peer, 0, 4);
int peerport = JF.atoi(portStr);
peer[4] = (byte)((peerport >> 8) & 0xff);
peer[5] = (byte)(peerport & 0xff);
if (event == null) event = "";
byte info_hash[] = decode_info_hash(info_hashStr);
if (numwantStr == null) numwantStr = "50";
int numwant = JF.atoi(numwantStr);
if (numwant < 50) numwant = 50;
if (numwant > 100) numwant = 100;
if (left == null) left = "1";
for(int a=0;a<torrents.size();a++) {
Torrent t = torrents.get(a);
if (Arrays.equals(t.info_hash, info_hash)) {
ByteArrayOutputStream bb = new ByteArrayOutputStream();
if (!compact.equals("1")) {
JFLog.log("bad request");
bb.write("d14:failure reason12:need compacte".getBytes());
os.write(("HTTP/1.0 500 Bad Request\r\nContent-Length: " + bb.toByteArray().length + "\r\n\r\n").getBytes());
os.write(bb.toByteArray());
os.flush();
return;
}
JFLog.log("event=" + event);
if (event.equals("completed")) {
t.complete++;
t.incomplete--;
}
if (event.equals("stopped")) {
synchronized(t.peersLock) {
int size = t.peers.size();
for(int b=0;b<size;b++) {
if (Arrays.equals(peer, t.peers.get(b))) {
t.peers.remove(b);
JFLog.log("remove peer:" + ia.getHostAddress());
break;
}
}
}
numwant = 0;
}
if (event.equals("started")) {
if (left.equals("0")) t.complete++; else t.incomplete++;
synchronized(t.peersLock) {
t.peers.add(peer);
JFLog.log("new peer:" + ia.getHostAddress());
}
}
bb.write("d".getBytes());
bb.write(("8:completei" + t.complete + "e").getBytes());
bb.write(("10:incompletei" + t.incomplete + "e").getBytes());
bb.write("8:intervali1800e".getBytes());
byte peers[];
synchronized(t.peersLock) {
int numpeers = t.peers.size();
if (numwant >= numpeers) numwant = numpeers-1;
if (numwant == -1) numwant = 0;
peers = new byte[numwant * 6];
boolean used[] = new boolean[numpeers];
Random r = new Random();
for(int b=0;b<numwant;b++) {
int idx = Math.abs(r.nextInt(numpeers));
while (used[idx] || Arrays.equals(t.peers.get(idx), peer)) {
idx++;
if (idx == numpeers) idx = 0;
}
used[idx] = true;
System.arraycopy(t.peers.get(idx), 0, peers, b*6, 6);
}
}
bb.write("5:peers".getBytes());
if (numwant == 0) {
bb.write("0:".getBytes());
} else {
bb.write(((numwant*6) + ":").getBytes());
bb.write(peers);
}
bb.write("e".getBytes()); //end main dict
os.write(("HTTP/1.0 200 OK\r\nContent-Length: " + bb.toByteArray().length + "\r\n\r\n").getBytes());
os.write(bb.toByteArray());
os.flush();
return;
}
}
ByteArrayOutputStream bb = new ByteArrayOutputStream();
bb.write("d14:failure reason16:torrent not heree".getBytes());
os.write(("HTTP/1.0 404 Not Found\r\nContent-Length: " + bb.toByteArray().length + "\r\n\r\n").getBytes());
os.write(bb.toByteArray());
os.flush();
} catch (Exception e) {
JFLog.log(e);
}
}
}
}