Author: toad
Date: 2007-06-27 13:23:27 +0000 (Wed, 27 Jun 2007)
New Revision: 13778

Added:
   trunk/freenet/src/freenet/node/FailureTable.java
   trunk/freenet/src/freenet/node/FailureTableEntry.java
Modified:
   trunk/freenet/src/freenet/node/NodeClientCore.java
   trunk/freenet/src/freenet/node/PeerNode.java
   trunk/freenet/src/freenet/support/LRUHashtable.java
Log:
Most of ULPR support.
Will have to be finished later or by somebody else.
I will file bugs for the remaining functionality.

Added: trunk/freenet/src/freenet/node/FailureTable.java
===================================================================
--- trunk/freenet/src/freenet/node/FailureTable.java                            
(rev 0)
+++ trunk/freenet/src/freenet/node/FailureTable.java    2007-06-27 13:23:27 UTC 
(rev 13778)
@@ -0,0 +1,263 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node;
+
+import java.lang.ref.WeakReference;
+
+import freenet.keys.Key;
+import freenet.keys.KeyBlock;
+import freenet.keys.NodeCHK;
+import freenet.support.LRUHashtable;
+
+/**
+ * Tracks recently DNFed keys, where they were routed to, what the location 
was at the time, who requested them.
+ * Implements Ultra-Lightweight Persistent Requests: Refuse requests for a key 
for 10 minutes after it's DNFed 
+ * (UNLESS we find a better route for the request), and when it is found, 
offer it to those who've asked for it
+ * in the last hour.
+ * @author toad
+ */
+public class FailureTable {
+       
+       /** FailureTableEntry's by key. Note that we push an entry only when 
sentTime changes. */
+       private final LRUHashtable entriesByKey;
+       /** BlockOfferList by key */
+       private final LRUHashtable blockOfferListByKey;
+       /** Peers */
+       private final PeerManager peers;
+       private final Node node;
+       
+       /** Maximum number of keys to track */
+       static final int MAX_ENTRIES = 20*1000;
+       /** Maximum number of offers to track */
+       static final int MAX_OFFERS = 10*1000;
+       /** Terminate a request if there was a DNF on the same key less than 10 
minutes ago */
+       static final int REJECT_TIME = 10*60*1000;
+       /** After 1 hour we forget about an entry completely */
+       static final int MAX_LIFETIME = 60*60*1000;
+       /** Offers expire after 10 minutes */
+       static final int OFFER_EXPIRY_TIME = 10*60*1000;
+       
+       FailureTable(PeerManager peers, Node node) {
+               entriesByKey = new LRUHashtable();
+               blockOfferListByKey = new LRUHashtable();
+               this.peers = peers;
+               this.node = node;
+       }
+       
+       /**
+        * Called when a key DNFs, or is killed by a RecentlyFailed message. 
Either way this can create a 
+        * FailureTableEntry.
+        * @param key The key that was fetched.
+        * @param htl The HTL it was fetched at.
+        * @param requestors The nodes requesting it (if any).
+        * @param requested The single node it was forwarded to, which DNFed.
+        * @param now The time at which the request was sent.
+        * @param timeout The number of millis from when the request was sent 
to when the failure block times out.
+        * I.e. between 0 and REJECT_TIME.
+        */
+       public void onFailure(Key key, short htl, PeerNode[] requestors, 
PeerNode requested, int timeout, long now) {
+               FailureTableEntry entry;
+               synchronized(this) {
+                       entry = (FailureTableEntry) entriesByKey.get(key);
+                       if(entry == null) {
+                               entry = new FailureTableEntry(key, htl, 
requestors, requested);
+                               entriesByKey.push(key, entry);
+                               return;
+                       } else {
+                               entriesByKey.push(key, entry);
+                       }
+                       trimEntries(now);
+               }
+               entry.onFailure(htl, requestors, requested, timeout, now);
+       }
+       
+       private void trimEntries(long now) {
+               while(entriesByKey.size() > MAX_ENTRIES) {
+                       entriesByKey.popKey();
+               }
+               while(true) {
+                       FailureTableEntry e = (FailureTableEntry) 
entriesByKey.peekValue();
+                       if(now - e.creationTime > MAX_LIFETIME) 
entriesByKey.popKey();
+                       else break;
+               }
+       }
+
+       /**
+        * Called when a request is made. Determine whether we should fail the 
request, and add the requestors to the list
+        * of interested nodes.
+        * @param key The key to fetch.
+        * @param htl The HTL it will be fetched at.
+        * @param requestor The node requesting it.
+        * @return True if the request should be failed with an 
FNPRecentlyFailed.
+        */
+       public synchronized boolean shouldFail(Key key, short htl, PeerNode 
requestor) {
+               long now = System.currentTimeMillis();
+               FailureTableEntry entry = (FailureTableEntry) 
entriesByKey.get(key);
+               if(entry == null) {
+                       // Don't know anything about the key
+                       return false;
+               }
+               entry.addRequestors(new PeerNode[] { requestor }, now);
+               if(htl > entry.htl) {
+                       // If the HTL is higher this time, let it through
+                       entriesByKey.push(key, entry);
+                       return false;
+               }
+               if(now > entry.timeoutTime) {
+                       // If it's more than 10 minutes since we sent a 
request, let it through
+                       return false;
+               }
+               /*
+                * If the best node available now is closer than the best 
location we have routed to so far, out of those
+                * nodes which are still connected, then accept the request.
+                * 
+                * Note that this means we can route to the same node twice - 
but only if its location improves.
+                */
+               double bestLiveLoc = entry.bestLiveLocDiff();
+               
+               PeerNode p = peers.closerPeer(requestor, null, null, 
key.toNormalizedDouble(), true, false, 0, null, 
PeerManager.distance(bestLiveLoc, key.toNormalizedDouble()));
+               
+               if(p != null) return false; // there is a better route now / we 
want to retry an old one
+               
+               return true; // kill the request
+       }
+       
+       private final class BlockOfferList {
+               private BlockOffer[] offers;
+               final FailureTableEntry entry;
+               
+               BlockOfferList(FailureTableEntry entry, BlockOffer offer) {
+                       this.entry = entry;
+                       this.offers = new BlockOffer[] { offer };
+               }
+
+               public long expires() {
+                       long last = 0;
+                       for(int i=0;i<offers.length;i++) {
+                               if(offers[i].offeredTime > last) last = 
offers[i].offeredTime;
+                       }
+                       return last;
+               }
+
+               public boolean isEmpty(long now) {
+                       for(int i=0;i<offers.length;i++) {
+                               if(offers[i].offeredTime > now) return false;
+                       }
+                       return true;
+               }
+       }
+       
+       private final class BlockOffer {
+               final long offeredTime;
+               /** Either offered by or offered to this node */
+               final WeakReference nodeRef;
+               
+               BlockOffer(PeerNode pn, long now) {
+                       this.nodeRef = pn.myRef;
+                       this.offeredTime = now;
+               }
+       }
+       
+       /**
+        * Called when a data block is found (after it has been stored; there 
is a good chance of its being available in the
+        * near future). If there are nodes waiting for it, we will offer it to 
them.
+        */
+       public void onFound(KeyBlock block) {
+               Key key = block.getKey();
+               FailureTableEntry entry;
+               synchronized(this) {
+                       entry = (FailureTableEntry) entriesByKey.get(key);
+                       if(entry == null) return; // Nobody cares
+                       entriesByKey.removeKey(key);
+               }
+               entry.offer();
+       }
+       
+       /**
+        * Called when we get an offer for a key. If this is an SSK, we will 
only accept it if we have previously asked for it.
+        * If it is a CHK, we will accept it if we want it.
+        * @param key The key we are being offered.
+        * @param peer The node offering it.
+        */
+       public void onOffer(Key key, PeerNode peer) {
+               if(wantOffer(key, peer)) {
+                       // Okay, we want the offer. Now what?
+                       // Two ClientRequestScheduler's? Then you'd have to 
remove the key from two different RGA's :(
+                       // Anyway, we don't want a key to be requested just 
because another key in the same group has an offer.
+                       // So what we want is a list of keys at each level 
which have been offered.
+                       // These would be considered before the other keys at 
that level, but only if the offer is still valid.
+               }
+       }
+       
+       boolean wantOffer(Key key, PeerNode peer) {
+               FailureTableEntry entry;
+               long now = System.currentTimeMillis();
+               synchronized(this) {
+                       entry = (FailureTableEntry) entriesByKey.get(key);
+                       if(entry == null) return false; // we haven't asked for 
it
+                       
+                       /*
+                        * Accept (subject to later checks) if we asked for it.
+                        * Should we accept it if we were asked for it? This is 
"bidirectional propagation".
+                        * It's good because it makes the whole structure much 
more reliable; it's bad because
+                        * it's not entirely under our control - we didn't 
choose to route it to the node, the node
+                        * routed it to us. Now it's found it before we did...
+                        * 
+                        * Attacks:
+                        * - Frost spamming etc: Is it easier to offer data to 
our peers rather than inserting it? Will
+                        * it result in it being propagated further? Probably 
not. Propagation to nodes that have asked is
+                        * worthwhile in general partly because reduced polling 
cost enables more secure messaging systems
+                        * e.g. outbox polling... Not relevant with CHKs.
+                        * - Social engineering: If a key is unpopular, you can 
put a different copy of it on different 
+                        * nodes. You can then use this to trace the requestor 
- identify that he is or isn't on the target. 
+                        * You can't do this with a regular insert because it 
will often go several nodes even at htl 0. 
+                        * With subscriptions, you might be able to bypass this 
- but only if you know no other nodes in the
+                        * neighbourhood are subscribed. Easier with SSKs; with 
CHKs you have only binary information of 
+                        * whether the person got the key (with social 
engineering). Hard to exploit on darknet; if you're 
+                        * that close to the suspect there are easier ways to 
get at them e.g. correlation attacks.
+                        * 
+                        * Conclusion: We should accept the request if:
+                        * - We asked for it from that node. (Note that a node 
might both have asked us and been asked).
+                        * - That node asked for it, and it's a CHK.
+                        */
+                       
+                       if(!(entry.askedFromPeer(peer, now) || 
+                                       ((key instanceof NodeCHK) && 
entry.askedByPeer(peer, now)))) {
+                               if(entry.isEmpty(now)) 
entriesByKey.removeKey(key);
+                               return false;
+                       }
+                       if(entry.isEmpty(now)) entriesByKey.removeKey(key);
+                       
+                       // Valid offer.
+                       
+                       // Add to offers list
+                       
+                       BlockOfferList bl = (BlockOfferList) 
blockOfferListByKey.get(key);
+                       BlockOffer offer = new BlockOffer(peer, now);
+                       if(bl == null) {
+                               bl = new BlockOfferList(entry, offer);
+                       }
+                       blockOfferListByKey.push(key, offer);
+                       trimOffersList(now);
+               }
+               
+               // Now, does anyone want it?
+               // Firstly, do we want it?
+               if(!node.clientCore.clientWantKey(key)) return true;
+               if(entry.othersWant(peer)) return true;
+               return false;
+       }
+
+       private synchronized void trimOffersList(long now) {
+               while(true) {
+                       if(blockOfferListByKey.isEmpty()) return;
+                       BlockOfferList bl = (BlockOfferList) 
blockOfferListByKey.peekValue();
+                       if(bl.isEmpty(now) || bl.expires() < now || 
blockOfferListByKey.size() > MAX_OFFERS) {
+                               blockOfferListByKey.popKey();
+                       } else {
+                               return;
+                       }
+               }
+       }
+}

Added: trunk/freenet/src/freenet/node/FailureTableEntry.java
===================================================================
--- trunk/freenet/src/freenet/node/FailureTableEntry.java                       
        (rev 0)
+++ trunk/freenet/src/freenet/node/FailureTableEntry.java       2007-06-27 
13:23:27 UTC (rev 13778)
@@ -0,0 +1,421 @@
+/**
+ * 
+ */
+package freenet.node;
+
+import java.lang.ref.WeakReference;
+
+import freenet.keys.Key;
+
+class FailureTableEntry {
+       
+       /** The key */
+       Key key; // FIXME should this be stored compressed somehow e.g. just 
the routing key?
+       /** The HTL at which it was requested last time. Any request of higher 
HTL will be let through. */
+       short htl;
+       /** Time of creation of this entry */
+       long creationTime;
+       /** Time we last received a request for the key */
+       long receivedTime;
+       /** Time we last received a DNF after sending a request for a key */
+       long sentTime;
+       /** Time at which we can send a request again */
+       long timeoutTime;
+       /** WeakReference's to PeerNode's who have requested the key */
+       WeakReference[] requestorNodes;
+       /** Times at which they requested it */
+       long[] requestorTimes;
+       /** Boot ID when they requested it. We don't send it to restarted 
nodes, as a 
+        * (weak, but useful if combined with other measures) protection 
against seizure. */
+       long[] requestorBootIDs;
+       /** WeakReference's to PeerNode's we have requested it from */
+       WeakReference[] requestedNodes;
+       /** Their locations when we requested it */
+       double[] requestedLocs;
+       long[] requestedBootIDs;
+       long[] requestedTimes;
+       
+       /** We remember that a node has asked us for a key for up to an hour; 
after that, we won't offer the key, and
+        * if we receive an offer from that node, we will reject it */
+       static final int MAX_TIME_BETWEEN_REQUEST_AND_OFFER = 60 * 60 * 1000;
+       
+       FailureTableEntry(Key key2, short htl2, PeerNode[] requestors, PeerNode 
requested) {
+               long now = System.currentTimeMillis();
+               this.key = key2;
+               this.htl = htl2;
+               creationTime = now;
+               receivedTime = now;
+               sentTime = now;
+               requestorNodes = new WeakReference[requestors.length];
+               requestorTimes = new long[requestors.length];
+               requestorBootIDs = new long[requestors.length];
+               for(int i=0;i<requestorNodes.length;i++) {
+                       requestorNodes[i] = new WeakReference(requestors[i]);
+                       requestorTimes[i] = now;
+                       requestorBootIDs[i] = requestors[i].getBootID();
+               }
+               requestedNodes = new WeakReference[] { new 
WeakReference(requested) };
+               requestedLocs = new double[] { 
requested.getLocation().getValue() };
+               requestedBootIDs = new long[] { requested.getBootID() };
+       }
+       
+       /**
+        * Called when there is a failure which could cause a block to be 
added: Either a DataNotFound, or a
+        * RecentlyFailed.
+        * @param htl2
+        * @param requestors
+        * @param requested
+        */
+       public void onFailure(short htl2, PeerNode[] requestors, PeerNode 
requested, int timeout, long now) {
+               synchronized(this) {
+                       long newTimeoutTime = now + timeout;
+                       if(now > timeoutTime /* has expired */ || 
newTimeoutTime > timeoutTime) {
+                               htl = htl2;
+                               timeoutTime = newTimeoutTime;
+                       }
+                       addRequestors(requestors, now);
+                       addRequestedFrom(new PeerNode[] { requested }, now);
+               }
+       }
+
+       // These are rather low level, in an attempt to absolutely minimize 
memory usage...
+       // The two methods have almost identical code/logic.
+       // Dunno if there's a more elegant way of dealing with this which 
doesn't significantly increase
+       // per entry byte cost.
+       // Note also this will generate some churn...
+       
+       synchronized void addRequestors(PeerNode[] requestors, long now) {
+               receivedTime = now;
+               int notIncluded = 0;
+               int nulls = 0;
+               int ptr = 0;
+               for(int i=0;i<requestors.length;i++) {
+                       PeerNode req = requestors[i];
+                       boolean requestorIncluded = false;
+                       for(int j=0;j<requestorNodes.length;j++) {
+                               PeerNode got = requestorNodes[i] == null ? null 
: (PeerNode) requestorNodes[i].get();
+                               // No longer subscribed if they have rebooted, 
or expired
+                               if(got.getBootID() != requestorBootIDs[i] ||
+                                               now - requestorTimes[i] > 
MAX_TIME_BETWEEN_REQUEST_AND_OFFER) {
+                                       requestorNodes[i] = null;
+                                       got = null;
+                               }
+                               if(got == null)
+                                       nulls++;
+                               if(got == req) {
+                                       // Update existing entry
+                                       requestorIncluded = true;
+                                       requestorTimes[j] = now;
+                                       requestorBootIDs[j] = req.getBootID();
+                                       break;
+                               }
+                       }
+                       if(!requestorIncluded) {
+                               notIncluded++;
+                               requestors[ptr++] = requestors[i];
+                       } // if it's new, keep it in requestors
+               }
+               for(int i=ptr;i<requestors.length;i++) requestors[i] = null;
+               if(notIncluded == 0 && nulls == 0) return;
+               // Because weak, these can become null; doesn't matter, but we 
want to minimise memory usage
+               if(notIncluded == nulls && notIncluded > 0) {
+                       // Nice special case
+                       int x = 0;
+                       for(int i=0;i<requestorNodes.length;i++) {
+                               if(requestorNodes[i].get() == null) {
+                                       PeerNode pn = requestors[x++];
+                                       requestorNodes[i] = pn.myRef;
+                                       requestorTimes[i] = now;
+                                       requestorBootIDs[i] = pn.getBootID();
+                                       if(x == ptr) break;
+                               }
+                       }
+                       return;
+               }
+               WeakReference[] newRequestorNodes = new 
WeakReference[requestorNodes.length+notIncluded-nulls];
+               long[] newRequestorTimes = new 
long[requestorNodes.length+notIncluded-nulls];
+               long[] newRequestorBootIDs = new 
long[requestorNodes.length+notIncluded-nulls];
+               int fromIndex = 0;
+               int toIndex = 0;
+               for(int i=0;i<requestorNodes.length;i++) {
+                       WeakReference ref = requestorNodes[i];
+                       if(ref == null || ref.get() == null) {
+                               while(fromIndex < ptr) {
+                                       PeerNode pn = requestors[fromIndex];
+                                       if(pn != null) {
+                                               newRequestorNodes[toIndex] = 
pn.myRef;
+                                               newRequestorTimes[toIndex] = 
now;
+                                               newRequestorBootIDs[toIndex] = 
pn.getBootID();
+                                               toIndex++;
+                                               break;
+                                       }
+                                       fromIndex++;
+                               }
+                       } else {
+                               newRequestorNodes[toIndex] = requestorNodes[i];
+                               newRequestorTimes[toIndex] = requestorTimes[i];
+                               newRequestorBootIDs[toIndex] = 
requestorBootIDs[i];
+                               toIndex++;
+                       }
+               }
+               for(;fromIndex<ptr;fromIndex++) {
+                       PeerNode pn = requestors[fromIndex];
+                       if(pn != null) {
+                               newRequestorNodes[toIndex] = pn.myRef;
+                               newRequestorTimes[toIndex] = now;
+                               newRequestorBootIDs[toIndex] = pn.getBootID();
+                               toIndex++;
+                       }
+               }
+               for(int i=toIndex;i<newRequestorNodes.length;i++) 
newRequestorNodes[i] = null;
+               if(toIndex > newRequestorNodes.length + 2) {
+                       WeakReference[] newNewRequestorNodes = new 
WeakReference[toIndex];
+                       long[] newNewRequestorTimes = new long[toIndex];
+                       long[] newNewRequestorBootIDs = new long[toIndex];
+                       System.arraycopy(newRequestorNodes, 0, 
newNewRequestorNodes, 0, toIndex);
+                       System.arraycopy(newRequestorTimes, 0, 
newNewRequestorTimes, 0, toIndex);
+                       System.arraycopy(newRequestorBootIDs, 0, 
newNewRequestorBootIDs, 0, toIndex);
+                       newRequestorNodes = newNewRequestorNodes;
+                       newRequestorTimes = newNewRequestorTimes;
+                       newRequestorBootIDs = newNewRequestorBootIDs;
+               }
+               requestorNodes = newRequestorNodes;
+               requestorTimes = newRequestorTimes;
+               requestorBootIDs = newRequestorBootIDs;
+       }
+
+       private synchronized void addRequestedFrom(PeerNode[] requestedFrom, 
long now) {
+               sentTime = now;
+               int notIncluded = 0;
+               int nulls = 0;
+               int ptr = 0;
+               for(int i=0;i<requestedFrom.length;i++) {
+                       PeerNode req = requestedFrom[i];
+                       boolean requestorIncluded = false;
+                       for(int j=0;j<requestedNodes.length;j++) {
+                               PeerNode got = requestedNodes[i] == null ? null 
: (PeerNode) requestedNodes[i].get();
+                               if(got == null)
+                                       nulls++;
+                               if(got == req) {
+                                       // Update existing entry
+                                       requestorIncluded = true;
+                                       requestedLocs[j] = 
req.getLocation().getValue();
+                                       requestedBootIDs[j] = req.getBootID();
+                                       requestedTimes[j] = now;
+                                       break;
+                               }
+                       }
+                       if(!requestorIncluded) {
+                               notIncluded++;
+                               requestedFrom[ptr++] = requestedFrom[i];
+                       } // if it's new, keep it in requestedFrom, otherwise 
delete it
+               }
+               for(int i=ptr;i<requestedFrom.length;i++) requestedFrom[i] = 
null;
+               if(notIncluded == 0 && nulls == 0) return;
+               // Because weak, these can become null; doesn't matter, but we 
want to minimise memory usage
+               if(notIncluded == nulls && notIncluded > 0) {
+                       // Nice special case
+                       int x = 0;
+                       for(int i=0;i<requestedNodes.length;i++) {
+                               if(requestedNodes[i].get() == null) {
+                                       PeerNode pn = requestedFrom[x++];
+                                       requestedNodes[i] = pn.myRef;
+                                       requestedLocs[i] = 
pn.getLocation().getValue();
+                                       requestedTimes[i] = now;
+                                       if(x == ptr) break;
+                               }
+                       }
+                       return;
+               }
+               WeakReference[] newRequestedNodes = new 
WeakReference[requestedNodes.length+notIncluded-nulls];
+               double[] newRequestedLocs = new 
double[requestedNodes.length+notIncluded-nulls];
+               long[] newRequestedBootIDs = new 
long[requestedNodes.length+notIncluded-nulls];
+               long[] newRequestedTimes = new 
long[requestedNodes.length+notIncluded-nulls];
+
+               int fromIndex = 0;
+               int toIndex = 0;
+               for(int i=0;i<requestedNodes.length;i++) {
+                       WeakReference ref = requestedNodes[i];
+                       if(ref == null || ref.get() == null) {
+                               while(fromIndex < ptr) {
+                                       PeerNode pn = requestedFrom[fromIndex];
+                                       if(pn != null) {
+                                               newRequestedNodes[toIndex] = 
pn.myRef;
+                                               newRequestedLocs[toIndex] = 
pn.getLocation().getValue();
+                                               newRequestedBootIDs[toIndex] = 
pn.getBootID();
+                                               newRequestedTimes[toIndex] = 
now;
+                                               toIndex++;
+                                       }
+                                       fromIndex++;
+                               }
+                       } else {
+                               newRequestedNodes[toIndex] = requestedNodes[i];
+                               newRequestedLocs[toIndex] = requestedLocs[i];
+                               newRequestedBootIDs[toIndex] = 
requestedBootIDs[i];
+                               newRequestedTimes[toIndex] = requestedTimes[i];
+                               toIndex++;
+                       }
+               }
+               for(;fromIndex<ptr;fromIndex++) {
+                       PeerNode pn = requestedFrom[fromIndex];
+                       if(pn != null) {
+                               newRequestedNodes[toIndex] = pn.myRef;
+                               newRequestedLocs[toIndex] = 
pn.getLocation().getValue();
+                               newRequestedBootIDs[toIndex] = pn.getBootID();
+                               newRequestedTimes[toIndex] = now;
+                               toIndex++;
+                       }
+               }
+               for(int i=toIndex;i<newRequestedNodes.length;i++) 
newRequestedNodes[i] = null;
+               if(toIndex > newRequestedNodes.length + 2) {
+                       WeakReference[] newNewRequestedNodes = new 
WeakReference[toIndex];
+                       double[] newNewRequestedLocs = new double[toIndex];
+                       long[] newNewRequestedBootIDs = new long[toIndex];
+                       long[] newNewRequestedTimes = new long[toIndex];
+                       System.arraycopy(newRequestedNodes, 0, 
newNewRequestedNodes, 0, toIndex);
+                       System.arraycopy(newRequestedLocs, 0, 
newNewRequestedLocs, 0, toIndex);
+                       System.arraycopy(newRequestedBootIDs, 0, 
newNewRequestedBootIDs, 0, toIndex);
+                       System.arraycopy(newRequestedTimes, 0, 
newNewRequestedTimes, 0, toIndex);
+                       newRequestedNodes = newNewRequestedNodes;
+                       newRequestedLocs = newNewRequestedLocs;
+                       newRequestedBootIDs = newNewRequestedBootIDs;
+                       newRequestedTimes = newNewRequestedTimes;
+               }
+               requestedNodes = newRequestedNodes;
+               requestedLocs = newRequestedLocs;
+               requestedBootIDs = newRequestedBootIDs;
+               requestedTimes = newRequestedTimes;
+       }
+
+       public double bestLiveLocDiff() {
+               WeakReference[] nodes;
+               synchronized(this) {
+                       nodes = requestedNodes;
+               }
+               double bestDiff = 2.0;
+               for(int i=0;i<nodes.length;i++) {
+                       if(nodes[i] == null) continue;
+                       PeerNode pn = (PeerNode) nodes[i].get();
+                       if(pn == null) continue;
+                       if(!(pn.isRoutable() && pn.isRoutingBackedOff())) 
continue;
+                       double diff = 
PeerManager.distance(key.toNormalizedDouble(), pn.getLocation().getValue());
+                       if(diff < bestDiff) bestDiff = diff;
+               }
+               return bestDiff;
+       }
+
+       /** Offer this node to all the nodes that have requested it, and all 
the nodes it has been requested from.
+        * Called after a) the data has been stored, and b) this entry has been 
removed from the FT */
+       public void offer() {
+               for(int i=0;i<requestorNodes.length;i++) {
+                       WeakReference ref = requestorNodes[i];
+                       if(ref == null) continue;
+                       PeerNode pn = (PeerNode) ref.get();
+                       if(pn == null) continue;
+                       if(pn.getBootID() != requestorBootIDs[i]) continue;
+                       pn.offer(key);
+               }
+               for(int i=0;i<requestedNodes.length;i++) {
+                       WeakReference ref = requestedNodes[i];
+                       if(ref == null) continue;
+                       PeerNode pn = (PeerNode) ref.get();
+                       if(pn == null) continue;
+                       if(pn.getBootID() != requestedBootIDs[i]) continue;
+                       pn.offer(key);
+               }
+       }
+
+       /**
+        * Has any node asked for this key?
+        */
+       public synchronized boolean othersWant(PeerNode peer) {
+               boolean anyValid = false;
+               for(int i=0;i<requestorNodes.length;i++) {
+                       WeakReference ref = requestorNodes[i];
+                       if(ref == null) continue;
+                       PeerNode pn = (PeerNode) ref.get();
+                       if(pn == null) {
+                               requestorNodes[i] = null;
+                               continue;
+                       }
+                       long bootID = pn.getBootID();
+                       if(bootID != requestorBootIDs[i]) {
+                               requestorNodes[i] = null;
+                               continue;
+                       }
+                       anyValid = true;
+                       if(pn == peer) return true;
+               }
+               if(!anyValid) {
+                       requestorNodes = new WeakReference[0];
+                       requestorTimes = requestorBootIDs = new long[0];
+               }
+               return false;
+       }
+
+       /**
+        * Has this peer asked us for the key?
+        */
+       public synchronized boolean askedByPeer(PeerNode peer, long now) {
+               boolean anyValid = false;
+               for(int i=0;i<requestorNodes.length;i++) {
+                       WeakReference ref = requestorNodes[i];
+                       if(ref == null) continue;
+                       PeerNode pn = (PeerNode) ref.get();
+                       if(pn == null) {
+                               requestorNodes[i] = null;
+                               continue;
+                       }
+                       long bootID = pn.getBootID();
+                       if(bootID != requestorBootIDs[i]) {
+                               requestorNodes[i] = null;
+                               continue;
+                       }
+                       if(now - requestorTimes[i] > 
MAX_TIME_BETWEEN_REQUEST_AND_OFFER) return true;
+                       anyValid = true;
+                       if(pn == peer) return true;
+               }
+               if(!anyValid) {
+                       requestorNodes = new WeakReference[0];
+                       requestorTimes = requestorBootIDs = new long[0];
+               }
+               return false;
+       }
+
+       /**
+        * Have we asked this peer for the key?
+        */
+       public synchronized boolean askedFromPeer(PeerNode peer, long now) {
+               boolean anyValid = false;
+               for(int i=0;i<requestedNodes.length;i++) {
+                       WeakReference ref = requestedNodes[i];
+                       if(ref == null) continue;
+                       PeerNode pn = (PeerNode) ref.get();
+                       if(pn == null) {
+                               requestedNodes[i] = null;
+                               continue;
+                       }
+                       long bootID = pn.getBootID();
+                       if(bootID != requestedBootIDs[i]) {
+                               requestedNodes[i] = null;
+                               continue;
+                       }
+                       if(now - requestedTimes[i] > 
MAX_TIME_BETWEEN_REQUEST_AND_OFFER) return true;
+                       anyValid = true;
+                       if(pn == peer) return true;
+               }
+               if(!anyValid) {
+                       requestedNodes = new WeakReference[0];
+                       requestedTimes = requestedBootIDs = new long[0];
+               }
+               return false;
+       }
+
+       public synchronized boolean isEmpty(long now) {
+               if(timeoutTime > now) return false;
+               if(requestedNodes.length > 0) return false;
+               if(requestorNodes.length > 0) return false;
+               return true;
+       }
+
+}
\ No newline at end of file

Modified: trunk/freenet/src/freenet/node/NodeClientCore.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeClientCore.java  2007-06-27 12:25:15 UTC 
(rev 13777)
+++ trunk/freenet/src/freenet/node/NodeClientCore.java  2007-06-27 13:23:27 UTC 
(rev 13778)
@@ -36,6 +36,7 @@
 import freenet.keys.Key;
 import freenet.keys.KeyBlock;
 import freenet.keys.NodeCHK;
+import freenet.keys.NodeSSK;
 import freenet.keys.SSKBlock;
 import freenet.keys.SSKVerifyException;
 import freenet.l10n.L10n;
@@ -1076,4 +1077,16 @@
        public File getTempDir() {
                return tempDir;
        }
+
+       /**
+        * Has any client registered an interest in this particular key?
+        */
+       public boolean clientWantKey(Key key) {
+               if(key instanceof NodeCHK)
+                       return 
requestStarters.chkFetchScheduler.anyWantKey(key);
+               else if(key instanceof NodeSSK)
+                       return 
requestStarters.sskFetchScheduler.anyWantKey(key);
+               else
+                       throw new IllegalArgumentException("Not a CHK and not 
an SSK!");
+       }
 }

Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java        2007-06-27 12:25:15 UTC 
(rev 13777)
+++ trunk/freenet/src/freenet/node/PeerNode.java        2007-06-27 13:23:27 UTC 
(rev 13778)
@@ -61,6 +61,7 @@
 import freenet.io.xfer.PartiallyReceivedBulk;
 import freenet.keys.ClientSSK;
 import freenet.keys.FreenetURI;
+import freenet.keys.Key;
 import freenet.keys.USK;
 import freenet.l10n.L10n;
 import freenet.node.useralerts.N2NTMUserAlert;
@@ -3943,4 +3944,14 @@
        public long getClockDelta() {
                return clockDelta;
        }
+
+       /** Offer a key to this node */
+       public void offer(Key key) {
+               Message msg = DMT.createFNPOfferKey(key);
+               try {
+                       sendAsync(msg, null, 0, null);
+               } catch (NotConnectedException e) {
+                       // Ignore
+               }
+       }
 }

Modified: trunk/freenet/src/freenet/support/LRUHashtable.java
===================================================================
--- trunk/freenet/src/freenet/support/LRUHashtable.java 2007-06-27 12:25:15 UTC 
(rev 13777)
+++ trunk/freenet/src/freenet/support/LRUHashtable.java 2007-06-27 13:23:27 UTC 
(rev 13778)
@@ -129,4 +129,8 @@
         }
     }

+       public boolean isEmpty() {
+               return list.isEmpty();
+       }
+
 }


Reply via email to