Package freenet.node

Source Code of freenet.node.OpennetManager

/* 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 static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.Map;

import freenet.crypt.Util;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
import freenet.io.comm.FreenetInetAddress;
import freenet.io.comm.Message;
import freenet.io.comm.MessageFilter;
import freenet.io.comm.NotConnectedException;
import freenet.io.comm.Peer;
import freenet.io.comm.PeerContext;
import freenet.io.comm.PeerParseException;
import freenet.io.comm.ReferenceSignatureVerificationException;
import freenet.io.comm.RetrievalException;
import freenet.io.comm.SlowAsyncMessageFilterCallback;
import freenet.io.xfer.BulkReceiver;
import freenet.io.xfer.BulkTransmitter;
import freenet.io.xfer.BulkTransmitter.AllSentCallback;
import freenet.io.xfer.PartiallyReceivedBulk;
import freenet.node.OpennetPeerNode.NOT_DROP_REASON;
import freenet.support.Fields;
import freenet.support.HTMLNode;
import freenet.support.LRUQueue;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.SimpleFieldSet;
import freenet.support.TimeSortedHashtable;
import freenet.support.io.ByteArrayRandomAccessBuffer;
import freenet.support.io.Closer;
import freenet.support.io.FileUtil;
import freenet.support.io.NativeThread;
import freenet.support.transport.ip.HostnameSyntaxException;
import freenet.support.transport.ip.IPUtil;

/**
* Central location for all things opennet.
* In particular:
* - Opennet crypto
* - LRU connections
*
* Both here and in OpennetPeerNode there are lots of dubious heuristics to avoid excessive
* connection churn.
* @author toad
*/
public class OpennetManager {

  final Node node;
  final NodeCrypto crypto;
  final Announcer announcer;
  final SeedAnnounceTracker seedTracker = new SeedAnnounceTracker();

  /* The routing table is split into "buckets" by distance, each of which has a separate LRU
   * list. For now there are only 2 buckets; the PETS paper suggested many buckets, but this
   * would have larger overhead, more dependence on the network size, and it is not clear that
   * it is necessary at the moment.
   *
   * The measured global link length distribution showed a good (1/d) length distribution below
   * 0.01 (but nowhere near enough nodes) and a flat distribution above 0.01. Hence the choice of
   * LONG_DISTANCE as 0.01. It appeared that there were very few short links (~ 15% less than
   * 0.01 distance) and a lot of random long links, which is the opposite of what we need for
   * good routing, so requests would mostly bounce around randomly on the long links.
   *
   * LONG_PROPORTION is chosen as 30% for two reasons: (a) It is close to the Kleinberg optimum
   * (around 20%), and (b) it ensures that nodes with 10 connections still have 3 long links, so
   * long links cannot form chains and the routing still scales if the short routing is broken.
   *
   * See USK@ZLwcSLwqpM1527Tw1YmnSiXgzITU0neHQ11Cyl0iLmk,f6FLo3TvsEijIcJq-X3BTjjtm0ErVZwAPO7AUd9V7lY,AQACAAE/fix-link-length/7/
   * (FIXME move to wiki or other permanent storage)
   */
  /** Peers with more than this distance are considered "long links". */
  static final double LONG_DISTANCE = 0.01;
  /** This proportion of the routing table consists of "long links". */
  static final double LONG_PROPORTION = 0.3;
  /** The proportion of the routing table which consists of "short links". */
  public static final double SHORT_PROPORTION = 1.0 - LONG_PROPORTION;
 
    enum LinkLengthClass {
        /** Shorter than LONG_DISTANCE */
        SHORT {
            @Override
            public int getTargetPeers(int target) {
                int longPeers = (int) (target * LONG_PROPORTION);
                return target - longPeers;
            }
        },
        /** Longer than LONG_DISTANCE */
        LONG {
            @Override
            public int getTargetPeers(int target) {
                int longPeers = (int) (target * LONG_PROPORTION);
                return longPeers;
            }
        };
        /** Get the target number of peers for this class, given the overall target number of peers */
        public abstract int getTargetPeers(int target);
    }
   
    /** Peers LRUs by LinkLengthClass. PeerNodes are promoted within their LRU when they
     * successfully fetch a key. Normally we take the bottom peer, but if that isn't eligible
     * to be dropped, we iterate up the list. */
    private final EnumMap<LinkLengthClass, LRUQueue<OpennetPeerNode>> peersLRUByDistance;
 
  /** Old peers. Opennet peers which we dropped but would still like to talk to
   * if we have no other option. */
  private final LRUQueue<OpennetPeerNode> oldPeers;
  /** Maximum number of old peers */
  static final int MAX_OLD_PEERS = 25;
  /** Time at which last dropped a peer due to an incoming connection of each type. */
  private final EnumMap<ConnectionType,Long> timeLastDropped;
  // These only count stuff where we actually have a node to add.
  private final EnumMap<ConnectionType,Long> connectionAttempts;
  private final EnumMap<ConnectionType,Long> connectionAttemptsAdded;
  private final EnumMap<ConnectionType,Long> connectionAttemptsAddedPlentySpace;
  private final EnumMap<ConnectionType,Long> connectionAttemptsRejectedByPerTypeEnforcement;
  private final EnumMap<ConnectionType,Long> connectionAttemptsRejectedNoPeersDroppable;
  /** Number of successful CHK requests since last added a node. All values are incremented on a
   * successful request, but when we add a node, we reset the value for that type of node. */
  private final EnumMap<ConnectionType,Long> successCount;

  /** Only drop a connection after at least this many successful requests.
   * This is per connection type. */
  // FIXME should be a function of # opennet peers? max # opennet peers? ...
  public static final int MIN_SUCCESS_BETWEEN_DROP_CONNS = 10;
  /** Chance of resetting path folding (for plausible deniability) is 1 in this number. */
  public static final int RESET_PATH_FOLDING_PROB = 20;
  /** Don't re-add a node until it's been up and disconnected for at least this long */
  public static final long DONT_READD_TIME = MINUTES.toMillis(1);
  /** Don't drop a node until it's at least this old, if it's connected. */
  public static final long DROP_MIN_AGE = MINUTES.toMillis(5);
  /** Don't drop a node until it's at least this old, if it's not connected (if it has connected once then DROP_DISCONNECT_DELAY applies, but only once an hour as below). Must be less than DROP_MIN_AGE.
   * Relatively generous because noderef transfers e.g. for announcement can be slow (Note
   * that announcements actually wait for previous transfers!). */
  public static final long DROP_MIN_AGE_DISCONNECTED = MINUTES.toMillis(5);
  /** Don't drop a node until this long after startup */
  public static final long DROP_STARTUP_DELAY = MINUTES.toMillis(2);
  /** Don't drop a node until this long after losing connection to it.
   * This should be long enough to cover a typical reboot, but not so long as to result in a lot
   * of disconnected nodes in the Strangers list. Also it should probably not be longer than DROP_MIN_AGE! */
  public static final long DROP_DISCONNECT_DELAY = MINUTES.toMillis(5);
  /** But if it has disconnected more than once in this period, allow it to be dropped anyway */
  public static final long DROP_DISCONNECT_DELAY_COOLDOWN = MINUTES.toMillis(60);
  /** Every DROP_CONNECTED_TIME, we may drop a peer even though it is connected.
   * This is per connection type, we should consider whether to reduce it further. */
  public static final long DROP_CONNECTED_TIME = MINUTES.toMillis(5);
  /** Minimum time between offers, if we have maximum peers. Less than the above limits,
   * since an offer may not be accepted. */
  public static final long MIN_TIME_BETWEEN_OFFERS = SECONDS.toMillis(30);

  private static volatile boolean logMINOR;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  /** How big to pad opennet noderefs to? If they are bigger than this then we won't send them. */
  public static final int PADDED_NODEREF_SIZE = 3072;
  /** Allow for future expansion. However at any given time all noderefs should be PADDED_NODEREF_SIZE */
  public static final int MAX_OPENNET_NODEREF_LENGTH = 32768;

  /** Enable scaling of peers with bandwidth? */
  public static final boolean ENABLE_PEERS_PER_KB_OUTPUT = true;
  /** Constant for scaling peers: we multiply bandwidth in kB/sec by this
   * and then take the square root. 12 gives 11 at 10K, 15 at 20K, 19 at
   * 30K, 26 at 60K, 34 at 100K, 40 at 140K, 100 at 2500K.
   * 212 at 30mbit/s (the mean upload in Japan in 2014) and
   * 363 at 88mbit/s (the mean upload in Hong Kong in 2014).*/
  public static final double SCALING_CONSTANT = 12.0;
  /**
   * Minimum number of peers. Do not reduce this: As a rough estimate, because the vast majority
   * of requests complete in 5 hops, this gives just one binary decision per hop on average.
   */
  public static final int MIN_PEERS_FOR_SCALING = 10;
  /** The maximum possible distance between two nodes in the wrapping [0,1) location space. */
  public static final double MAX_DISTANCE = 0.5;
  /** The fraction of nodes which are only a short distance away. */
  public static final double SHORT_NODES_FRACTION = LONG_DISTANCE / MAX_DISTANCE;
  /** The estimated average number of nodes which are active at any given time. */
  public static final int LAST_NETWORK_SIZE_ESTIMATE = 5000;
  /** The estimated number of nodes which are a short distance away. */
  public static final int AVAILABLE_SHORT_DISTANCE_NODES =
    (int) (LAST_NETWORK_SIZE_ESTIMATE * SHORT_NODES_FRACTION);
  /**
   * Maximum number of peers.
   *
   * This is limited by the expected availability of nodes with short links to a given location.
   * Above that number of peers, fast nodes will not be able to find enough peers with short
   * links.
   *
   * @see freenet.node.OpennetManager.LinkLengthClass
   */
  public static final int MAX_PEERS_FOR_SCALING =
    (int) (AVAILABLE_SHORT_DISTANCE_NODES / SHORT_PROPORTION);
  /** Maximum number of peers for purposes of FOAF attack/sanity check */
  public static final int PANIC_MAX_PEERS = MAX_PEERS_FOR_SCALING + 10;
  /** Stop trying to reconnect to an old-opennet-peer after a month. */
  public static final long MAX_TIME_ON_OLD_OPENNET_PEERS = DAYS.toMillis(31);

  // This is only relevant while the connection is in the grace period.
  // Null means none of the above e.g. not in grace period.
  public enum ConnectionType {
    PATH_FOLDING,
    ANNOUNCE,
    RECONNECT
  }

  private final long creationTime;
 
  private boolean stopping;

  public OpennetManager(Node node, NodeCryptoConfig opennetConfig, long startupTime, boolean enableAnnouncement) throws NodeInitException {
    this.creationTime = System.currentTimeMillis();
    this.node = node;
    crypto =
      new NodeCrypto(node, true, opennetConfig, startupTime, node.enableARKs);

    timeLastDropped = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    connectionAttempts = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    connectionAttemptsAdded = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    connectionAttemptsAddedPlentySpace = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    connectionAttemptsRejectedByPerTypeEnforcement = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    connectionAttemptsRejectedNoPeersDroppable = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    successCount = new EnumMap<ConnectionType,Long>(ConnectionType.class);
    for(ConnectionType c : ConnectionType.values()) {
      timeLastDropped.put(c, 0L);
      connectionAttempts.put(c, 0L);
      connectionAttemptsAdded.put(c, 0L);
      connectionAttemptsAddedPlentySpace.put(c, 0L);
      connectionAttemptsRejectedByPerTypeEnforcement.put(c, 0L);
      connectionAttemptsRejectedNoPeersDroppable.put(c, 0L);
      successCount.put(c, 0L);
    }

    File nodeFile = node.nodeDir().file("opennet-"+crypto.portNumber);
    File backupNodeFile = node.nodeDir().file("opennet-"+crypto.portNumber+".bak");

    // Keep opennet crypto details in a separate file
    try {
      readFile(nodeFile);
    } catch (IOException e) {
      try {
        readFile(backupNodeFile);
      } catch (IOException e1) {
        crypto.initCrypto();
      }
    }
    peersLRUByDistance = new EnumMap<LinkLengthClass, LRUQueue<OpennetPeerNode>>(LinkLengthClass.class);
    for(LinkLengthClass l : LinkLengthClass.values())
        peersLRUByDistance.put(l, new LRUQueue<OpennetPeerNode>());
    oldPeers = new LRUQueue<OpennetPeerNode>();
    announcer = (enableAnnouncement ? new Announcer(this) : null);
  }

  public void writeFile() {
    File nodeFile = node.nodeDir().file("opennet-"+crypto.portNumber);
    File backupNodeFile = node.nodeDir().file("opennet-"+crypto.portNumber+".bak");
    writeFile(nodeFile, backupNodeFile);
  }

  private void writeFile(File orig, File backup) {
    SimpleFieldSet fs = crypto.exportPrivateFieldSet();

    if(orig.exists()) backup.delete();

    FileOutputStream fos = null;
    OutputStreamWriter osr = null;
    BufferedWriter bw = null;
    try {
      fos = new FileOutputStream(backup);
      osr = new OutputStreamWriter(fos, "UTF-8");
      bw = new BufferedWriter(osr);
      fs.writeTo(bw);

      bw.close();
      FileUtil.renameTo(backup, orig);
    } catch (IOException e) {
      Closer.close(bw);
      Closer.close(osr);
      Closer.close(fos);
    }
  }

  private void readFile(File filename) throws IOException {
    // REDFLAG: Any way to share this code with Node and NodePeer?
    FileInputStream fis = new FileInputStream(filename);
    InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
    BufferedReader br = new BufferedReader(isr);
    SimpleFieldSet fs = new SimpleFieldSet(br, false, true);
    br.close();
    // Read contents
    String[] udp = fs.getAll("physical.udp");
    if((udp != null) && (udp.length > 0)) {
      for(String u: udp) {
        // Just keep the first one with the correct port number.
        Peer p;
        try {
          p = new Peer(u, false, true);
        } catch (HostnameSyntaxException e) {
          Logger.error(this, "Invalid hostname or IP Address syntax error while loading opennet peer node reference: "+u);
          System.err.println("Invalid hostname or IP Address syntax error while loading opennet peer node reference: "+u);
          continue;
        } catch (PeerParseException e) {
          throw (IOException)new IOException().initCause(e);
        }
        if(p.getPort() == crypto.portNumber) {
          // DNSRequester doesn't deal with our own node
          node.ipDetector.setOldIPAddress(p.getFreenetAddress());
          break;
        }
      }
    }

    crypto.readCrypto(fs);
  }

  public void start() {
    synchronized(this) {
      stopping = false;
    }
    // Do this outside the constructor, since the constructor is called by the Node constructor, and callbacks may make assumptions about data structures being ready.
    node.peers.tryReadPeers(node.nodeDir().file("openpeers-"+crypto.portNumber).toString(), crypto, this, true, false);
    OpennetPeerNode[] nodes = node.peers.getOpennetPeers();
    Arrays.sort(nodes, new Comparator<OpennetPeerNode>() {
      @Override
      public int compare(OpennetPeerNode pn1, OpennetPeerNode pn2) {
        if(pn1 == pn2) return 0;
        long lastSuccess1 = pn1.timeLastSuccess();
        long lastSuccess2 = pn2.timeLastSuccess();

        if(lastSuccess1 > lastSuccess2) return 1;
        if(lastSuccess2 > lastSuccess1) return -1;

        boolean neverConnected1 = pn1.neverConnected();
        boolean neverConnected2 = pn2.neverConnected();
        if(neverConnected1 && (!neverConnected2))
          return -1;
        if((!neverConnected1) && neverConnected2)
          return 1;
        // a-b not opposite sign to b-a possible in a corner case (a=0 b=Integer.MIN_VALUE).
        if(pn1.hashCode > pn2.hashCode) return 1;
        else if(pn1.hashCode < pn2.hashCode) return -1;
        Logger.error(this, "Two OpennetPeerNodes with the same hashcode: "+pn1+" vs "+pn2);
        return Fields.compareObjectID(pn1, pn2);
      }
    });
    for(OpennetPeerNode opn: nodes) {
        // Drop any peers which don't have a location yet. That means we haven't connected to
        // them yet, and we need the location to decide which LRU to put them in ...
        // This should only be a problem with old nodes; we will include the location in new
        // path folding noderefs...
        if(Location.isValid(opn.getLocation()))
            lruQueue(opn).push(opn);
        else
            node.peers.disconnectAndRemove(opn, false, false, false);
    }
    if(logMINOR) {
      Logger.minor(this, "My full compressed ref: "+crypto.myCompressedFullRef().length);
      Logger.minor(this, "My full setup ref: "+crypto.myCompressedSetupRef().length);
      Logger.minor(this, "My heavy setup ref: "+crypto.myCompressedHeavySetupRef().length);
    }
    dropAllExcessPeers();
    writeFile();
    // Read old peers
    node.peers.tryReadPeers(node.nodeDir().file("openpeers-old-"+crypto.portNumber).toString(), crypto, this, true, true);
    crypto.start();
    if(announcer!= null)
      announcer.start();
  }

  /**
   * Called when opennet is disabled
   */
  public void stop(boolean purge) {
    synchronized(this) {
      stopping = true;
    }
    if(announcer != null)
      announcer.stop();
    crypto.stop();
    if(purge)
      node.peers.removeOpennetPeers();
    crypto.socket.getAddressTracker().setPresumedInnocent();
  }
 
  synchronized boolean stopping() {
    return stopping;
  }
 
  private LRUQueue<OpennetPeerNode> lruQueue(LinkLengthClass distance) {
      return peersLRUByDistance.get(distance);
  }
 
    private LRUQueue<OpennetPeerNode> lruQueue(OpennetPeerNode pn) {
        return lruQueue(pn.linkLengthClass());
    }
   
  public boolean alreadyHaveOpennetNode(SimpleFieldSet fs) {
    try {
      // FIXME OPT can we do this cheaper?
      // Maybe just parse the pubkey, and then compare it with the existing peers?
      OpennetPeerNode pn = new OpennetPeerNode(fs, node, crypto, this, node.peers, false, crypto.packetMangler);
      if(lruQueue(pn).contains(pn)) {
        if(logMINOR) Logger.minor(this, "Not adding "+pn.userToString()+" to opennet list as already there");
        return true;
      }
      // Don't check for self. That should be passed through too.
      return false;
    } catch (Throwable t) {
      // Don't break the code flow in the caller which is normally a request.
      Logger.error(this, "Caught "+t+" parsing opennet node from fieldset", t);
      return false;
    }
  }

  public OpennetPeerNode addNewOpennetNode(SimpleFieldSet fs, ConnectionType connectionType, boolean allowExisting) throws FSParseException, PeerParseException, ReferenceSignatureVerificationException {
    try {
    OpennetPeerNode pn = new OpennetPeerNode(fs, node, crypto, this, node.peers, false, crypto.packetMangler);
    if(Arrays.equals(pn.getPubKeyHash(), crypto.pubKeyHash)) {
      if(logMINOR) Logger.minor(this, "Not adding self as opennet peer");
      return null; // Equal to myself
    }
    LinkLengthClass distance = pn.linkLengthClass();
    LRUQueue<OpennetPeerNode> peersLRU = lruQueue(distance);
    if(peersLRU.contains(pn)) {
      if(logMINOR) Logger.minor(this, "Not adding "+pn.userToString()+" to opennet list as already there");
      if(allowExisting) {
        // However, we can reconnect.
        return (OpennetPeerNode) peersLRU.get(pn);
      } else {
        return null;
      }
    }
    if(pn.isUnroutableOlderVersion() && node.nodeUpdater != null && node.nodeUpdater.dontAllowUOM()) {
      // We can't send the UOM to it, so we should not accept it.
      // Plus, some versions around 1320 had big problems with being connected both as a seednode and as an opennet peer.
      return null;
    }
    if(wantPeer(pn, true, false, false, connectionType, distance)) return pn;
    else return null;
    // Start at bottom. Node must prove itself.
    } catch (Throwable t) {
      // Don't break the code flow in the caller which is normally a request.
      Logger.error(this, "Caught "+t+" adding opennet node from fieldset", t);
      return null;
    }

  }

  /** When did we last offer our noderef to some other node? */
  private long timeLastOffered;

  void forceAddPeer(OpennetPeerNode nodeToAddNow, boolean addAtLRU) {
      LinkLengthClass distance = nodeToAddNow.linkLengthClass();
        LRUQueue<OpennetPeerNode> peersLRU = lruQueue(distance);
    synchronized(this) {
      if(addAtLRU)
        peersLRU.pushLeast(nodeToAddNow);
      else
        peersLRU.push(nodeToAddNow);
      oldPeers.remove(nodeToAddNow);
    }
    dropExcessPeers(distance);
  }

  public boolean wantPeer(OpennetPeerNode nodeToAddNow, boolean addAtLRU, boolean justChecking, boolean oldOpennetPeer, ConnectionType connectionType) {
      if(nodeToAddNow != null) {
          if(!Location.isValid(nodeToAddNow.getLocation())) {
              Logger.error(this, "Added opennet node reference must include a valid location", new Exception("error"));
              return false;
          }
            // We have received a node reference, so we know whether it is long or short.
          LinkLengthClass distance = nodeToAddNow.linkLengthClass();
          return wantPeer(nodeToAddNow, addAtLRU, justChecking, oldOpennetPeer, connectionType, distance);
      } else {
          // Initiate path folding if we want a long link *or* a short link.
          // FIXME ideally we'd like to indicate whether we want long links or short links.
          return wantPeer(nodeToAddNow, addAtLRU, justChecking, oldOpennetPeer, connectionType, LinkLengthClass.SHORT)
               || wantPeer(nodeToAddNow, addAtLRU, justChecking, oldOpennetPeer, connectionType, LinkLengthClass.LONG);
      }
  }
 
  /**
   * Trim the peers list and possibly add a new node. Note that if we are not adding a new node,
   * we will only return true every MIN_TIME_BETWEEN_OFFERS, to prevent problems caused by many
   * pending offers being accepted simultaneously.
   * @param nodeToAddNow Node to add. Can be null, which means the caller needs to know whether
   * we have space for another node, without actually adding one. This happens e.g. when we are
   * the data source and are trying to decide whether to send our noderef downstream for path
   * folding.
   * @param addAtLRU If there is a node to add, add it at the bottom rather than the top. Normally
   * we set this on new path folded nodes so that they will be replaced if during the trial period,
   * plus the time it takes to get a new path folding offer, they don't have a successful request.
   * @param justChecking If true, we want to know whether there is space for a node to be added
   * RIGHT NOW. If false, the normal behaviour applies: if nodeToAddNow is passed in, we decide
   * whether to add that node, if it's null, we decide whether to send an offer subject to the
   * inter-offer time.
   * @param oldOpennetPeer If true, we are trying to add an old-opennet-peer which has reconnected.
   * There is a throttle, we accept no more than one old-opennet-peer every 30 seconds. On receiving
   * a packet, we call once to decide whether to try to parse it against the old-opennet-peers, and
   * then again to decide whether it is worth keeping; in the latter case if we decide not, the
   * old-opennet-peer will be told to disconnect and go away, but normally we don't reach that point
   * because of the first check.
   * @param isLong True if the peer to add is distant. False otherwise.
   * @return True if the node was added / should be added.
   */
  public boolean wantPeer(OpennetPeerNode nodeToAddNow, boolean addAtLRU, boolean justChecking, boolean oldOpennetPeer, ConnectionType connectionType, LinkLengthClass distance) {
      LRUQueue<OpennetPeerNode> peersLRU = lruQueue(distance);
    boolean notMany = false;
    boolean noDisconnect;
    long now = System.currentTimeMillis();
    if(logMINOR) Logger.minor(this, "wantPeer("+(nodeToAddNow != null) + "," +addAtLRU+","+justChecking+","+oldOpennetPeer+","+connectionType+","+distance+")");
    boolean outdated = nodeToAddNow == null ? false : nodeToAddNow.isUnroutableOlderVersion();
    if(outdated && logMINOR) Logger.minor(this, "Peer is outdated: "+nodeToAddNow.getVersionNumber()+" for "+connectionType);
    if(outdated) {
      if(tooManyOutdatedPeers()) {
        if(logMINOR) Logger.minor(this, "Rejecting TOO OLD peer from "+connectionType+" (too many already): "+nodeToAddNow);
        return false;
      }
    }
    if(nodeToAddNow != null && crypto.config.oneConnectionPerAddress()) {
      boolean okay = false;
      boolean any = false;
      Peer[] handshakeIPs = nodeToAddNow.getHandshakeIPs();
      if(handshakeIPs != null) {
        for(Peer p : handshakeIPs) {
          if(p == null) continue;
          FreenetInetAddress addr = p.getFreenetAddress();
          if(addr == null) continue;
          InetAddress a = addr.getAddress(false);
          if(a == null) continue;
          if(a.isAnyLocalAddress() || a.isLinkLocalAddress() || IPUtil.isSiteLocalAddress(a)) continue;
          any = true;
          if(crypto.allowConnection(nodeToAddNow, addr))
            okay = true;
          else {
            // if NodeCrypto reject *any* address, reject peer
            okay = false;
            break;
          }
        }
      } else {
        Logger.error(this, "Peer does not have any IP addresses???");
      }
      if(any && !okay) {
        Logger.normal(this, "Rejecting peer as we are already connected to a peer with the same IP address");
        return false;
      }
    }
    int maxPeers = getNumberOfConnectedPeersToAim(distance);
    if(logMINOR) Logger.minor(this, "Peers target: "+maxPeers);
    synchronized(this) {
      if(nodeToAddNow != null &&
          peersLRU.contains(nodeToAddNow)) {
        if(logMINOR)
          Logger.minor(this, "Opennet peer already present in LRU: "+nodeToAddNow);
        return true;
      }
      if(nodeToAddNow != null)
        connectionAttempts.put(connectionType, connectionAttempts.get(connectionType)+1);
      if(getSize(distance) < maxPeers || outdated) {
        if(nodeToAddNow != null) {
          if(logMINOR) Logger.minor(this, "Added opennet peer "+nodeToAddNow+" as opennet peers list not full");
          if(addAtLRU)
            peersLRU.pushLeast(nodeToAddNow);
          else
            peersLRU.push(nodeToAddNow);
          oldPeers.remove(nodeToAddNow);
          connectionAttemptsAddedPlentySpace.put(connectionType, connectionAttemptsAddedPlentySpace.get(connectionType)+1);
        } else {
          if(logMINOR) Logger.minor(this, "Want peer because not enough opennet nodes");
        }
        if(nodeToAddNow == null && !justChecking)
          timeLastOffered = System.currentTimeMillis();
        notMany = true;
        // Don't check timeLastAddedOldOpennetPeer, since we want it anyway. But do update it.
      }
      // Old opennet peers should only replace free slots / disconnected droppable nodes.
      // We can make offers regardless of timeLastOffered provided they are disconnected droppable peers.
      // And we only allow a connection to be dropped every 10 successful fetches.
      noDisconnect = successCount.get(connectionType) < MIN_SUCCESS_BETWEEN_DROP_CONNS || oldOpennetPeer || (nodeToAddNow == null && now - timeLastOffered <= MIN_TIME_BETWEEN_OFFERS) || now - timeLastDropped.get(connectionType) < DROP_CONNECTED_TIME;
    }
    if(nodeToAddNow != null)
      nodeToAddNow.setAddedReason(connectionType);
    if(notMany) {
      if(nodeToAddNow != null) {
        node.peers.addPeer(nodeToAddNow, true, true); // Add to peers outside the OM lock
      }
      return true;
    }
    boolean canAdd = true;
    ArrayList<OpennetPeerNode> dropList = new ArrayList<OpennetPeerNode>();
    maxPeers = getNumberOfConnectedPeersToAim(distance);
    synchronized(this) {
      int size = getSize(distance);
      if(size == maxPeers && nodeToAddNow == null) {
        // Allow an offer to be predicated on throwing out a connected node,
        // provided that we meet the other criteria e.g. time since last added,
        // node isn't too new.
        OpennetPeerNode toDrop = peerToDrop(noDisconnect, false, nodeToAddNow != null, connectionType, maxPeers, distance, peersLRU);
        if(toDrop == null) {
          if(logMINOR)
            Logger.minor(this, "No more peers to drop (in first bit), still "+peersLRU.size()+" peers, cannot accept peer"+(nodeToAddNow == null ? "" : nodeToAddNow.toString()));
          canAdd = false;
          if(nodeToAddNow != null)
            connectionAttemptsRejectedNoPeersDroppable.put(connectionType, connectionAttemptsRejectedNoPeersDroppable.get(connectionType)+1);
        } else {
          // Only check per-type limits if we are throwing out connected peers.
          // This is important for bootstrapping, given the low announcement limit.
          if(toDrop.isConnected() && enforcePerTypeGracePeriodLimits(maxPeers, connectionType, nodeToAddNow != null, peersLRU)) {
            if(nodeToAddNow != null)
              connectionAttemptsRejectedByPerTypeEnforcement.put(connectionType, connectionAttemptsRejectedByPerTypeEnforcement.get(connectionType)+1);
            return false;
          }
        }
      } else while(canAdd && (size = getSize(distance)) > maxPeers - ((nodeToAddNow == null || outdated) ? 0 : 1)) {
        OpennetPeerNode toDrop;
        // can drop peers which are over the limit
        toDrop = peerToDrop(noDisconnect, false, nodeToAddNow != null, connectionType, maxPeers, distance, peersLRU);
        if(toDrop == null) {
          if(logMINOR)
            Logger.minor(this, "No more peers to drop, still "+peersLRU.size()+" peers, cannot accept peer"+(nodeToAddNow == null ? "" : nodeToAddNow.toString()));
          canAdd = false;
          if(nodeToAddNow != null)
            connectionAttemptsRejectedNoPeersDroppable.put(connectionType, connectionAttemptsRejectedNoPeersDroppable.get(connectionType)+1);
          break;
        }
        // Only check per-type limits if we are throwing out connected peers.
        // This is important for bootstrapping, given the low announcement limit.
        if(toDrop.isConnected() && enforcePerTypeGracePeriodLimits(maxPeers, connectionType, nodeToAddNow != null, peersLRU)) {
          if(nodeToAddNow != null)
            connectionAttemptsRejectedByPerTypeEnforcement.put(connectionType, connectionAttemptsRejectedByPerTypeEnforcement.get(connectionType)+1);
          return false;
        }
        if(nodeToAddNow != null || size > maxPeers) {
          if(logMINOR)
            Logger.minor(this, "Drop opennet peer: "+toDrop+" (connected="+toDrop.isConnected()+") of "+peersLRU.size()+":"+getSize(distance));
          peersLRU.remove(toDrop);
          dropList.add(toDrop);
        }
      }
      if(canAdd && !justChecking) {
        if(nodeToAddNow != null) {
          successCount.put(connectionType, 0L);
          if(addAtLRU)
            peersLRU.pushLeast(nodeToAddNow);
          else
            peersLRU.push(nodeToAddNow);
          if(logMINOR) Logger.minor(this, "Added opennet peer "+nodeToAddNow+" after clearing "+dropList.size()+" items - now have "+peersLRU.size()+" opennet peers");
          oldPeers.remove(nodeToAddNow);
          if(!dropList.isEmpty()) {
            if(logMINOR) Logger.minor(this, "Dropped opennet peer: "+dropList.get(0));
            timeLastDropped.put(connectionType, now);
          }
          connectionAttemptsAdded.put(connectionType, connectionAttemptsAdded.get(connectionType)+1);
        } else {
          // Do not update timeLastDropped, anything dropped was over the limit so doesn't count (because nodeToAddNow == null).
          if(!justChecking) {
            timeLastOffered = now;
            if(logMINOR)
              Logger.minor(this, "Sending offer");
          }
        }
      }
    }
    if(nodeToAddNow != null && canAdd && !node.peers.addPeer(nodeToAddNow, true, true)) {
      if(logMINOR)
        Logger.minor(this, "Already in global peers list: "+nodeToAddNow+" when adding opennet node");
      // Just because it's in the global peers list doesn't mean its in the LRU, it may be an old-opennet-peers reconnection.
      // In which case we add it to the global peers list *before* adding it here.
    }
    for(OpennetPeerNode pn : dropList) {
      if(logMINOR) Logger.minor(this, "Dropping LRU opennet peer: "+pn);
      pn.setAddedReason(null);
      node.peers.disconnectAndRemove(pn, true, true, true);
    }
    return canAdd;
  }
 
  private int maxOutdatedPeers() {
    return Math.max(5, getNumberOfConnectedPeersToAimIncludingDarknet() / 4);
  }
 
  private boolean tooManyOutdatedPeers() {
      // This does not check whether they are short or long as it is irrelevant for outdated peers.
    int maxTooOldPeers = maxOutdatedPeers();
    int count = 0;
    OpennetPeerNode[] peers = node.peers.getOpennetPeers();
    for(OpennetPeerNode pn : peers) {
      if(pn.isUnroutableOlderVersion()) {
        count++;
        if(count >= maxTooOldPeers)
          return true;
      }
    }
    return false;
  }

  private synchronized boolean enforcePerTypeGracePeriodLimits(int maxPeers, ConnectionType type, boolean addingPeer, LRUQueue<OpennetPeerNode> peersLRU) {
    if(type == null) {
      if(logMINOR) Logger.minor(this, "No type set, not enforcing per type limits");
    }

    // We do NOT want to have all our peers in grace periods!
    // For opennet to work, we need LRU. For LRU to work it needs a choice.
    // If everything is in a grace period, then we have no choice - we replace the one node that comes out of its grace period as soon as it does.
    // So first calculate an overall limit on the number of peers in grace periods.

    // Heuristic: Half rounded down.
    int maxGracePeriodPeers = maxPeers / 2;

    int announceMax;
    int reconnectMax;
    int pathFoldingMax;
    // Same total global number of slots as 1242/1243.
    announceMax = reconnectMax = (maxGracePeriodPeers / 5) + 1;
    pathFoldingMax = maxGracePeriodPeers - announceMax - reconnectMax;
    if(pathFoldingMax < 2) return false;
    if(logMINOR) Logger.minor(this, "Per type grace period limits: total peers: "+maxPeers+" announce "+announceMax+" reconnect "+reconnectMax+" path folding "+pathFoldingMax);
    int myLimit;
    if(type == ConnectionType.PATH_FOLDING)
      myLimit = pathFoldingMax;
    else if(type == ConnectionType.ANNOUNCE)
      myLimit = announceMax;
    else
      myLimit = reconnectMax;
    int count = 0;
    OpennetPeerNode[] peers = peersLRU.toArray(new OpennetPeerNode[peersLRU.size()]);
    for(OpennetPeerNode pn : peers) {
      if(pn.getAddedReason() != type) continue;
      if(!pn.isConnected()) continue;
      if(pn.isDroppable(false)) continue;
      if(++count >= myLimit) {
        if(logMINOR) Logger.minor(this, "Per type grace period limit rejected peer of type "+type+" count is "+count+" limit is "+myLimit);
        return true;
      }
    }
    if(logMINOR) Logger.minor(this, "Per type grace period limit allowed connection of type "+type+" count is "+count+" limit is "+myLimit+" addingPeer="+addingPeer);
    return false;
  }
 
  void dropAllExcessPeers() {
        for(LinkLengthClass l : LinkLengthClass.values()) dropExcessPeers(l);
  }

  void dropExcessPeers(LinkLengthClass distance) {
      LRUQueue<OpennetPeerNode> peersLRU = lruQueue(distance);
    int maxPeers = getNumberOfConnectedPeersToAim(distance);
    while(peersLRU.size() > maxPeers) {
      if(logMINOR)
              Logger.minor(this, "Dropping opennet peers: currently "+peersLRU.size()+" of "+maxPeers+" for "+distance+" distance links");
            OpennetPeerNode toDrop;
      toDrop = peerToDrop(false, false, false, null, maxPeers, distance, peersLRU);
      if(toDrop == null) toDrop = peerToDrop(false, true, false, null, maxPeers, distance, peersLRU);
      if(toDrop == null) return;
      synchronized(this) {
        peersLRU.remove(toDrop);
      }
      if(logMINOR)
        Logger.minor(this, "Dropping "+toDrop);
      node.peers.disconnectAndRemove(toDrop, true, true, true);
    }
  }

  // A TOO OLD peer does not count towards the limit, even if it is not connected.
  // It can however be dumped if it doesn't connect in a reasonable time, and if
  // it upgrades, it may not have the usual grace period.
 
  /**
   * How many opennet peers do we have?
   * Connected but out of date nodes don't count towards the connection limit. Let them connect for
   * long enough to auto-update. They will be disconnected eventually, and then removed:
   * @see OpennetPeerNode.shouldDisconnectAndRemoveNow()
   */
  synchronized public int getSize(LinkLengthClass distance) {
    int x = 0;
    for (Enumeration<OpennetPeerNode> e = lruQueue(distance).elements(); e.hasMoreElements();) {
      OpennetPeerNode pn = e.nextElement();
      if(!pn.isUnroutableOlderVersion()) x++;
    }
    return x;
  }

  private OpennetPeerNode peerToDrop(boolean noDisconnect, boolean force, boolean addingNode, ConnectionType connectionType, int maxPeers, LinkLengthClass distance, LRUQueue<OpennetPeerNode> peersLRU) {
    if(getSize(distance) < maxPeers) {
      // Don't drop any peers
      if(logMINOR) Logger.minor(this, "peerToDrop(): Not dropping any peer (force="+force+" addingNode="+addingNode+") because don't need to");
      return null;
    }
    synchronized(this) {
      EnumMap<NOT_DROP_REASON, Integer> map = null;
      if(addingNode) map = new EnumMap<NOT_DROP_REASON, Integer>(NOT_DROP_REASON.class);
      // Do we want it?
      OpennetPeerNode[] peers = peersLRU.toArrayOrdered(new OpennetPeerNode[peersLRU.size()]);
      for(OpennetPeerNode pn: peers) {
        if(pn == null) continue;
        boolean tooOld = pn.isUnroutableOlderVersion();
        if(pn.isConnected() && tooOld) {
          // Doesn't count towards the opennet peers limit, so no point dropping it.
          continue;
        }
        NOT_DROP_REASON reason = pn.isDroppableWithReason(false);
        if(map != null) {
          Integer x = map.get(reason);
          if(x == null)
            map.put(reason, 1);
          else
            map.put(reason, x+1);
        }
        // Over the limit does not force us to drop TOO OLD peers since they don't count towards the limit.
        if((reason != NOT_DROP_REASON.DROPPABLE) && ((!force) || tooOld)) {
          continue;
        }
        // LOCKING: Always take the OpennetManager lock first
        if(!pn.isConnected()) {
          if(logMINOR)
            Logger.minor(this, "Possibly dropping opennet peer "+pn+" as is disconnected (reason="+reason+" force="+force+" tooOld="+tooOld);
          pn.setWasDropped();
          return pn;
        }
      }
      if(noDisconnect) {
        if(addingNode && logMINOR) {
          Logger.minor(this, "Not disconnecting");
          if(map != null)
            for(Map.Entry<NOT_DROP_REASON, Integer> entry : map.entrySet()) {
              Logger.minor(this, ""+entry.getKey()+" : "+entry.getValue());
            }
        }
        return null;
      }
      if(map != null) map.clear();
      for(OpennetPeerNode pn: peers) {
        if(pn == null) continue;
        boolean tooOld = pn.isUnroutableOlderVersion();
        if(pn.isConnected() && tooOld) {
          // Doesn't count anyway.
          continue;
        }
        NOT_DROP_REASON reason = pn.isDroppableWithReason(false);
        if(map != null) {
          Integer x = map.get(reason);
          if(x == null)
            map.put(reason, 1);
          else
            map.put(reason, x+1);
        }
        // Over the limit does not force us to drop TOO OLD peers since they don't count towards the limit.
        if((reason != NOT_DROP_REASON.DROPPABLE) && ((!force) || tooOld)) {
          continue;
        }
        if(logMINOR)
          Logger.minor(this, "Possibly dropping opennet peer "+pn+" "+
              ((connectionType == null) ? "" : ((System.currentTimeMillis() - timeLastDropped.get(connectionType))+" ms since last dropped peer of type "+connectionType)));
        pn.setWasDropped();
        return pn;
      }
      if(addingNode && logMINOR) {
        Logger.minor(this, "Nothing to drop");
        if(map != null)
          for(Map.Entry<NOT_DROP_REASON, Integer> entry : map.entrySet()) {
            Logger.minor(this, ""+entry.getKey()+" : "+entry.getValue());
          }
      }
    }
    return null;
  }

  public void onSuccess(OpennetPeerNode pn) {
      LinkLengthClass distance = pn.linkLengthClass();
      LRUQueue<OpennetPeerNode> peersLRU = lruQueue(distance);
    synchronized(this) {
      for(ConnectionType type : ConnectionType.values())
        successCount.put(type, successCount.get(type)+1);
      if(peersLRU.contains(pn)) {
        peersLRU.push(pn);
        if(logMINOR) Logger.minor(this, "Opennet peer "+pn+" promoted to top of LRU because of successful request");
        return;
      } else {
        if(logMINOR) Logger.minor(this, "Success on opennet peer which isn't in the LRU!: "+pn, new Exception("debug"));
        // Re-add it: nasty race condition when we have few peers
      }
    }
    if(!wantPeer(pn, false, false, false, ConnectionType.RECONNECT, distance)) // Start at top as it just succeeded
      node.peers.disconnectAndRemove(pn, true, false, true);
  }

  public void onRemove(OpennetPeerNode pn) {
    long now = System.currentTimeMillis();
        LRUQueue<OpennetPeerNode> peersLRU = lruQueue(pn);
    synchronized (this) {
      peersLRU.remove(pn);
      if(pn.isDroppable(true) && !pn.grabWasDropped()) {
        if(logMINOR) Logger.minor(this, "onRemove() for "+pn);
        if(pn.timeLastConnected(now) > 0) {
          // Don't even add it if it never connected.
          oldPeers.push(pn);
          while (oldPeers.size() > MAX_OLD_PEERS)
            oldPeers.pop();
        }
      }
    }
  }

  synchronized OpennetPeerNode[] getOldPeers() {
    return oldPeers.toArrayOrdered(new OpennetPeerNode[oldPeers.size()]);
  }

  synchronized OpennetPeerNode[] getUnsortedOldPeers() {
    return oldPeers.toArray(new OpennetPeerNode[oldPeers.size()]);
  }

  /**
   * Add an old opennet node - a node which might try to reconnect, and which we should accept
   * if we are desperate.
   * @param pn The node to add to the old opennet nodes LRU.
   */
  synchronized void addOldOpennetNode(OpennetPeerNode pn) {
    oldPeers.push(pn);
  }

  final String getOldPeersFilename() {
    return node.nodeDir().file("openpeers-old-"+crypto.portNumber).toString();
  }

  synchronized int countOldOpennetPeers() {
    return oldPeers.size();
  }

  OpennetPeerNode randomOldOpennetNode() {
    OpennetPeerNode[] nodes = getUnsortedOldPeers();
    if(nodes.length == 0) return null;
    return nodes[node.random.nextInt(nodes.length)];
  }

  public synchronized void purgeOldOpennetPeer(OpennetPeerNode source) {
    oldPeers.remove(source);
  }

  public int getNumberOfConnectedPeersToAimIncludingDarknet() {
    int max = node.getMaxOpennetPeers();
    if(ENABLE_PEERS_PER_KB_OUTPUT) {
      int obwLimit = node.getOutputBandwidthLimit();
      int targetPeers = (int)Math.round(Math.min(MAX_PEERS_FOR_SCALING, Math.sqrt(obwLimit * SCALING_CONSTANT / 1000.0)));
      if(targetPeers < MIN_PEERS_FOR_SCALING)
        targetPeers = MIN_PEERS_FOR_SCALING;
      if(max > targetPeers) max = targetPeers; // Allow user to reduce it.
    }
    return max;
  }

  /** Get the target number of opennet peers. Do not call while holding locks. */
  public int getNumberOfConnectedPeersToAim(LinkLengthClass distance) {
      if(distance == null) throw new IllegalArgumentException();
      int target = getNumberOfConnectedPeersToAim();
      return distance.getTargetPeers(target);
  }
 
  public int getNumberOfConnectedPeersToAim() {
    int max = getNumberOfConnectedPeersToAimIncludingDarknet();
    return max - node.peers.countConnectedDarknetPeers();
  }

  public void sendOpennetRef(boolean isReply, long uid, PeerNode peer, byte[] noderef, ByteCounter ctr) throws NotConnectedException {
    sendOpennetRef(isReply, uid, peer, noderef, ctr, null);
  }
 
  /**
   * Send our opennet noderef to a node.
   * @param isReply If true, send an FNPOpennetConnectReply, else send an FNPOpennetConnectDestination.
   * @param uid The unique ID of the request chain involved.
   * @param peer The node to send the noderef to. Not necessarily an OpennetPeerNode, as path
   * folding and possibly announcement can pass through darknet.
   * @param cs The full compressed noderef to send.
   * @throws NotConnectedException If the peer becomes disconnected while we are trying to send the noderef.
   */
  public boolean sendOpennetRef(boolean isReply, long uid, PeerNode peer, byte[] noderef, ByteCounter ctr, AllSentCallback cb) throws NotConnectedException {
    byte[] padded = new byte[paddedSize(noderef.length)];
    if(noderef.length > padded.length) {
      Logger.error(this, "Noderef too big: "+noderef.length+" bytes");
      return false;
    }
    System.arraycopy(noderef, 0, padded, 0, noderef.length);
    Util.randomBytes(node.fastWeakRandom, padded, noderef.length, padded.length-noderef.length);
    long xferUID = node.random.nextLong();
    Message msg2 = isReply ? DMT.createFNPOpennetConnectReplyNew(uid, xferUID, noderef.length, padded.length) :
      DMT.createFNPOpennetConnectDestinationNew(uid, xferUID, noderef.length, padded.length);
    peer.sendAsync(msg2, null, ctr);
    return innerSendOpennetRef(xferUID, padded, peer, ctr, cb);
  }

  /**
   * Just the actual transfer.
   * @param xferUID The transfer UID
   * @param padded The length of the data to transfer.
   * @param peer The peer to send it to.
   * @param cb
   * @throws NotConnectedException If the peer is not connected, or we lose the connection to the peer,
   * or it restarts.
   */
  private boolean innerSendOpennetRef(long xferUID, byte[] padded, PeerNode peer, ByteCounter ctr, AllSentCallback cb) throws NotConnectedException {
    ByteArrayRandomAccessBuffer raf = new ByteArrayRandomAccessBuffer(padded);
    raf.setReadOnly();
    PartiallyReceivedBulk prb =
      new PartiallyReceivedBulk(node.usm, padded.length, Node.PACKET_SIZE, raf, true);
    try {
      BulkTransmitter bt =
        new BulkTransmitter(prb, peer, xferUID, true, ctr, true, cb);
      return bt.send();
    } catch (DisconnectedException e) {
      throw new NotConnectedException(e);
    }
  }

  public long startSendAnnouncementRequest(long uid, PeerNode peer, byte[] noderef, ByteCounter ctr,
      double target, short htl) throws NotConnectedException {
    long xferUID = node.random.nextLong();
    Message msg = DMT.createFNPOpennetAnnounceRequest(uid, xferUID, noderef.length,
        paddedSize(noderef.length), target, htl);
    peer.sendAsync(msg, null, ctr);
    return xferUID;
  }

  public void finishSentAnnouncementRequest(PeerNode peer, byte[] noderef, ByteCounter ctr,
      long xferUID) throws NotConnectedException {
    byte[] padded = new byte[paddedSize(noderef.length)];
    System.arraycopy(noderef, 0, padded, 0, noderef.length);
    Util.randomBytes(node.fastWeakRandom, padded, noderef.length, padded.length-noderef.length);
    innerSendOpennetRef(xferUID, padded, peer, ctr, null);
  }

  private int paddedSize(int length) {
    if(length < PADDED_NODEREF_SIZE) return PADDED_NODEREF_SIZE;
    Logger.normal(this, "Large noderef: "+length);
    if(length > MAX_OPENNET_NODEREF_LENGTH)
      throw new IllegalArgumentException("Too big noderef: "+length+" limit is "+MAX_OPENNET_NODEREF_LENGTH);
    return ((length >>> 10) + ((length & 1023) == 0 ? 0 : 1)) << 10;
  }

  public void sendAnnouncementReply(long uid, PeerNode peer, byte[] noderef, ByteCounter ctr)
  throws NotConnectedException {
    byte[] padded = new byte[PADDED_NODEREF_SIZE];
    if(noderef.length > padded.length) {
      Logger.error(this, "Noderef too big: "+noderef.length+" bytes");
      return;
    }
    System.arraycopy(noderef, 0, padded, 0, noderef.length);
    long xferUID = node.random.nextLong();
    Message msg = DMT.createFNPOpennetAnnounceReply(uid, xferUID, noderef.length,
        padded.length);
    peer.sendAsync(msg, null, ctr);
    innerSendOpennetRef(xferUID, padded, peer, ctr, null);
  }

  interface NoderefCallback {
    /** Got a noderef. */
    void gotNoderef(byte[] noderef);
    /** Timed out waiting for a noderef. */
    void timedOut();
    /** Got an ack - didn't timeout but there won't be a noderef.
     * @param timedOutMessage */
    void acked(boolean timedOutMessage);
  }
 
  private static class SyncNoderefCallback implements NoderefCallback {

    byte[] returned;
    boolean finished;
    boolean timedOut;
   
    @Override
    public synchronized void timedOut() {
      timedOut = true;
      finished = true;
      notifyAll();
    }
   
    @Override
    public void acked(boolean timedOutMessage) {
      gotNoderef(null);
    }
   
    @Override
    public synchronized void gotNoderef(byte[] noderef) {
      returned = noderef;
      finished = true;
      notifyAll();
    }
   
    public synchronized byte[] waitForResult() throws WaitedTooLongForOpennetNoderefException {
      while(!finished)
        try {
          wait();
        } catch (InterruptedException e) {
          // Ignore
        }
      if(timedOut) throw new WaitedTooLongForOpennetNoderefException();
      return returned;
    }
   
  }
 
  @SuppressWarnings("serial")
  static class WaitedTooLongForOpennetNoderefException extends Exception {
   
  }
 
  /**
   * Wait for an opennet noderef.
   * @param isReply If true, wait for an FNPOpennetConnectReply[New], if false wait for an FNPOpennetConnectDestination[New].
   * @param uid The UID of the parent request.
   * @return An opennet noderef.
   */
  public static byte[] waitForOpennetNoderef(boolean isReply, PeerNode source, long uid, ByteCounter ctr, Node node) throws WaitedTooLongForOpennetNoderefException {
    SyncNoderefCallback cb = new SyncNoderefCallback();
    if(logMINOR) Logger.minor(OpennetManager.class, "Waiting for opennet noderef on "+uid+" from "+source+" reply="+isReply);
    waitForOpennetNoderef(isReply, source, uid, ctr, cb, node);
    return cb.waitForResult();
  }
 
  public static void waitForOpennetNoderef(final boolean isReply, final PeerNode source, final long uid, final ByteCounter ctr, final NoderefCallback callback, final Node node) {
    // FIXME remove back compat code
    MessageFilter mf =
      MessageFilter.create().setSource(source).setField(DMT.UID, uid).
      setTimeout(RequestSender.OPENNET_TIMEOUT).
      setType(isReply ? DMT.FNPOpennetConnectReplyNew : DMT.FNPOpennetConnectDestinationNew);
    // Also waiting for an ack
    MessageFilter mfAck =
      MessageFilter.create().setSource(source).setField(DMT.UID, uid).
      setTimeout(RequestSender.OPENNET_TIMEOUT).setType(DMT.FNPOpennetCompletedAck);
    // Also waiting for an upstream timed out.
    MessageFilter mfAckTimeout =
      MessageFilter.create().setSource(source).setField(DMT.UID, uid).
      setTimeout(RequestSender.OPENNET_TIMEOUT).setType(DMT.FNPOpennetCompletedTimeout);
   
    mf = mfAck.or(mfAckTimeout.or(mf));
    try {
      node.usm.addAsyncFilter(mf, new SlowAsyncMessageFilterCallback() {
       
        boolean completed;

        @Override
        public void onMatched(Message msg) {
          if (msg.getSpec() == DMT.FNPOpennetCompletedAck ||
              msg.getSpec() == DMT.FNPOpennetCompletedTimeout) {
            synchronized(this) {
              if(completed) return;
              completed = true;
            }
            callback.acked(msg.getSpec() == DMT.FNPOpennetCompletedTimeout);
          } else {
            // Noderef bulk transfer
            long xferUID = msg.getLong(DMT.TRANSFER_UID);
            int paddedLength = msg.getInt(DMT.PADDED_LENGTH);
            int realLength = msg.getInt(DMT.NODEREF_LENGTH);
            complete(innerWaitForOpennetNoderef(xferUID, paddedLength, realLength, source, isReply, uid, false, ctr, node));
          }
        }

        @Override
        public boolean shouldTimeout() {
          return false;
        }

        @Override
        public void onTimeout() {
          synchronized(this) {
            if(completed) return;
            completed = true;
          }
          callback.timedOut();
        }

        @Override
        public void onDisconnect(PeerContext ctx) {
          complete(null);
        }

        @Override
        public void onRestarted(PeerContext ctx) {
          complete(null);
        }

        @Override
        public int getPriority() {
          return NativeThread.NORM_PRIORITY;
        }
       
        private void complete(byte[] buf) {
          synchronized(this) {
            if(completed) return;
            completed = true;
          }
          callback.gotNoderef(buf);
        }
       
      }, ctr);
    } catch (DisconnectedException e) {
      callback.gotNoderef(null);
    }
  }

  static byte[] innerWaitForOpennetNoderef(long xferUID, int paddedLength, int realLength, PeerNode source, boolean isReply, long uid, boolean sendReject, ByteCounter ctr, Node node) {
    byte[] buf = new byte[paddedLength];
    ByteArrayRandomAccessBuffer raf = new ByteArrayRandomAccessBuffer(buf);
    PartiallyReceivedBulk prb = new PartiallyReceivedBulk(node.usm, buf.length, Node.PACKET_SIZE, raf, false);
    BulkReceiver br = new BulkReceiver(prb, source, xferUID, ctr);
    if (logMINOR) {
      Logger.minor(OpennetManager.class, "Receiving noderef (reply="+isReply+") as bulk transfer for request uid "+uid+" with transfer "+xferUID+" from "+source);
    }
    if (!br.receive()) {
      if (source.isConnected()) {
        String msg = "Failed to receive noderef bulk transfer : "
          +RetrievalException.getErrString(prb.getAbortReason())+" : "
          +prb.getAbortDescription()+" from "+source;
        if (prb.getAbortReason() != RetrievalException.SENDER_DISCONNECTED) {
          Logger.warning(OpennetManager.class, msg);
        } else {
          Logger.normal(OpennetManager.class, msg);
        }
        if (sendReject) rejectRef(uid, source, DMT.NODEREF_REJECTED_TRANSFER_FAILED, ctr);
      }
      return null;
    }
    byte[] noderef = Arrays.copyOf(buf, realLength);
    return noderef;
  }

  public static void rejectRef(long uid, PeerNode source, int reason, ByteCounter ctr) {
    Message msg = DMT.createFNPOpennetNoderefRejected(uid, reason);
    try {
      source.sendAsync(msg, null, ctr);
    } catch (NotConnectedException e) {
      // Ignore
    }
  }

  public static SimpleFieldSet validateNoderef(byte[] noderef, int offset, int length, PeerNode from, boolean forceOpennetEnabled) {
      SimpleFieldSet ref;
    try {
      ref = PeerNode.compressedNoderefToFieldSet(noderef, 0, noderef.length);
    } catch (FSParseException e) {
      Logger.error(OpennetManager.class, "Invalid noderef: "+e, e);
      return null;
    }
    if(forceOpennetEnabled)
      ref.put("opennet", true);

    if(!OpennetPeerNode.validateRef(ref)) {
      Logger.error(OpennetManager.class, "Could not parse opennet noderef from "+from);
      return null;
    }

    if (ref != null) {
      String identity = ref.get("identity");
      if (identity != null) // N2N_MESSAGE_TYPE_DIFFNODEREF don't have identity
        registerKnownIdentity(identity);
    }
    return ref;
  }

  /** Do an announcement !!
   * @param target The location to announce to. In 0.7 we don't try to prevent nodes from choosing their
   * announcement location, because it is easy for them to get the location they want later on anyway,
   * and we can do a much more effective announcement this way. */
  public void announce(double target, AnnouncementCallback cb) {
    AnnounceSender sender = new AnnounceSender(target, this, node, cb, null);
    node.executor.execute(sender, "Announcement to "+target);
  }

  public long getCreationTime() {
    return creationTime;
  }


  private static final long MAX_AGE = DAYS.toMillis(7);
  private static final TimeSortedHashtable<String> knownIds = new TimeSortedHashtable<String>();

  private static void registerKnownIdentity(String d) {
    if (logMINOR)
      Logger.minor(OpennetManager.class, "Known Id: " + d);
    long now = System.currentTimeMillis();

    synchronized (knownIds) {
      if(logMINOR) Logger.minor(OpennetManager.class, "Adding Id " + d + " knownIds size " + knownIds.size());
      knownIds.push(d, now);
      if(logMINOR) Logger.minor(OpennetManager.class, "Added Id " + d + " knownIds size " + knownIds.size());
      knownIds.removeBefore(now - MAX_AGE);
      if(logMINOR) Logger.minor(OpennetManager.class, "Added and pruned location " + d + " knownIds size " + knownIds.size());
    }
    if(logMINOR) Logger.minor(OpennetManager.class, "Estimated opennet size(session): " + knownIds.size());
  }
    //Return the estimated network size based on locations seen after timestamp or for the whole session if -1
  public int getNetworkSizeEstimate(long timestamp) {
    return knownIds.countValuesAfter(timestamp);
  }

  public int getAnnouncementThreshold() {
    return announcer.getAnnouncementThreshold();
  }

  /** Notification that a peer was disconnected. Query the Announcer,
   * it may need to rerun. */
  public void onDisconnect(PeerNode node2) {
    if(announcer != null)
      announcer.maybeSendAnnouncementOffThread();
  }

  public void drawOpennetStatsBox(HTMLNode box) {
    HTMLNode table = box.addChild("table", "border", "0");
    HTMLNode row = table.addChild("tr");

    row.addChild("th");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("th", type.name());
    }

    row = table.addChild("tr");
    row.addChild("td", "Connection attempts");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("td", Long.toString(connectionAttempts.get(type)));
    }

    row = table.addChild("tr");
    row.addChild("td", "Connections accepted");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("td", Long.toString(connectionAttemptsAdded.get(type)));
    }

    row = table.addChild("tr");
    row.addChild("td", "Accepted (free slots)");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("td", Long.toString(connectionAttemptsAddedPlentySpace.get(type)));
    }

    row = table.addChild("tr");
    row.addChild("td", "Rejected (per-type grace periods)");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("td", Long.toString(connectionAttemptsRejectedByPerTypeEnforcement.get(type)));
    }

    row = table.addChild("tr");
    row.addChild("td", "Rejected (no droppable peers)");
    for(ConnectionType type : ConnectionType.values()) {
      row.addChild("td", Long.toString(connectionAttemptsRejectedNoPeersDroppable.get(type)));
    }

  }

  public boolean waitingForUpdater() {
    return announcer.isWaitingForUpdater();
  }
 
  public void reannounce() {
    announcer.reannounce();
  }

  public void drawSeedStatsBox(HTMLNode content) {
    seedTracker.drawSeedStats(content);
  }

  /** Called when a connection completes. If it's an opennet peer, we may need to drop a peer
   * to make space. If it's a darknet peer, the connection limit for opennet peers may have
   * decreased so again we may need to drop a peer. */
    public void onConnectedPeer(PeerNode pn) {
        if(pn instanceof OpennetPeerNode) {
            dropExcessPeers(((OpennetPeerNode)pn).linkLengthClass());
        } else {
            // The peer count target may have decreased, so we may need to drop an opennet peer.
            dropAllExcessPeers();
        }
    }

}
TOP

Related Classes of freenet.node.OpennetManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.