- Protection against modifying metainfos

- Announce peers to local tracker
- Ping node on port reception
- More info on directory pages
- Cleanups
This commit is contained in:
zzz
2010-12-22 22:22:38 +00:00
parent 973407498b
commit 97f0c13c15
9 changed files with 141 additions and 43 deletions

View File

@ -54,28 +54,30 @@ public class MetaInfo
private final byte[] info_hash; private final byte[] info_hash;
private final String name; private final String name;
private final String name_utf8; private final String name_utf8;
private final List files; private final List<List<String>> files;
private final List files_utf8; private final List<List<String>> files_utf8;
private final List lengths; private final List<Long> lengths;
private final int piece_length; private final int piece_length;
private final byte[] piece_hashes; private final byte[] piece_hashes;
private final long length; private final long length;
private Map infoMap; private Map<String, BEValue> infoMap;
/** /**
* Called by Storage when creating a new torrent from local data * Called by Storage when creating a new torrent from local data
* *
* @param announce may be null * @param announce may be null
* @param files null for single-file torrent
* @param lengths null for single-file torrent
*/ */
MetaInfo(String announce, String name, String name_utf8, List files, List lengths, MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
int piece_length, byte[] piece_hashes, long length) int piece_length, byte[] piece_hashes, long length)
{ {
this.announce = announce; this.announce = announce;
this.name = name; this.name = name;
this.name_utf8 = name_utf8; this.name_utf8 = name_utf8;
this.files = files; this.files = files == null ? null : Collections.unmodifiableList(files);
this.files_utf8 = null; this.files_utf8 = null;
this.lengths = lengths; this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
this.piece_length = piece_length; this.piece_length = piece_length;
this.piece_hashes = piece_hashes; this.piece_hashes = piece_hashes;
this.length = length; this.length = length;
@ -131,7 +133,7 @@ public class MetaInfo
if (val == null) if (val == null)
throw new InvalidBEncodingException("Missing info map"); throw new InvalidBEncodingException("Missing info map");
Map info = val.getMap(); Map info = val.getMap();
infoMap = info; infoMap = Collections.unmodifiableMap(info);
val = (BEValue)info.get("name"); val = (BEValue)info.get("name");
if (val == null) if (val == null)
@ -171,39 +173,39 @@ public class MetaInfo
throw new InvalidBEncodingException throw new InvalidBEncodingException
("Missing length number and/or files list"); ("Missing length number and/or files list");
List list = val.getList(); List<BEValue> list = val.getList();
int size = list.size(); int size = list.size();
if (size == 0) if (size == 0)
throw new InvalidBEncodingException("zero size files list"); throw new InvalidBEncodingException("zero size files list");
files = new ArrayList(size); List<List<String>> m_files = new ArrayList(size);
files_utf8 = new ArrayList(size); List<List<String>> m_files_utf8 = new ArrayList(size);
lengths = new ArrayList(size); List<Long> m_lengths = new ArrayList(size);
long l = 0; long l = 0;
for (int i = 0; i < list.size(); i++) for (int i = 0; i < list.size(); i++)
{ {
Map desc = ((BEValue)list.get(i)).getMap(); Map<String, BEValue> desc = list.get(i).getMap();
val = (BEValue)desc.get("length"); val = desc.get("length");
if (val == null) if (val == null)
throw new InvalidBEncodingException("Missing length number"); throw new InvalidBEncodingException("Missing length number");
long len = val.getLong(); long len = val.getLong();
lengths.add(new Long(len)); m_lengths.add(new Long(len));
l += len; l += len;
val = (BEValue)desc.get("path"); val = (BEValue)desc.get("path");
if (val == null) if (val == null)
throw new InvalidBEncodingException("Missing path list"); throw new InvalidBEncodingException("Missing path list");
List path_list = val.getList(); List<BEValue> path_list = val.getList();
int path_length = path_list.size(); int path_length = path_list.size();
if (path_length == 0) if (path_length == 0)
throw new InvalidBEncodingException("zero size file path list"); throw new InvalidBEncodingException("zero size file path list");
List file = new ArrayList(path_length); List<String> file = new ArrayList(path_length);
Iterator it = path_list.iterator(); Iterator<BEValue> it = path_list.iterator();
while (it.hasNext()) while (it.hasNext())
file.add(((BEValue)it.next()).getString()); file.add(it.next().getString());
files.add(file); m_files.add(Collections.unmodifiableList(file));
val = (BEValue)desc.get("path.utf-8"); val = (BEValue)desc.get("path.utf-8");
if (val != null) { if (val != null) {
@ -213,11 +215,14 @@ public class MetaInfo
file = new ArrayList(path_length); file = new ArrayList(path_length);
it = path_list.iterator(); it = path_list.iterator();
while (it.hasNext()) while (it.hasNext())
file.add(((BEValue)it.next()).getString()); file.add(it.next().getString());
files_utf8.add(file); m_files_utf8.add(Collections.unmodifiableList(file));
} }
} }
} }
files = Collections.unmodifiableList(m_files);
files_utf8 = Collections.unmodifiableList(m_files_utf8);
lengths = Collections.unmodifiableList(m_lengths);
length = l; length = l;
} }
@ -265,9 +270,8 @@ public class MetaInfo
* a single name. It has the same size as the list returned by * a single name. It has the same size as the list returned by
* getLengths(). * getLengths().
*/ */
public List getFiles() public List<List<String>> getFiles()
{ {
// XXX - Immutable?
return files; return files;
} }
@ -276,9 +280,8 @@ public class MetaInfo
* files, or null if it is a single file. It has the same size as * files, or null if it is a single file. It has the same size as
* the list returned by getFiles(). * the list returned by getFiles().
*/ */
public List getLengths() public List<Long> getLengths()
{ {
// XXX - Immutable?
return lengths; return lengths;
} }

View File

@ -32,6 +32,7 @@ import java.util.Map;
import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log; import net.i2p.util.Log;
import org.klomp.snark.bencode.BEValue; import org.klomp.snark.bencode.BEValue;
@ -384,6 +385,13 @@ public class Peer implements Comparable
return options; return options;
} }
/** @since 0.8.4 */
public Destination getDestination() {
if (sock == null)
return null;
return sock.getPeerDestination();
}
/** /**
* Shared state across all peers, callers must sync on returned object * Shared state across all peers, callers must sync on returned object
* @return non-null * @return non-null

View File

@ -37,7 +37,8 @@ class PeerCheckerTask extends TimerTask
private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000); private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
private final PeerCoordinator coordinator; private final PeerCoordinator coordinator;
public I2PSnarkUtil _util; private final I2PSnarkUtil _util;
private int _runCount;
PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator) PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
{ {
@ -49,6 +50,7 @@ class PeerCheckerTask extends TimerTask
public void run() public void run()
{ {
_runCount++;
List<Peer> peerList = coordinator.peerList(); List<Peer> peerList = coordinator.peerList();
if (peerList.isEmpty() || coordinator.halted()) { if (peerList.isEmpty() || coordinator.halted()) {
coordinator.setRateHistory(0, 0); coordinator.setRateHistory(0, 0);
@ -204,6 +206,10 @@ class PeerCheckerTask extends TimerTask
} }
peer.retransmitRequests(); peer.retransmitRequests();
peer.keepAlive(); peer.keepAlive();
// announce them to local tracker (TrackerClient does this too)
if (_util.getDHT() != null && (_runCount % 5) == 0) {
_util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash());
}
} }
// Resync actual uploaders value // Resync actual uploaders value
@ -245,8 +251,13 @@ class PeerCheckerTask extends TimerTask
// close out unused files, but we don't need to do it every time // close out unused files, but we don't need to do it every time
Storage storage = coordinator.getStorage(); Storage storage = coordinator.getStorage();
if (storage != null && random.nextInt(4) == 0) { if (storage != null && (_runCount % 4) == 0) {
storage.cleanRAFs(); storage.cleanRAFs();
} }
// announce ourselves to local tracker (TrackerClient does this too)
if (_util.getDHT() != null && (_runCount % 16) == 0) {
_util.getDHT().announce(coordinator.getInfoHash());
}
} }
} }

View File

@ -36,6 +36,8 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2; import net.i2p.util.SimpleTimer2;
import org.klomp.snark.dht.KRPC;
/** /**
* Coordinates what peer does what. * Coordinates what peer does what.
*/ */
@ -1186,10 +1188,13 @@ public class PeerCoordinator implements PeerListener
/** /**
* PeerListener callback * PeerListener callback
* Tell the DHT to ping it, this will get back the node info
* @since 0.8.4 * @since 0.8.4
*/ */
public void gotPort(Peer peer, int port) { public void gotPort(Peer peer, int port) {
// send to DHT KRPC krpc = _util.getDHT();
if (krpc != null)
krpc.ping(peer.getDestination(), port);
} }
/** Return number of allowed uploaders for this torrent. /** Return number of allowed uploaders for this torrent.

View File

@ -776,7 +776,7 @@ public class Snark
} }
/** /**
* @return needed of all torrent files, or total of metainfo file if fetching magnet, or -1 * @return number of pieces still needed (magnet mode or not), or -1 if unknown
* @since 0.8.4 * @since 0.8.4
*/ */
public long getNeeded() { public long getNeeded() {
@ -786,7 +786,7 @@ public class Snark
// FIXME subtract chunks we have // FIXME subtract chunks we have
return meta.getTotalLength(); return meta.getTotalLength();
// FIXME fake // FIXME fake
return 16 * 16 * 1024; return -1;
} }
/** /**
@ -800,6 +800,17 @@ public class Snark
return 16*1024; return 16*1024;
} }
/**
* @return number of pieces
* @since 0.8.4
*/
public int getPieces() {
if (meta != null)
return meta.getPieces();
// FIXME else return metainfo pieces if available
return -1;
}
/** /**
* @return true if restarted * @return true if restarted
* @since 0.8.4 * @since 0.8.4

View File

@ -68,6 +68,7 @@ public class SnarkManager implements Snark.CompleteListener {
public static final String PROP_META_PREFIX = "i2psnark.zmeta."; public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
public static final String PROP_META_PRIORITY_SUFFIX = ".priority"; public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
public static final String PROP_META_MAGNET_SUFFIX = ".magnet";
private static final String CONFIG_FILE = "i2psnark.config"; private static final String CONFIG_FILE = "i2psnark.config";
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
@ -1031,6 +1032,9 @@ public class SnarkManager implements Snark.CompleteListener {
} }
} }
//start magnets
// here because we need to delay until I2CP is up // here because we need to delay until I2CP is up
// although the user will see the default until then // although the user will see the default until then
getBWLimit(); getBWLimit();
@ -1245,6 +1249,7 @@ public class SnarkManager implements Snark.CompleteListener {
if ( (snark != null) && (!snark.isStopped()) ) if ( (snark != null) && (!snark.isStopped()) )
snark.stopTorrent(); snark.stopTorrent();
} }
//save magnets
} }
} }
} }

View File

@ -100,7 +100,7 @@ public class Storage
getFiles(baseFile); getFiles(baseFile);
long total = 0; long total = 0;
ArrayList lengthsList = new ArrayList(); ArrayList<Long> lengthsList = new ArrayList();
for (int i = 0; i < lengths.length; i++) for (int i = 0; i < lengths.length; i++)
{ {
long length = lengths[i]; long length = lengths[i];
@ -122,10 +122,10 @@ public class Storage
bitfield = new BitField(pieces); bitfield = new BitField(pieces);
needed = 0; needed = 0;
List files = new ArrayList(); List<List<String>> files = new ArrayList();
for (int i = 0; i < names.length; i++) for (int i = 0; i < names.length; i++)
{ {
List file = new ArrayList(); List<String> file = new ArrayList();
StringTokenizer st = new StringTokenizer(names[i], File.separator); StringTokenizer st = new StringTokenizer(names[i], File.separator);
while (st.hasMoreTokens()) while (st.hasMoreTokens())
{ {

View File

@ -277,18 +277,26 @@ public class TrackerClient extends I2PAppThread
runStarted = true; runStarted = true;
tr.started = true; tr.started = true;
Set peers = info.getPeers(); Set<Peer> peers = info.getPeers();
tr.seenPeers = info.getPeerCount(); tr.seenPeers = info.getPeerCount();
if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
snark.setTrackerSeenPeers(tr.seenPeers); snark.setTrackerSeenPeers(tr.seenPeers);
// pass everybody over to our tracker
if (_util.getDHT() != null) {
for (Peer peer : peers) {
_util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
}
}
if ( (left > 0) && (!completed) ) { if ( (left > 0) && (!completed) ) {
// we only want to talk to new people if we need things // we only want to talk to new people if we need things
// from them (duh) // from them (duh)
List ordered = new ArrayList(peers); List<Peer> ordered = new ArrayList(peers);
Collections.shuffle(ordered, r); Collections.shuffle(ordered, r);
Iterator it = ordered.iterator(); Iterator<Peer> it = ordered.iterator();
while ((!stop) && it.hasNext()) { while ((!stop) && it.hasNext()) {
Peer cur = (Peer)it.next(); Peer cur = it.next();
// FIXME if id == us || dest == us continue; // FIXME if id == us || dest == us continue;
// only delay if we actually make an attempt to add peer // only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur)) { if(coordinator.addPeer(cur)) {

View File

@ -6,6 +6,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.text.Collator; import java.text.Collator;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -875,8 +876,7 @@ public class I2PSnarkServlet extends Default {
else if (isValid) else if (isValid)
icon = toIcon(meta.getName()); icon = toIcon(meta.getName());
else else
// todo get a nice magnet icon? icon = "magnet";
icon = "page_white";
if (remaining == 0 || isMultiFile) { if (remaining == 0 || isMultiFile) {
out.write(toImg(icon, _("Open"))); out.write(toImg(icon, _("Open")));
out.write("</a>"); out.write("</a>");
@ -1375,6 +1375,7 @@ public class I2PSnarkServlet extends Default {
if (ihash.length() == 32) { if (ihash.length() == 32) {
ih = Base32.decode(ihash); ih = Base32.decode(ihash);
} else if (ihash.length() == 40) { } else if (ihash.length() == 40) {
// Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
ih = new byte[20]; ih = new byte[20];
try { try {
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
@ -1539,6 +1540,7 @@ public class I2PSnarkServlet extends Default {
if (title.endsWith("/")) if (title.endsWith("/"))
title = title.substring(0, title.length() - 1); title = title.substring(0, title.length() - 1);
String directory = title;
title = _("Torrent") + ": " + title; title = _("Torrent") + ": " + title;
buf.append(title); buf.append(title);
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" + buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
@ -1550,10 +1552,40 @@ public class I2PSnarkServlet extends Default {
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete(); boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete();
if (showPriority) if (showPriority)
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n"); buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" >" + buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" ><thead>");
"<thead><tr><th>") if (snark != null) {
// first row - torrent info
// FIXME center
buf.append("<tr><th colspan=\"" + (showPriority ? '4' : '3') + "\"><div>")
.append(_("Torrent")).append(": ").append(snark.getBaseName());
int pieces = snark.getPieces();
double completion = (pieces - snark.getNeeded()) / (double) pieces;
if (completion < 1.0)
buf.append("<br>").append(_("Completion")).append(": ").append((new DecimalFormat("0.00%")).format(completion));
else
buf.append("<br>").append(_("Complete"));
// else unknown
buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength()));
MetaInfo meta = snark.getMetaInfo();
if (meta != null) {
List files = meta.getFiles();
int fileCount = files != null ? files.size() : 1;
buf.append("<br>").append(_("Files")).append(": ").append(fileCount);
}
buf.append("<br>").append(_("Pieces")).append(": ").append(pieces);
buf.append("<br>").append(_("Piece size")).append(": ").append(formatSize(snark.getPieceLength(0)));
String hex = toHex(snark.getInfoHash());
buf.append("<br>").append(_("Magnet link")).append(": <a href=\"").append(MAGNET).append(hex).append("\">")
.append(MAGNET).append(hex).append("</a>");
// We don't have the hash of the torrent file
//buf.append("<br>").append(_("Maggot link")).append(": <a href=\"").append(MAGGOT).append(hex).append(':').append(hex).append("\">")
// .append(MAGGOT).append(hex).append(':').append(hex).append("</a>");
buf.append("</div></th></tr>");
}
// second row - dir info
buf.append("<tr><th>")
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;") .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;")
.append(title).append("</th><th align=\"right\">") .append(_("Directory")).append(": ").append(directory).append("</th><th align=\"right\">")
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;") .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;")
.append(_("Size")); .append(_("Size"));
buf.append("</th><th class=\"headerstatus\">") buf.append("</th><th class=\"headerstatus\">")
@ -1767,6 +1799,21 @@ public class I2PSnarkServlet extends Default {
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\">"; return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\">";
} }
/**
* Like DataHelper.toHexString but ensures no loss of leading zero bytes
* @since 0.8.4
*/
private static String toHex(byte[] b) {
StringBuilder buf = new StringBuilder(40);
for (int i = 0; i < b.length; i++) {
int bi = b[i] & 0xff;
if (bi < 16)
buf.append('0');
buf.append(Integer.toHexString(bi));
}
return buf.toString();
}
/** @since 0.8.1 */ /** @since 0.8.1 */
private void savePriorities(Snark snark, Map postParams) { private void savePriorities(Snark snark, Map postParams) {
Storage storage = snark.getStorage(); Storage storage = snark.getStorage();