diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index b0e26bfe6acb2bb6a361e60b45224d9987031518..bbdc09ab4267bf285d6972df69d2e35c4e1b167d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -197,11 +197,14 @@ public class I2PSnarkUtil { /** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { - Hash dest = peer.getAddress().calculateHash(); + Destination addr = peer.getAddress(); + if (addr == null) + throw new IOException("Null address"); + Hash dest = addr.calculateHash(); if (_shitlist.contains(dest)) throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted"); try { - I2PSocket rv = _manager.connect(peer.getAddress()); + I2PSocket rv = _manager.connect(addr); if (rv != null) _shitlist.remove(dest); return rv; diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index e0ae6e1f25449a2b47adf884629ace0d10f5aa66..4e965a072144eabc66befeb178d6c19284ec9c45 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -129,7 +129,7 @@ public class Peer implements Comparable @Override public int hashCode() { - return peerID.hashCode() ^ (2 << _id); + return peerID.hashCode() ^ (7777 * (int)_id); } /** @@ -150,6 +150,7 @@ public class Peer implements Comparable /** * Compares the PeerIDs. + * @deprecated unused? */ public int compareTo(Object o) { @@ -207,12 +208,15 @@ public class Peer implements Comparable // = new BufferedOutputStream(sock.getOutputStream()); byte [] id = handshake(in, out); //handshake(bis, bos); byte [] expected_id = peerID.getID(); - if (!Arrays.equals(expected_id, id)) - throw new IOException("Unexpected peerID '" + if (expected_id == null) + peerID.setID(id); + else if (Arrays.equals(expected_id, id)) + _log.debug("Handshake got matching IDs with " + toString()); + else + throw new IOException("Unexpected peerID '" + PeerID.idencode(id) + "' expected '" + PeerID.idencode(expected_id) + "'"); - _log.debug("Handshake got matching IDs with " + toString()); } else { _log.debug("Already have din [" + sock + "] with " + toString()); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 2a2e40e7d5f46b14d3b12418dc77c859edb842fb..5e40f662edd9f979daebbac7fafb5f928da7582f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -373,7 +373,8 @@ public class PeerCoordinator implements PeerListener if (need_more) { - _log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run")); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run")); // Run the peer with us as listener and the current bitfield. final PeerListener listener = this; diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java index c4a4e9194164dc91b70bdc22c0eada56e9e1f886..37cf1a9b65ccbadad69299d2a783373ce665fc48 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java @@ -24,19 +24,33 @@ import java.io.IOException; import java.net.UnknownHostException; import java.util.Map; +import net.i2p.I2PAppContext; +import net.i2p.data.Base32; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.data.Destination; import org.klomp.snark.bencode.BDecoder; import org.klomp.snark.bencode.BEValue; import org.klomp.snark.bencode.InvalidBEncodingException; +/** + * Store the address information about a peer. + * Prior to 0.8.1, an instantiation required a peer ID, and full Destination address. + * Starting with 0.8.1, to support compact tracker responses, + * a PeerID can be instantiated with a Destination Hash alone. + * The full destination lookup is deferred until getAddress() is called, + * and the PeerID is not required. + * Equality is now determined solely by the dest hash. + */ public class PeerID implements Comparable { - private final byte[] id; - private final Destination address; + private byte[] id; + private Destination address; private final int port; - + private byte[] destHash; + /** whether we have tried to get the dest from the hash - only do once */ + private boolean triedDestLookup; private final int hash; public PeerID(byte[] id, Destination address) @@ -44,7 +58,7 @@ public class PeerID implements Comparable this.id = id; this.address = address; this.port = 6881; - + this.destHash = address.calculateHash().getData(); hash = calculateHash(); } @@ -77,17 +91,49 @@ public class PeerID implements Comparable throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]"); port = 6881; - + this.destHash = address.calculateHash().getData(); hash = calculateHash(); } + /** + * Creates a PeerID from a destHash + * @since 0.8.1 + */ + public PeerID(byte[] dest_hash) throws InvalidBEncodingException + { + // id and address remain null + port = 6881; + if (dest_hash.length != 32) + throw new InvalidBEncodingException("bad hash length"); + destHash = dest_hash; + hash = DataHelper.hashCode(dest_hash); + } + public byte[] getID() { return id; } - public Destination getAddress() + /** for connecting out to peer based on desthash @since 0.8.1 */ + public void setID(byte[] xid) { + id = xid; + } + + /** + * Get the destination. + * If this PeerId was instantiated with a destHash, + * and we have not yet done so, lookup the full destination, which may take + * up to 10 seconds. + * @return Dest or null if unknown + */ + public synchronized Destination getAddress() + { + if (address == null && destHash != null && !triedDestLookup) { + String b32 = Base32.encode(destHash) + ".b32.i2p"; + address = I2PAppContext.getGlobalContext().namingService().lookup(b32); + triedDestLookup = true; + } return address; } @@ -96,16 +142,19 @@ public class PeerID implements Comparable return port; } + /** @since 0.8.1 */ + public byte[] getDestHash() + { + return destHash; + } + private int calculateHash() { - int b = 0; - for (int i = 0; i < id.length; i++) - b ^= id[i]; - return (b ^ address.hashCode()) ^ port; + return DataHelper.hashCode(destHash); } /** - * The hash code of a PeerID is the exclusive or of all id bytes. + * The hash code of a PeerID is the hashcode of the desthash */ @Override public int hashCode() @@ -115,18 +164,15 @@ public class PeerID implements Comparable /** * Returns true if and only if this peerID and the given peerID have - * the same 20 bytes as ID. + * the same destination hash */ public boolean sameID(PeerID pid) { - boolean equal = true; - for (int i = 0; equal && i < id.length; i++) - equal = id[i] == pid.id[i]; - return equal; + return DataHelper.eq(destHash, pid.getDestHash()); } /** - * Two PeerIDs are equal when they have the same id, address and port. + * Two PeerIDs are equal when they have the same dest hash */ @Override public boolean equals(Object o) @@ -135,9 +181,7 @@ public class PeerID implements Comparable { PeerID pid = (PeerID)o; - return port == pid.port - && address.equals(pid.address) - && sameID(pid); + return sameID(pid); } else return false; @@ -145,6 +189,7 @@ public class PeerID implements Comparable /** * Compares port, address and id. + * @deprecated unused? and will NPE now that address can be null? */ public int compareTo(Object o) { @@ -176,6 +221,8 @@ public class PeerID implements Comparable @Override public String toString() { + if (id == null || address == null) + return "unkn@" + Base64.encode(destHash).substring(0, 6); int nonZero = 0; for (int i = 0; i < id.length; i++) { if (id[i] != 0) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 065e7088014a7a1b19c489e75ab31fe7ee230528..05df351c69b7c28e93deff8e783e1aa0a537cf21 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -357,6 +357,7 @@ public class TrackerClient extends I2PAppThread + "&uploaded=" + uploaded + "&downloaded=" + downloaded + "&left=" + left + + "&compact" + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : ""); if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) s += "&numwant=0"; diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java index 84198f12f0f0eacca955c53cf14a018c91202f1e..ee2c2da25f46e9c5f95e34fec8a9cd8d7dbc44b2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java @@ -33,11 +33,17 @@ import org.klomp.snark.bencode.BDecoder; import org.klomp.snark.bencode.BEValue; import org.klomp.snark.bencode.InvalidBEncodingException; +/** + * The data structure for the tracker response. + * Handles both traditional and compact formats. + * Compact format 1 - a list of hashes - implemented + * Compact format 2 - One big string of concatenated hashes - unimplemented + */ public class TrackerInfo { private final String failure_reason; private final int interval; - private final Set peers; + private final Set<Peer> peers; private int complete; private int incomplete; @@ -76,6 +82,7 @@ public class TrackerInfo if (bePeers == null) peers = Collections.EMPTY_SET; else + // if compact is going to be one big string instead, try/catch here peers = getPeers(bePeers.getList(), my_id, metainfo); BEValue bev = (BEValue)m.get("complete"); @@ -94,33 +101,39 @@ public class TrackerInfo } } - public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo) +/****** + public static Set<Peer> getPeers(InputStream in, byte[] my_id, MetaInfo metainfo) throws IOException { return getPeers(new BDecoder(in), my_id, metainfo); } - public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo) + public static Set<Peer> getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo) throws IOException { return getPeers(be.bdecodeList().getList(), my_id, metainfo); } +******/ - public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo) + private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo) throws IOException { - Set peers = new HashSet(l.size()); + Set<Peer> peers = new HashSet(l.size()); - Iterator it = l.iterator(); - while (it.hasNext()) - { + for (BEValue bev : l) { PeerID peerID; try { - peerID = new PeerID(((BEValue)it.next()).getMap()); + // Case 1 - non-compact - A list of dictionaries (maps) + peerID = new PeerID(bev.getMap()); } catch (InvalidBEncodingException ibe) { - // don't let one bad entry spoil the whole list - //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR); - continue; + try { + // Case 2 - compact - A list of 32-byte binary strings (hashes) + peerID = new PeerID(bev.getBytes()); + } catch (InvalidBEncodingException ibe2) { + // don't let one bad entry spoil the whole list + //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR); + continue; + } } peers.add(new Peer(peerID, my_id, metainfo)); } @@ -128,7 +141,7 @@ public class TrackerInfo return peers; } - public Set getPeers() + public Set<Peer> getPeers() { 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 cba6b973fd2d70f846e32dd690228cdcb6996e03..c1733cb13b2000ddcc62e0fa29cb82a5fcd6bd60 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java +++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java @@ -140,7 +140,7 @@ public class BEValue * succeeds when the BEValue is actually a List, otherwise it will * throw a InvalidBEncodingException. */ - public List getList() throws InvalidBEncodingException + public List<BEValue> getList() throws InvalidBEncodingException { try { @@ -157,7 +157,7 @@ public class BEValue * values. This operation only succeeds when the BEValue is actually * a Map, otherwise it will throw a InvalidBEncodingException. */ - public Map getMap() throws InvalidBEncodingException + public Map<BEValue, BEValue> getMap() throws InvalidBEncodingException { try {