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(); + } + }
