diff --git a/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java b/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java index e212f6f6139b2c42019cf7db0dc841faf4eb28c4..5b7b0b0382f1fcc38432eb536b3ba4c8e2b3b19d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java +++ b/apps/i2psnark/java/src/org/klomp/snark/ExtensionHandler.java @@ -21,7 +21,6 @@ import org.klomp.snark.bencode.InvalidBEncodingException; */ abstract class ExtensionHandler { - private static final byte[] _handshake = buildHandshake(); private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ExtensionHandler.class); public static final int ID_METADATA = 3; @@ -32,17 +31,15 @@ abstract class ExtensionHandler { /** + * @param metasize -1 if unknown * @return bencoded outgoing handshake message */ - public static byte[] getHandshake() { - return _handshake; - } - - /** outgoing handshake message */ - private static byte[] buildHandshake() { + public static byte[] getHandshake(int metasize) { Map<String, Object> handshake = new HashMap(); Map<String, Integer> m = new HashMap(); m.put(TYPE_METADATA, Integer.valueOf(ID_METADATA)); + if (metasize >= 0) + handshake.put("metadata_size", Integer.valueOf(metasize)); handshake.put("m", m); handshake.put("p", Integer.valueOf(6881)); handshake.put("v", "I2PSnark"); @@ -51,6 +48,8 @@ abstract class ExtensionHandler { } public static void handleMessage(Peer peer, PeerListener listener, int id, byte[] bs) { + if (_log.shouldLog(Log.INFO)) + _log.info("Got extension msg " + id + " length " + bs.length + " from " + peer); if (id == 0) handleHandshake(peer, listener, bs); else if (id == ID_METADATA) @@ -71,10 +70,24 @@ abstract class ExtensionHandler { peer.setHandshakeMap(map); Map<String, BEValue> msgmap = map.get("m").getMap(); - // rv not used, just to throw an NPE to get out of here - msgmap.get(TYPE_METADATA).getInt(); + if (msgmap.get(TYPE_METADATA) == null) { + if (_log.shouldLog(Log.WARN)) + _log.debug("Peer does not support metadata extension: " + peer); + // drop if we need metainfo ? + return; + } + + BEValue msize = map.get("metadata_size"); + if (msize == null) { + if (_log.shouldLog(Log.WARN)) + _log.debug("Peer does not have the metainfo size yet: " + peer); + // drop if we need metainfo ? + return; + } + int metaSize = msize.getInt(); + if (_log.shouldLog(Log.WARN)) + _log.debug("Got the metainfo size: " + metaSize); - int metaSize = map.get("metadata_size").getInt(); MagnetState state = peer.getMagnetState(); int remaining; synchronized(state) { @@ -83,12 +96,16 @@ abstract class ExtensionHandler { if (state.isInitialized()) { if (state.getSize() != metaSize) { + if (_log.shouldLog(Log.WARN)) + _log.debug("Wrong metainfo size " + metaSize + " from: " + peer); peer.disconnect(); return; } } else { // initialize it if (metaSize > MAX_METADATA_SIZE) { + if (_log.shouldLog(Log.WARN)) + _log.debug("Huge metainfo size " + metaSize + " from: " + peer); peer.disconnect(false); return; } @@ -112,8 +129,7 @@ abstract class ExtensionHandler { } } catch (Exception e) { if (_log.shouldLog(Log.WARN)) - _log.info("Handshake exception from " + peer, e); - //peer.disconnect(false); + _log.warn("Handshake exception from " + peer, e); } } @@ -140,6 +156,8 @@ abstract class ExtensionHandler { MagnetState state = peer.getMagnetState(); if (type == TYPE_REQUEST) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got request for " + piece + " from: " + peer); byte[] pc; synchronized(state) { pc = state.getChunk(piece); @@ -150,12 +168,19 @@ abstract class ExtensionHandler { listener.uploaded(peer, pc.length); } else if (type == TYPE_DATA) { int size = map.get("total_size").getInt(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got data for " + piece + " length " + size + " from: " + peer); boolean done; int chk = -1; synchronized(state) { if (state.isComplete()) return; int len = is.available(); + if (len != size) { + // probably fatal + if (_log.shouldLog(Log.WARN)) + _log.warn("total_size " + size + " but avail data " + len); + } peer.downloaded(len); listener.downloaded(peer, len); done = state.saveChunk(piece, bs, bs.length - len, len); @@ -219,15 +244,15 @@ abstract class ExtensionHandler { private static void sendPiece(Peer peer, int piece, byte[] data) { Map<String, Object> map = new HashMap(); - map.put("msg_type", Integer.valueOf(TYPE_REQUEST)); + map.put("msg_type", Integer.valueOf(TYPE_DATA)); map.put("piece", Integer.valueOf(piece)); map.put("total_size", Integer.valueOf(data.length)); byte[] dict = BEncoder.bencode(map); byte[] payload = new byte[dict.length + data.length]; System.arraycopy(dict, 0, payload, 0, dict.length); - System.arraycopy(data, 0, payload, dict.length, payload.length); + System.arraycopy(data, 0, payload, dict.length, data.length); try { - int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get("METADATA").getInt(); + int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get(TYPE_METADATA).getInt(); peer.sendExtension(hisMsgCode, payload); } catch (Exception e) { // NPE, no metadata caps diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index ef194ac3f4516437cf17a516eff75e28b23d5768..775d4cc4ab7e5b6d902ab9c25f6d48099ce02a83 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -446,6 +446,21 @@ public class I2PSnarkUtil { return Boolean.valueOf(rv).booleanValue(); } + /** + * Like DataHelper.toHexString but ensures no loss of leading zero bytes + * @since 0.8.4 + */ + public 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(); + } + /** hook between snark's logger and an i2p log */ void debug(String msg, int snarkDebugLevel) { debug(msg, snarkDebugLevel, null); diff --git a/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java b/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java index fb7e5a542da7cd8a4f861841083877bc6252d372..e9eb5638963a309ccf12c123341bd1abee19c5e1 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MagnetState.java @@ -53,8 +53,6 @@ class MagnetState { metainfo = meta; initialize(meta.getInfoBytes().length); complete = true; - } else { - metainfoBytes = new byte[metaSize]; } } @@ -68,10 +66,13 @@ class MagnetState { isInitialized = true; metaSize = size; totalChunks = (size + (CHUNK_SIZE - 1)) / CHUNK_SIZE; - if (metainfo == null) { + if (metainfo != null) { + metainfoBytes = metainfo.getInfoBytes(); + } else { // we don't need these if complete have = new BitField(totalChunks); requested = new BitField(totalChunks); + metainfoBytes = new byte[metaSize]; } } @@ -194,8 +195,7 @@ class MagnetState { InputStream is = new ByteArrayInputStream(metainfoBytes); BDecoder dec = new BDecoder(is); BEValue bev = dec.bdecodeMap(); - Map<String, BEValue> info = bev.getMap(); - map.put("info", info); + map.put("info", bev); MetaInfo newmeta = new MetaInfo(map); if (!DataHelper.eq(newmeta.getInfoHash(), infohash)) throw new IOException("info hash mismatch"); diff --git a/apps/i2psnark/java/src/org/klomp/snark/Message.java b/apps/i2psnark/java/src/org/klomp/snark/Message.java index 57fecf43d97b982b7302f3ee50ca5edab4018560..01c474216e4f086226fd6142dba7ebbc183f25ba 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Message.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Message.java @@ -104,7 +104,7 @@ class Message if (type == REQUEST || type == CANCEL) datalen += 4; - // length is 1 byte + // msg type is 1 byte if (type == EXTENSION) datalen += 1; @@ -167,6 +167,8 @@ class Message return "PIECE(" + piece + "," + begin + "," + length + ")"; case CANCEL: return "CANCEL(" + piece + "," + begin + "," + length + ")"; + case PORT: + return "PORT(" + piece + ")"; case EXTENSION: return "EXTENSION(" + piece + ',' + data.length + ')'; default: diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index de8d2b1d09161f066cd49f2fdfe06fe103dd8a8f..18c9ad229face87ee5d1cec7e1d32bf6f1c99a0a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -120,7 +120,7 @@ public class Peer implements Comparable this.peerID = new PeerID(id, sock.getPeerDestination()); _id = ++__id; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating " + _id)); + _log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id)); } /** @@ -261,14 +261,22 @@ public class Peer implements Comparable _log.debug("Already have din [" + sock + "] with " + toString()); } + // bad idea? + if (metainfo == null && (options & OPTION_EXTENSION) == 0) { + if (_log.shouldLog(Log.INFO)) + _log.info("Peer does not support extensions and we need metainfo, dropping"); + throw new IOException("Peer does not support extensions and we need metainfo, dropping"); + } + PeerConnectionIn in = new PeerConnectionIn(this, din); PeerConnectionOut out = new PeerConnectionOut(this, dout); PeerState s = new PeerState(this, listener, metainfo, in, out); if ((options & OPTION_EXTENSION) != 0) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Peer supports extensions, sending test message"); - out.sendExtension(0, ExtensionHandler.getHandshake()); + _log.debug("Peer supports extensions, sending reply message"); + int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1; + out.sendExtension(0, ExtensionHandler.getHandshake(metasize)); } if ((options & OPTION_DHT) != 0 && util.getDHT() != null) { @@ -423,6 +431,7 @@ public class Peer implements Comparable * @since 0.8.4 */ public void setMetaInfo(MetaInfo meta) { + metainfo = meta; PeerState s = state; if (s != null) s.setMetaInfo(meta); diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java index a27fc1c9dd17a91cfcc6698349b7e0186c9af3be..33da75263e9f20e69e3c9f2d3f932e9b9354b6c1 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java @@ -32,6 +32,13 @@ class PeerConnectionIn implements Runnable private final Peer peer; private final DataInputStream din; + // The max length of a complete message in bytes. + // The biggest is the piece message, for which the length is the + // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8 + // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother) + private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9, + MagnetState.CHUNK_SIZE + 100); // 100 for the ext msg dictionary + private Thread thread; private volatile boolean quit; @@ -77,13 +84,9 @@ class PeerConnectionIn implements Runnable int len; // Wait till we hear something... - // The length of a complete message in bytes. - // The biggest is the piece message, for which the length is the - // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8 - // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother) int i = din.readInt(); lastRcvd = System.currentTimeMillis(); - if (i < 0 || i > PeerState.PARTSIZE + 9) + if (i < 0 || i > MAX_MSG_SIZE) throw new IOException("Unexpected length prefix: " + i); if (i == 0) @@ -176,13 +179,14 @@ class PeerConnectionIn implements Runnable ps.portMessage(port); if (_log.shouldLog(Log.DEBUG)) _log.debug("Received port message from " + peer); + break; case 20: // Extension message int id = din.readUnsignedByte(); byte[] payload = new byte[i-2]; din.readFully(payload); - ps.extensionMessage(id, payload); if (_log.shouldLog(Log.DEBUG)) _log.debug("Received extension message from " + peer); + ps.extensionMessage(id, payload); break; default: byte[] bs = new byte[i-1]; diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 504749b6255d9aae9d5ae11fbce49aa903507ca2..74a840fc7b4e132a6e622908babfd48fe36f87c1 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -410,7 +410,7 @@ public class PeerCoordinator implements PeerListener name = "Magnet"; else name = metainfo.getName(); - _log.info("New connection to peer: " + peer + " for " + metainfo.getName()); + _log.info("New connection to peer: " + peer + " for " + name); } // Add it to the beginning of the list. @@ -1169,9 +1169,6 @@ public class PeerCoordinator implements PeerListener _log.warn("Got completed metainfo via extension"); metainfo = magnetState.getMetaInfo(); listener.gotMetaInfo(this, metainfo); - for (Peer p : peers) { - p.setMetaInfo(metainfo); - } } } } @@ -1184,6 +1181,11 @@ public class PeerCoordinator implements PeerListener */ public void setStorage(Storage stg) { storage = stg; + setWantedPieces(); + // ok we should be in business + for (Peer p : peers) { + p.setMetaInfo(metainfo); + } } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java index 4164e310a21936c38792407a8636e0f2c4fc4af3..6989f1ea65caa8a97d0ac0ad5ae613f53337edfe 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java @@ -511,6 +511,8 @@ class PeerState implements DataLoader //bitfield = new BitField(meta.getPieces()); } metainfo = meta; + if (bitfield.count() > 0) + setInteresting(true); } /** @since 0.8.4 */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 45231965fcc493396a939f03c1d685b21f604c60..d114fe0ee2ea01c8e23479470731ab679150029a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -243,7 +243,7 @@ public class Snark public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections"; /** most of these used to be public, use accessors below instead */ - private final String torrent; + private String torrent; private MetaInfo meta; private Storage storage; private PeerCoordinator coordinator; @@ -360,6 +360,7 @@ public class Snark } } meta = new MetaInfo(new BDecoder(in)); + infoHash = meta.getInfoHash(); } catch(IOException ioe) { @@ -1028,8 +1029,13 @@ public class Snark meta = metainfo; try { storage = new Storage(_util, meta, this); - if (completeListener != null) - completeListener.gotMetaInfo(this); + storage.check(rootDataDir); + if (completeListener != null) { + String newName = completeListener.gotMetaInfo(this); + if (newName != null) + torrent = newName; + // else some horrible problem + } coordinator.setStorage(storage); } catch (IOException ioe) { if (storage != null) { @@ -1125,9 +1131,10 @@ public class Snark * metainfo and storage. The listener should now call getMetaInfo() * and save the data to disk. * + * @return the new name for the torrent or null on error * @since 0.8.4 */ - public void gotMetaInfo(Snark snark); + public String gotMetaInfo(Snark snark); // not really listeners but the easiest way to get back to an optional SnarkManager public long getSavedTorrentTime(Snark snark); diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 27640d53baacf2442e310f79fb5cdcc06cbfe8e6..1e5ba10bf25d5b01655aeb306f33a455e5f417b2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -68,7 +68,7 @@ public class SnarkManager implements Snark.CompleteListener { public static final String PROP_META_PREFIX = "i2psnark.zmeta."; public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; public static final String PROP_META_PRIORITY_SUFFIX = ".priority"; - public static final String PROP_META_MAGNET_SUFFIX = ".magnet"; + public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet."; private static final String CONFIG_FILE = "i2psnark.config"; public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops @@ -340,7 +340,9 @@ public class SnarkManager implements Snark.CompleteListener { int oldI2CPPort = _util.getI2CPPort(); String oldI2CPHost = _util.getI2CPHost(); int port = oldI2CPPort; - try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {} + if (i2cpPort != null) { + try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {} + } String host = oldI2CPHost; Map opts = new HashMap(); if (i2cpOpts == null) i2cpOpts = ""; @@ -627,7 +629,7 @@ public class SnarkManager implements Snark.CompleteListener { * @throws RuntimeException via Snark.fatal() * @since 0.8.4 */ - public void addMagnet(String name, byte[] ih) { + public void addMagnet(String name, byte[] ih, boolean updateStatus) { Snark torrent = new Snark(_util, name, ih, this, _peerCoordinatorSet, _connectionAcceptor, false, getDataDir().getPath()); @@ -640,6 +642,8 @@ public class SnarkManager implements Snark.CompleteListener { } // Tell the dir monitor not to delete us _magnets.add(name); + if (updateStatus) + saveMagnetStatus(ih); _snarks.put(name, torrent); } if (shouldAutoStart()) { @@ -667,6 +671,7 @@ public class SnarkManager implements Snark.CompleteListener { } snark.stopTorrent(); _magnets.remove(snark.getName()); + removeMagnetStatus(snark.getInfoHash()); } /** @@ -914,6 +919,28 @@ public class SnarkManager implements Snark.CompleteListener { saveConfig(); } + /** + * Just remember we have it + * @since 0.8.4 + */ + public void saveMagnetStatus(byte[] ih) { + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + _config.setProperty(PROP_META_MAGNET_PREFIX + infohash, "."); + saveConfig(); + } + + /** + * Remove the magnet marker from the config file. + * @since 0.8.4 + */ + public void removeMagnetStatus(byte[] ih) { + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + _config.remove(PROP_META_MAGNET_PREFIX + infohash); + saveConfig(); + } + /** * Does not really delete on failure, that's the caller's responsibility. * Warning - does not validate announce URL - use TrackerClient.isValidAnnounce() @@ -1032,12 +1059,10 @@ public class SnarkManager implements Snark.CompleteListener { } } -//start magnets - - // here because we need to delay until I2CP is up // although the user will see the default until then getBWLimit(); + boolean doMagnets = true; while (true) { File dir = getDataDir(); if (_log.shouldLog(Log.DEBUG)) @@ -1050,6 +1075,10 @@ public class SnarkManager implements Snark.CompleteListener { } catch (Exception e) { _log.error("Error in the DirectoryMonitor", e); } + if (doMagnets) { + addMagnets(); + doMagnets = false; + } try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} } } @@ -1090,9 +1119,10 @@ public class SnarkManager implements Snark.CompleteListener { * and save the data to disk. * A Snark.CompleteListener method. * + * @return the new name for the torrent or null on error * @since 0.8.4 */ - public void gotMetaInfo(Snark snark) { + public String gotMetaInfo(Snark snark) { MetaInfo meta = snark.getMetaInfo(); Storage storage = snark.getStorage(); if (meta != null && storage != null) { @@ -1100,23 +1130,51 @@ public class SnarkManager implements Snark.CompleteListener { if (rejectMessage != null) { addMessage(rejectMessage); snark.stopTorrent(); - return; + return null; } saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities String name = (new File(getDataDir(), storage.getBaseName() + ".torrent")).getAbsolutePath(); try { synchronized (_snarks) { locked_writeMetaInfo(meta, name); + // put it in the list under the new name + _snarks.remove(snark.getName()); + _snarks.put(name, snark); } + _magnets.remove(snark.getName()); + removeMagnetStatus(snark.getInfoHash()); + addMessage(_("Metainfo received for {0}", snark.getName())); + addMessage(_("Starting up torrent {0}", storage.getBaseName())); + return name; } catch (IOException ioe) { addMessage(_("Failed to copy torrent file to {0}", name)); _log.error("Failed to write torrent file", ioe); } } + return null; } // End Snark.CompleteListeners + /** + * Add all magnets from the config file + * @since 0.8.4 + */ + private void addMagnets() { + for (Object o : _config.keySet()) { + String k = (String) o; + if (k.startsWith(PROP_META_MAGNET_PREFIX)) { + String b64 = k.substring(PROP_META_MAGNET_PREFIX.length()); + b64 = b64.replace('$', '='); + byte[] ih = Base64.decode(b64); + // ignore value + if (ih != null && ih.length == 20) + addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, false); + // else remove from config? + } + } + } + private void monitorTorrents(File dir) { String fileNames[] = dir.list(TorrentFilenameFilter.instance()); List<String> foundNames = new ArrayList(0); diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 719edee69bc2538c5ff5ae908cb26912b8acb1bd..d03753d6bd396290eeb65ded028ff3a89327424c 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -173,7 +173,8 @@ public class TrackerClient extends I2PAppThread continue; if (primary.startsWith("http://i2p/" + dest)) continue; - trackers.add(new Tracker(url, false)); + // opentrackers are primary if we don't have primary + trackers.add(new Tracker(url, primary.equals(""))); _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash); } } @@ -238,7 +239,7 @@ public class TrackerClient extends I2PAppThread uploaded = coordinator.getUploaded(); downloaded = coordinator.getDownloaded(); - left = coordinator.getLeft(); + left = coordinator.getLeft(); // -1 in magnet mode // First time we got a complete download? String event; @@ -289,7 +290,7 @@ public class TrackerClient extends I2PAppThread } } - if ( (left > 0) && (!completed) ) { + if ( (left != 0) && (!completed) ) { // we only want to talk to new people if we need things // from them (duh) List<Peer> ordered = new ArrayList(peers); @@ -344,7 +345,7 @@ public class TrackerClient extends I2PAppThread // FIXME this needs to be in its own thread if (_util.getDHT() != null && !stop) { int numwant; - if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) + if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) numwant = 1; else numwant = _util.getMaxConnections(); @@ -362,7 +363,7 @@ public class TrackerClient extends I2PAppThread List<Peer> peers = new ArrayList(hashes.size()); for (Hash h : hashes) { PeerID pID = new PeerID(h.getData()); - peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), meta)); + peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo())); } Collections.shuffle(peers, r); Iterator<Peer> it = peers.iterator(); @@ -370,7 +371,7 @@ public class TrackerClient extends I2PAppThread Peer cur = it.next(); if (coordinator.addPeer(cur)) { int delay = DELAY_MUL; - delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10; + delay *= r.nextInt(10); delay += DELAY_MIN; try { Thread.sleep(delay); } catch (InterruptedException ie) {} } @@ -418,6 +419,8 @@ public class TrackerClient extends I2PAppThread long downloaded, long left, String event) throws IOException { + // What do we send for left in magnet mode? Can we omit it? + long tleft = left >= 0 ? left : 1; String s = tr.announce + "?info_hash=" + infoHash + "&peer_id=" + peerID @@ -425,10 +428,10 @@ public class TrackerClient extends I2PAppThread + "&ip=" + _util.getOurIPString() + ".i2p" + "&uploaded=" + uploaded + "&downloaded=" + downloaded - + "&left=" + left + + "&left=" + tleft + "&compact=1" // NOTE: opentracker will return 400 for &compact alone + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : ""); - if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) + if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) s += "&numwant=0"; else s += "&numwant=" + _util.getMaxConnections(); @@ -445,7 +448,7 @@ public class TrackerClient extends I2PAppThread in = new FileInputStream(fetched); TrackerInfo info = new TrackerInfo(in, snark.getID(), - snark.getMetaInfo()); + snark.getInfoHash(), snark.getMetaInfo()); _util.debug("TrackerClient response: " + info, Snark.INFO); String failure = info.getFailureReason(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java index ef67fc58b1195fbbb03347ac92cf75c568f8b916..1b829d0eef00d674fdbbf5225381014c931beb8d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java @@ -46,19 +46,20 @@ public class TrackerInfo private int complete; private int incomplete; - public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo) + /** @param metainfo may be null */ + public TrackerInfo(InputStream in, byte[] my_id, byte[] infohash, MetaInfo metainfo) throws IOException { - this(new BDecoder(in), my_id, metainfo); + this(new BDecoder(in), my_id, infohash, metainfo); } - public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo) + private TrackerInfo(BDecoder be, byte[] my_id, byte[] infohash, MetaInfo metainfo) throws IOException { - this(be.bdecodeMap().getMap(), my_id, metainfo); + this(be.bdecodeMap().getMap(), my_id, infohash, metainfo); } - public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo) + private TrackerInfo(Map m, byte[] my_id, byte[] infohash, MetaInfo metainfo) throws IOException { BEValue reason = (BEValue)m.get("failure reason"); @@ -84,10 +85,10 @@ public class TrackerInfo Set<Peer> p; try { // One big string (the official compact format) - p = getPeers(bePeers.getBytes(), my_id, metainfo); + p = getPeers(bePeers.getBytes(), my_id, infohash, metainfo); } catch (InvalidBEncodingException ibe) { // List of Dictionaries or List of Strings - p = getPeers(bePeers.getList(), my_id, metainfo); + p = getPeers(bePeers.getList(), my_id, infohash, metainfo); } peers = p; } @@ -123,7 +124,7 @@ public class TrackerInfo ******/ /** List of Dictionaries or List of Strings */ - private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo) + private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, byte[] infohash, MetaInfo metainfo) throws IOException { Set<Peer> peers = new HashSet(l.size()); @@ -144,7 +145,7 @@ public class TrackerInfo continue; } } - peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo)); + peers.add(new Peer(peerID, my_id, infohash, metainfo)); } return peers; @@ -156,7 +157,7 @@ public class TrackerInfo * One big string of concatenated 32-byte hashes * @since 0.8.1 */ - private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo) + private static Set<Peer> getPeers(byte[] l, byte[] my_id, byte[] infohash, MetaInfo metainfo) throws IOException { int count = l.length / HASH_LENGTH; @@ -172,7 +173,7 @@ public class TrackerInfo // won't happen continue; } - peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo)); + peers.add(new Peer(peerID, my_id, infohash, metainfo)); } return peers; diff --git a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java index 986e456437a7e57770e442d207f12b3377019c13..75a63bab19892c80d3b8afcc46e2f237d76c05a0 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java +++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java @@ -24,6 +24,8 @@ import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Map; +import net.i2p.data.Base64; + /** * Holds different types that a bencoded byte array can represent. * You need to call the correct get method to get the correct java @@ -180,10 +182,14 @@ public class BEValue { byte[] bs = (byte[])value; // XXX - Stupid heuristic... and not UTF-8 - if (bs.length <= 12) - valueString = new String(bs); + //if (bs.length <= 12) + // valueString = new String(bs); + //else + // valueString = "bytes:" + bs.length; + if (bs.length <= 32) + valueString = bs.length + " bytes: " + Base64.encode(bs); else - valueString = "bytes:" + bs.length; + valueString = bs.length + " bytes"; } else valueString = value.toString(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index bd08dc46d3a853a572dbefa709e379ba28c090ac..077b24351f25e6857de82d5d8bc6ff6fb36a6621 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -1314,15 +1314,17 @@ public class I2PSnarkServlet extends Default { out.write(" "); out.write(renderOptions(0, 4, options.remove("outbound.length"), "outbound.length", HOP)); - out.write("<tr><td>"); - out.write(_("I2CP host")); - out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\"" - + _manager.util().getI2CPHost() + "\" size=\"15\" > "); - - out.write("<tr><td>"); - out.write(_("I2CP port")); - out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" + - + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n"); + if (!_context.isRouterContext()) { + out.write("<tr><td>"); + out.write(_("I2CP host")); + out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\"" + + _manager.util().getI2CPHost() + "\" size=\"15\" > "); + + out.write("<tr><td>"); + out.write(_("I2CP port")); + out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" + + + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n"); + } StringBuilder opts = new StringBuilder(64); for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {