Package freenet.node.probe

Source Code of freenet.node.probe.Probe$ResultRelay

package freenet.node.probe;

import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import freenet.config.InvalidConfigValueException;
import freenet.config.NodeNeedRestartException;
import freenet.config.SubConfig;
import freenet.io.comm.AsyncMessageFilterCallback;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
import freenet.io.comm.Message;
import freenet.io.comm.MessageFilter;
import freenet.io.comm.NotConnectedException;
import freenet.io.comm.PeerContext;
import freenet.node.Location;
import freenet.node.Node;
import freenet.node.OpennetManager;
import freenet.node.PeerNode;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.api.BooleanCallback;
import freenet.support.api.LongCallback;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

/**
* Handles starting, routing, and responding to Metropolis-Hastings corrected probes.
*
* Possible future additions to these probes' results include:
* <ul>
* <li>Starting a regular request for a key.</li>
* <li>Success rates for remote requests by HTL; perhaps over some larger amount of time than the past hour.</li>
* </ul>
*
* @see freenet.node.probe Explanation of Metropolis-Hastings correction
*/
public class Probe implements ByteCounter {

  private static volatile boolean logMINOR;
  private static volatile boolean logDEBUG;
  private static volatile boolean logWARNING;

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

  private final static String SOURCE_DISCONNECT = "Previous step in probe chain no longer connected.";

  /**
   * Maximum hopsToLive value to clamp requests to.
   */
  public static final byte MAX_HTL = 70;

  /**
   * Maximum number of forwarding attempts to make before failing with DISCONNECTED.
   */
  public static final int MAX_SEND_ATTEMPTS = 50;

  /**
   * Probability of HTL decrement at HTL = 1.
   */
  public static final float DECREMENT_PROBABILITY = 0.2f;

  /**
   * In ms, per HTL above HTL = 1.
   */
  public static final long TIMEOUT_PER_HTL = SECONDS.toMillis(3);

  /**
   * In ms, to account for probabilistic decrement at HTL = 1.
   */
  public static final long TIMEOUT_HTL1 = (long) (TIMEOUT_PER_HTL / DECREMENT_PROBABILITY);

  /**
   * To make the timing less obvious when a node responds with a local result instead of forwarding at
   * HTL = 1, delay for a number of milliseconds, specifically an exponential distribution with this constant as
   * its mean.
   */
  public static final long WAIT_BASE = SECONDS.toMillis(1);

  /**
   * Maximum number of milliseconds to wait before sending a response.
   */
  public static final long WAIT_MAX = SECONDS.toMillis(2);

  /**
   * Maximum number of probes accepted from a single peer in the past minute.
   */
  public final int COUNTER_MAX_PEER = 10;

  /**
   * Maximum number of probes started locally in the past minute. This is the maximum conceivable value; the
   * probes should be used with a number of requests per minute closer to the per-peer limit times the minimum
   * expected number of peers. Around this value, and certainly above it, remote OVERLOADs may start coming
   * in, which are not useful. The Metropolis-Hastings correction makes behavior potentially inconsistent, so
   * keeping an eye on remote OVERLOADs is wise.
   */
  public final int COUNTER_MAX_LOCAL = COUNTER_MAX_PEER * OpennetManager.MAX_PEERS_FOR_SCALING;

  /**
   * Number of accepted probes in the last minute, keyed by peer.
   */
  private final Map<PeerNode, Counter> accepted;

  private final Node node;

  private final Timer timer;

  //Whether to respond to different types of probe requests.
  private volatile boolean respondBandwidth;
  private volatile boolean respondBuild;
  private volatile boolean respondIdentifier;
  private volatile boolean respondLinkLengths;
  private volatile boolean respondLocation;
  private volatile boolean respondStoreSize;
  private volatile boolean respondUptime;
  private volatile boolean respondRejectStats;
  private volatile boolean respondOverallBulkOutputCapacityUsage;

  private volatile long probeIdentifier;

  /**
   * Applies multiplicative Gaussian noise of mean 1.0 and the specified sigma to the input value.
   * @param input Value to apply noise to.
   * @param sigma Proportion change at one standard deviation.
   * @return Value +/- Gaussian percentage.
   */
  private final double randomNoise(final double input, final double sigma) {
    return node.nodeStats.randomNoise(input, sigma);
  }

  /**
   * Counts as probe request transfer.
   * @param bytes Bytes received.
   */
  @Override
  public void sentBytes(int bytes) {
    node.nodeStats.probeRequestCtr.sentBytes(bytes);
  }

  /**
   * Counts as probe request transfer.
   * @param bytes Bytes received.
   */
  @Override
  public void receivedBytes(int bytes) {
    node.nodeStats.probeRequestCtr.receivedBytes(bytes);
  }

  /**
   * No payload in probes.
   * @param bytes Ignored.
   */
  @Override
  public void sentPayload(int bytes) {}

  public Probe(final Node node) {
    this.node = node;
    this.accepted = Collections.synchronizedMap(new HashMap<PeerNode, Counter>());
    this.timer = new Timer(true);

    int sortOrder = 0;
    final SubConfig nodeConfig = node.config.get("node");

    nodeConfig.register("probeBandwidth", true, sortOrder++, true, true, "Node.probeBandwidthShort",
      "Node.probeBandwidthLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondBandwidth;
      }

      @Override
      public void set(Boolean val) {
        respondBandwidth = val;
      }
    });
    respondBandwidth = nodeConfig.getBoolean("probeBandwidth");
    nodeConfig.register("probeBuild", true, sortOrder++, true, true, "Node.probeBuildShort",
      "Node.probeBuildLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondBuild;
      }

      @Override
      public void set(Boolean val) {
        respondBuild = val;
      }
    });
    respondBuild = nodeConfig.getBoolean("probeBuild");
    nodeConfig.register("probeIdentifier", true, sortOrder++, true, true,
      "Node.probeRespondIdentifierShort", "Node.probeRespondIdentifierLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondIdentifier;
      }

      @Override
      public void set(Boolean val) {
        respondIdentifier = val;
      }
    });
    respondIdentifier = nodeConfig.getBoolean("probeIdentifier");
    nodeConfig.register("probeLinkLengths", true, sortOrder++, true, true, "Node.probeLinkLengthsShort",
      "Node.probeLinkLengthsLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondLinkLengths;
      }

      @Override
      public void set(Boolean val) {
        respondLinkLengths = val;
      }
    });
    respondLinkLengths = nodeConfig.getBoolean("probeLinkLengths");
    nodeConfig.register("probeLocation", true, sortOrder++, true, true, "Node.probeLocationShort",
      "Node.probeLocationLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondLocation;
      }

      @Override
      public void set(Boolean val) {
        respondLocation = val;
      }
    });
    respondLocation = nodeConfig.getBoolean("probeLocation");
    nodeConfig.register("probeStoreSize", true, sortOrder++, true, true, "Node.probeStoreSizeShort",
      "Node.probeStoreSizeLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondStoreSize;
      }

      @Override
      public void set(Boolean val) {
        respondStoreSize = val;
      }
    });
    respondStoreSize = nodeConfig.getBoolean("probeStoreSize");
    nodeConfig.register("probeUptime", true, sortOrder++, true, true, "Node.probeUptimeShort",
      "Node.probeUptimeLong", new BooleanCallback() {
      @Override
      public Boolean get() {
        return respondUptime;
      }

      @Override
      public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException {
        respondUptime = val;
      }
    });
    respondUptime = nodeConfig.getBoolean("probeUptime");
    nodeConfig.register("probeRejectStats", true, sortOrder++, true, true, "Node.probeRejectStatsShort",
        "Node.probeRejectStatsLong", new BooleanCallback() {
        @Override
        public Boolean get() {
          return respondRejectStats;
        }

        @Override
        public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException {
          respondRejectStats = val;
        }
      });
      respondRejectStats = nodeConfig.getBoolean("probeRejectStats");
     
    nodeConfig.register("probeOverallBulkOutputCapacityUsage", true, sortOrder++, true, true, "Node.respondOverallBulkOutputCapacityUsage",
        "Node.respondOverallBulkOutputCapacityUsageLong", new BooleanCallback() {

          @Override
          public Boolean get() {
            return respondOverallBulkOutputCapacityUsage;
          }

          @Override
          public void set(Boolean val)
              throws InvalidConfigValueException,
              NodeNeedRestartException {
            respondOverallBulkOutputCapacityUsage = val;
          }
     
    });
    respondOverallBulkOutputCapacityUsage = nodeConfig.getBoolean("probeOverallBulkOutputCapacityUsage");

    nodeConfig.register("identifier", -1, sortOrder++, true, true, "Node.probeIdentifierShort",
      "Node.probeIdentifierLong", new LongCallback() {
      @Override
      public Long get() {
        return probeIdentifier;
      }

      @Override
      public void set(Long val) {
        probeIdentifier = val;
        //-1 is reserved for picking a random value; don't pick it randomly.
        while(probeIdentifier == -1) probeIdentifier = node.random.nextLong();
      }
    }, false);
    probeIdentifier = nodeConfig.getLong("identifier");

    /*
     * set() is not used when setting up an option with its default value, so do so manually to avoid using
     * an identifier of -1.
     */
    try {
      if(probeIdentifier == -1) {
        nodeConfig.getOption("identifier").setValue("-1");
        //TODO: Store config here as it has changed?
        node.config.store();
      }
    } catch (InvalidConfigValueException e) {
      Logger.error(Probe.class, "node.identifier set() unexpectedly threw.", e);
    } catch (NodeNeedRestartException e) {
      Logger.error(Probe.class, "node.identifier set() unexpectedly threw.", e);
    }
  }

  /**
   * Sends an outgoing probe request.
   * @param htl htl for this outgoing probe: should be [1, MAX_HTL]
   * @param listener will be called with results.
   * @see Listener
   */
  public void start(final byte htl, final long uid, final Type type, final Listener listener) {
    request(DMT.createProbeRequest(htl, uid, type), null, listener);
  }

  /**
   * Processes an incoming probe request; relays results back to source.
   * If the probe has a positive HTL, routes with MH correction and probabilistically decrements HTL.
   * If the probe comes to have an HTL of zero: (an incoming HTL of less than one is discarded.)
   * Returns (as node settings allow) exactly one of:
   * <ul>
   *         <li>unique identifier and integer 7-day uptime percentage</li>
   *         <li>uptime: 48-hour percentage or 7-day percentage</li>
   *         <li>output bandwidth</li>
   *         <li>store size</li>
   *         <li>link lengths</li>
   *         <li>location</li>
   *         <li>build number</li>
   * </ul>
   *
   * @param message probe request, containing HTL
   */
  public void request(Message message, PeerNode source) {
    request(message, source, new ResultRelay(source, message.getLong(DMT.UID)));
  }

  /**
   * Processes a probe request, calling the listener with any results.
   * @param source node from which the probe request was received. If null, it is considered to have been sent
   * by the local node.
   * @param listener listener for probe response.
   */
  private void request(final Message message, final PeerNode source, final Listener listener) {
    final Long uid = message.getLong(DMT.UID);
    final byte typeCode = message.getByte(DMT.TYPE);
    final Type type;
    if (Type.isValid(typeCode)) {
      type = Type.valueOf(typeCode);
      if (logDEBUG) Logger.debug(Probe.class, "Probe type is " + type.name() + ".");
    } else {
      if (logMINOR) Logger.minor(Probe.class, "Invalid probe type " + typeCode + ".");
      listener.onError(Error.UNRECOGNIZED_TYPE, typeCode, true);
      return;
    }
    byte htl = message.getByte(DMT.HTL);
    if (htl < 1) {
      if (logWARNING) {
        Logger.warning(Probe.class, "Received out-of-bounds HTL of " + htl + " from " +
            source.getIdentityString() + " (" + source.userToString() + "); discarding.");
      }
      return;
    } else if (htl > MAX_HTL) {
      if (logMINOR) {
        Logger.minor(Probe.class, "Received out-of-bounds HTL of " + htl + " from " +
            source.getIdentityString() + " (" + source.userToString() + "); interpreting as " +
            MAX_HTL + ".");
      }
      htl = MAX_HTL;
    }
    boolean availableSlot = true;
    TimerTask task = null;
    //Allocate one of this peer's probe request slots for 60 seconds; send an overload if none are available.
    synchronized (accepted) {
      //If no counter exists for the current source, add one.
      if (!accepted.containsKey(source)) {
        // Null source is started locally.
        accepted.put(source, new Counter(source == null ? COUNTER_MAX_LOCAL : COUNTER_MAX_PEER));
      }
      final Counter counter = accepted.get(source);
      if (counter.value() == counter.maxAccepted) {
        //Set a flag instead of sending inside the lock.
        availableSlot = false;
      } else {
        //There's a free slot; increment the counter.
        counter.increment();
        task = new TimerTask() {
          @Override
          public void run() {
            synchronized (accepted) {
              counter.decrement();
              /* Once the counter hits zero, there's no reason to keep it around as it
               * can just be recreated when this peer sends another probe request
               * without changing behavior. To do otherwise would accumulate counters
               * at zero over time.
               */
              if (counter.value() == 0) {
                accepted.remove(source);
              }
            }
          }
        };
      }
    }
    if (!availableSlot) {
      //Send an overload error back to the source.
      if (logDEBUG) Logger.debug(Probe.class, "Already accepted maximum number of probes; rejecting incoming.");
      listener.onError(Error.OVERLOAD, null, true);
      return;
    }
    //One-minute window on acceptance; free up this probe slot in 60 seconds.
    timer.schedule(task, MINUTES.toMillis(1));

    /*
     * Route to a peer, using Metropolis-Hastings correction and ignoring backoff to get a more uniform
     * endpoint distribution. HTL is decremented before routing so that it's possible to respond locally without
     * attempting to route first. Send a local response if HTL is zero now or becomes zero while trying to route.
     * During routing HTL decrements if a candidate is rejected by the Metropolis-Hastings correction.
     */
    htl = probabilisticDecrement(htl);
    if (htl == 0 || !route(type, uid, htl, listener)) {
      long wait = WAIT_MAX;
      while (wait >= WAIT_MAX) wait = (long)(-Math.log(node.random.nextDouble()) * WAIT_BASE / Math.E);
      timer.schedule(new TimerTask() {
        @Override
        public void run() {
          respond(type, listener);
        }
      }, wait);
    }
  }

  /**
   * Attempts to route the message to a peer. If the maximum number of send attempts is exceeded, fails with the
   * error CANNOT_FORWARD.
   * @return True if no further action needed; false if HTL decremented to zero and a local response is needed.
   */
  private boolean route(final Type type, final long uid, byte htl, final Listener listener) {
    //Recreate the request so that any sub-messages or unintended fields are not forwarded.
    final Message message = DMT.createProbeRequest(htl, uid, type);
    PeerNode[] peers;
    //Degree of the local node.
    int degree;
    PeerNode candidate;
    /*
     * Attempt to forward until success or until reaching the send attempt limit.
     */
    for (int sendAttempts = 0; sendAttempts < MAX_SEND_ATTEMPTS; sendAttempts++) {
      peers = node.getConnectedPeers();
      degree = peers.length;
      //Can't handle a probe request if not connected to peers.
      if (degree == 0 ) {
        if (logMINOR) {
          Logger.minor(Probe.class, "Aborting probe request: no connections.");
        }

        /*
         * If this is a locally-started request, not a relayed one, give an error.
         * Otherwise, in this case there's nowhere to send the error.
         */
        listener.onError(Error.DISCONNECTED, null, true);
        return true;
      }

      candidate = peers[node.random.nextInt(degree)];

      if (candidate.isConnected()) {
        //acceptProbability is the MH correction.
        float acceptProbability;
        int candidateDegree = candidate.getDegree();
        /* Candidate's degree is unknown; fall back to random walk by accepting this candidate
         * regardless of its degree.
         */
        if (candidateDegree == 0) acceptProbability = 1.0f;
        else acceptProbability = (float)degree / candidateDegree;

        if (logDEBUG) Logger.debug(Probe.class, "acceptProbability is " + acceptProbability);
        if (node.random.nextFloat() < acceptProbability) {
          if (logDEBUG) Logger.debug(Probe.class, "Accepted candidate.");
          //Filter for response to this probe with requested result type.
          final MessageFilter filter = createResponseFilter(type, candidate, uid, htl);
          message.set(DMT.HTL, htl);
          try {
            node.getUSM().addAsyncFilter(filter, new ResultListener(listener), this);
            if (logDEBUG) Logger.debug(Probe.class, "Sending.");
            candidate.sendAsync(message, null, this);
            return true;
          } catch (NotConnectedException e) {
            if (logMINOR) Logger.minor(Probe.class, "Peer became disconnected between check and send attempt.", e);
            // Peer no longer connected - sending was not successful. Try again.
          } catch (DisconnectedException e) {
            if (logMINOR) Logger.minor(Probe.class, "Peer became disconnected while attempting to add filter.", e);
            // Peer no longer connected - cannot send. Try again.
          }
        } else {
          /*
           * Metropolis-Hastings correction rejected - decrement HTL so that it can run out depending on
           * relative degrees.
           */
          htl = probabilisticDecrement(htl);

          if (htl == 0) return false;
        }
      } else {
        if (logMINOR) Logger.minor(Probe.class, "Peer in connectedPeers was not connected.", new Exception());
      }
    }

    // Send attempt limit reached.
    if (logWARNING) {
      Logger.warning(Probe.class, "Aborting probe request: send attempt limit reached.");
    }

    listener.onError(Error.CANNOT_FORWARD, null, true);
    return true;
  }

  /**
   * @param type probe result type requested.
   * @param candidate node to filter for response from.
   * @param uid probe request uid, also to be used in any result.
   * @param htl current probe HTL; used to calculate timeout.
   * @return filter for the requested result type, probe error, and probe refusal.
   */
  private static MessageFilter createResponseFilter(final Type type, final PeerNode candidate, final long uid, final byte htl) {
    final long timeout = (htl - 1) * TIMEOUT_PER_HTL + TIMEOUT_HTL1;
    final MessageFilter filter = createFilter(candidate, uid, timeout);

    switch (type) {
      case BANDWIDTH: filter.setType(DMT.ProbeBandwidth); break;
      case BUILD: filter.setType(DMT.ProbeBuild); break;
      case IDENTIFIER: filter.setType(DMT.ProbeIdentifier); break;
      case LINK_LENGTHS: filter.setType(DMT.ProbeLinkLengths); break;
      case LOCATION: filter.setType(DMT.ProbeLocation); break;
      case STORE_SIZE: filter.setType(DMT.ProbeStoreSize); break;
      case UPTIME_48H:
      case UPTIME_7D: filter.setType(DMT.ProbeUptime); break;
      case REJECT_STATS: filter.setType(DMT.ProbeRejectStats); break;
      case OVERALL_BULK_OUTPUT_CAPACITY_USAGE: filter.setType(DMT.ProbeOverallBulkOutputCapacityUsage); break;
      default: throw new UnsupportedOperationException("Missing filter for " + type.name());
    }

    //Refusal or an error should also be listened for so it can be relayed.
    filter.or(createFilter(candidate, uid, timeout).setType(DMT.ProbeRefused)
          .or(createFilter(candidate, uid, timeout).setType(DMT.ProbeError)));

    return filter;
  }

  private static MessageFilter createFilter(final PeerNode source, final long uid, final long timeout) {
    return MessageFilter.create().setSource(source).setField(DMT.UID, uid).setTimeout(timeout);
  }

  /**
   * Depending on node settings, sends a message to source containing either a refusal or the requested result.
   */
  private void respond(final Type type, final Listener listener) {

    if (!respondTo(type)) {
      listener.onRefused();
      return;
    }

    /*
     * This adds noise to the results to make information less identifiable. The goal is making it difficult
     * to determine which value a node actually has; that any given value could mean a small range of common
     * values. Different result types have different sigma values such that one sigma contains multiple
     * reasonable values.
     */
    switch (type) {
    case BANDWIDTH:
      /*
       * 5% noise:
       * Reasonable output bandwidth limit is 20 KiB and people are likely to set limits in increments
       * of 1 KiB. 1 KiB / 20 KiB = 0.05 sigma.
       * 1,024 (2^10) bytes per KiB.
       */
      listener.onOutputBandwidth((float)randomNoise((double)node.getOutputBandwidthLimit()/(1 << 10), 0.05));
      break;
    case BUILD:
      listener.onBuild(node.nodeUpdater.getMainVersion());
      break;
    case IDENTIFIER:
      /*
       * 5% noise:
       * Reasonable uptime percentage is at least ~40 hours a week, or ~20%. This uptime is
       * quantized so only something above a full percentage point (0.01 * 168 hours = 1.68 hours) of
       * change will be guaranteed (from a percentage with a decimal component close to zero) to be
       * reflected. 1% / 20% = 0.05 sigma.
       *
       * 7-day uptime with random noise, then quantized. Quantization is to make it very, very
       * difficult to get useful information out of any given result because it is included with an
       * identifier,
       */
      long percent = Math.round(randomNoise(100*node.uptime.getUptimeWeek(), 0.05));
      //Clamp to byte.
      if (percent > Byte.MAX_VALUE) percent = Byte.MAX_VALUE;
      else if (percent < Byte.MIN_VALUE) percent = Byte.MIN_VALUE;
      listener.onIdentifier(probeIdentifier, (byte)percent);
      break;
    case LINK_LENGTHS:
      PeerNode[] peers = node.getConnectedPeers();
      float[] linkLengths = new float[peers.length];
      int i = 0;
      /*
       * 1% noise:
       * Link lengths are in the range [0.0, 0.5], and any change is enough to make the
       * match not exact between locations. Taking as an example a link length of 0.2. and with the
       * assumption that a change of 0.002 is enough to make it still useful for statistics but not
       * useful for identification, 0.002 change / 0.2 link length = 0.01 sigma.
       */
      double myLoc = node.getLocation();
      for (PeerNode peer : peers) {
        double peerLoc = peer.getLocation();
        if (Location.isValid(peerLoc)) {
          linkLengths[i++] = (float)randomNoise(Location.distance(myLoc, peerLoc), 0.01);
        }
      }
      linkLengths = java.util.Arrays.copyOf(linkLengths, i);
      java.util.Arrays.sort(linkLengths);
      listener.onLinkLengths(linkLengths);
      break;
    case LOCATION:
      listener.onLocation((float)node.getLocation());
      break;
    case STORE_SIZE:
      /*
       * 5% noise:
       * Reasonable datastore size is 20 GiB, and size is likely set in, at most, increments of 1 GiB.
       * 1 GiB / 20 GiB = 0.05 sigma.
       * 1,073,741,824 bytes (2^30) per GiB.
       */
      listener.onStoreSize((float)randomNoise((double)node.getStoreSize()/(1 << 30), 0.05));
      break;
    case UPTIME_48H:
      /*
       * 8% noise:
       * Continuing with the assumption that reasonable weekly uptime is around 40 hours, this allows
       * for 6 hours per day, 12 hours per 48 hours, or 25%. A half-hour seems a sufficient amount of
       * ambiguity, so 0.5 hours / 48 hours ~= 1%, and 1% / 25% = 0.04 sigma.
       */
      listener.onUptime((float)randomNoise(100*node.uptime.getUptime(), 0.04));
      break;
    case UPTIME_7D:
      /*
       * 2.4% noise:
       * As a 168-hour uptime covers a longer period 1 hour of ambiguity seems sufficient.
       * 1 hour / 168 hours ~= 0.6%, and 0.6% / 20% = 0.03 sigma.
       */
      listener.onUptime((float)randomNoise(100*node.uptime.getUptimeWeek(), 0.03));
      break;
    case REJECT_STATS:
      byte[] stats = node.nodeStats.getNoisyRejectStats();
      listener.onRejectStats(stats);
      break;
    case OVERALL_BULK_OUTPUT_CAPACITY_USAGE:
      byte bandwidthClass =
        DMT.bandwidthClassForCapacityUsage(node.getOutputBandwidthLimit());
      listener.onOverallBulkOutputCapacity(bandwidthClass,
          (float)randomNoise(node.nodeStats.getBandwidthLiabilityUsage(), 0.1));
      break;
    default:
      throw new UnsupportedOperationException("Missing response for " + type.name());
    }
  }

  private boolean respondTo(Type type) {
    switch (type){
    case BANDWIDTH: return respondBandwidth;
    case BUILD: return respondBuild;
    case IDENTIFIER: return respondIdentifier;
    case LINK_LENGTHS: return respondLinkLengths;
    case LOCATION: return respondLocation;
    case STORE_SIZE: return respondStoreSize;
    case UPTIME_48H:
    case UPTIME_7D: return respondUptime;
    case REJECT_STATS: return respondRejectStats;
    case OVERALL_BULK_OUTPUT_CAPACITY_USAGE: return respondOverallBulkOutputCapacityUsage;
    default: throw new UnsupportedOperationException("Missing permissions check for " + type.name());
    }
  }

  /**
   * Decrements 20% of the time at HTL 1; otherwise always. This is to protect the responding node, whereas the
   * anonymity of the node which initiated the request is not a concern.
   * @param htl current HTL
   * @return new HTL
   */
  private byte probabilisticDecrement(byte htl) {
    assert htl > 0;
    if (htl == 1) {
      if (node.random.nextFloat() < DECREMENT_PROBABILITY) return 0;
      return 1;
    }
    return (byte)(htl - 1);
  }

  /**
   * Filter listener which determines the type of result and calls the appropriate probe listener method.
   */
  private class ResultListener implements AsyncMessageFilterCallback {

    private final Listener listener;

    /**
     * @param listener to call appropriate methods for events such as matched messages or timeout.
     */
    public ResultListener(Listener listener) {
      this.listener = listener;
    }

    @Override
    public void onDisconnect(PeerContext context) {
      if (logDEBUG) Logger.debug(Probe.class, "Next node in chain disconnected.");
      listener.onError(Error.DISCONNECTED, null, true);
    }

    /**
     * Parses provided message and calls appropriate Probe.Listener method for the type of result.
     * @param message Probe result.
     */
    @Override
    public void onMatched(Message message) {
      if(logDEBUG) Logger.debug(Probe.class, "Matched " + message.getSpec().getName());
      if (message.getSpec().equals(DMT.ProbeBandwidth)) {
        listener.onOutputBandwidth(message.getFloat(DMT.OUTPUT_BANDWIDTH_UPPER_LIMIT));
      } else if (message.getSpec().equals(DMT.ProbeBuild)) {
        listener.onBuild(message.getInt(DMT.BUILD));
      } else if (message.getSpec().equals(DMT.ProbeIdentifier)) {
        listener.onIdentifier(message.getLong(DMT.PROBE_IDENTIFIER), message.getByte(DMT.UPTIME_PERCENT));
      } else if (message.getSpec().equals(DMT.ProbeLinkLengths)) {
        listener.onLinkLengths(message.getFloatArray(DMT.LINK_LENGTHS));
      } else if (message.getSpec().equals(DMT.ProbeLocation)) {
        listener.onLocation(message.getFloat(DMT.LOCATION));
      } else if (message.getSpec().equals(DMT.ProbeStoreSize)) {
        listener.onStoreSize(message.getFloat(DMT.STORE_SIZE));
      } else if (message.getSpec().equals(DMT.ProbeUptime)) {
        listener.onUptime(message.getFloat(DMT.UPTIME_PERCENT));
      } else if (message.getSpec().equals(DMT.ProbeRejectStats)) {
        listener.onRejectStats(message.getShortBufferBytes(DMT.REJECT_STATS));
      } else if (message.getSpec().equals(DMT.ProbeOverallBulkOutputCapacityUsage)) {
        listener.onOverallBulkOutputCapacity(message.getByte(DMT.OUTPUT_BANDWIDTH_CLASS), message.getFloat(DMT.CAPACITY_USAGE));
      } else if (message.getSpec().equals(DMT.ProbeError)) {
        final byte rawError = message.getByte(DMT.TYPE);
        if (Error.isValid(rawError)) {
          listener.onError(Error.valueOf(rawError), null, false);
        } else {
          //Not recognized locally.
          listener.onError(Error.UNKNOWN, rawError, false);
        }
      } else if (message.getSpec().equals(DMT.ProbeRefused)) {
        listener.onRefused();
      else {
        throw new UnsupportedOperationException("Missing handling for " + message.getSpec().getName());
      }
    }

    @Override
    public void onRestarted(PeerContext context) {}

    @Override
    public void onTimeout() {
      if (logDEBUG) Logger.debug(Probe.class, "Timed out.");
      listener.onError(Error.TIMEOUT, null, true);
    }

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

  /**
   * Listener which relays responses to the node specified during construction. Used for received probe requests.
   * This leads to reconstructing the messages, but removes potentially harmful sub-messages and also removes the
   * need for duplicate message sending code elsewhere, If the result includes a trace this would be the place
   * to add local results to it.
   */
  private class ResultRelay implements Listener {

    private final PeerNode source;
    private final Long uid;

    /**
     * @param source peer from which the request was received and to which send the response.
     * @throws IllegalArgumentException if source is null.
     */
    public ResultRelay(PeerNode source, Long uid) {
      this.source = source;
      this.uid = uid;
    }

    private void send(Message message) {
      if (!source.isConnected()) {
        if (logDEBUG) Logger.debug(Probe.class, SOURCE_DISCONNECT);
        return;
      }
      if (logDEBUG) Logger.debug(Probe.class, "Relaying " + message.getSpec().getName() + " back" +
                                                " to " + source.userToString());
      try {
        source.sendAsync(message, null, Probe.this);
      } catch (NotConnectedException e) {
        if (logDEBUG) Logger.debug(Probe.class, SOURCE_DISCONNECT, e);
      }
    }

    @Override
    public void onError(Error error, Byte code, boolean local) {
      send(DMT.createProbeError(uid, error));
    }

    @Override
    public void onRefused() {
      send(DMT.createProbeRefused(uid));
    }

    @Override
    public void onOutputBandwidth(float outputBandwidth) {
      send(DMT.createProbeBandwidth(uid, outputBandwidth));
    }

    @Override
    public void onBuild(int build) {
      send(DMT.createProbeBuild(uid, build));
    }

    @Override
    public void onIdentifier(long identifier, byte uptimePercentage) {
      send(DMT.createProbeIdentifier(uid, identifier, uptimePercentage));
    }

    @Override
    public void onLinkLengths(float[] linkLengths) {
      send(DMT.createProbeLinkLengths(uid, linkLengths));
    }

    @Override
    public void onLocation(float location) {
      send(DMT.createProbeLocation(uid, location));
    }

    @Override
    public void onStoreSize(float storeSize) {
      send(DMT.createProbeStoreSize(uid, storeSize));
    }

    @Override
    public void onUptime(float uptimePercentage) {
      send(DMT.createProbeUptime(uid, uptimePercentage));
    }

    @Override
    public void onRejectStats(byte[] stats) {
      if(stats.length < 4) {
        Logger.warning(this, "Unknown length for stats: "+stats.length);
        onError(Error.UNKNOWN, Error.UNKNOWN.code, true);
      } else {
        if(stats.length > 4)
          stats = Arrays.copyOf(stats, 4);
        send(DMT.createProbeRejectStats(uid, stats));
      }
    }

    @Override
    public void onOverallBulkOutputCapacity(
        byte bandwidthClassForCapacityUsage, float capacityUsage) {
      send(DMT.createProbeOverallBulkOutputCapacityUsage(uid, bandwidthClassForCapacityUsage, capacityUsage));
      // TODO Auto-generated method stub
     
    }
  }
}
TOP

Related Classes of freenet.node.probe.Probe$ResultRelay

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.