Package freenet.node

Source Code of freenet.node.SSKInsertSender

/* 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.SECONDS;

import freenet.crypt.DSAPublicKey;
import freenet.crypt.SHA256;
import freenet.io.comm.AsyncMessageCallback;
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.io.comm.SlowAsyncMessageFilterCallback;
import freenet.keys.NodeSSK;
import freenet.keys.SSKBlock;
import freenet.keys.SSKVerifyException;
import freenet.support.Logger;
import freenet.support.ShortBuffer;
import freenet.support.io.NativeThread;

/**
* SSKs require separate logic for inserts and requests, for various reasons:
* - SSKs can collide.
* - SSKs have only 1kB of data, so we can pack it into the DataReply, and we don't need to
*   wait for a long data-transfer timeout.
* - SSKs have pubkeys, which don't always need to be sent.
*/
public class SSKInsertSender extends BaseSender implements PrioRunnable, AnyInsertSender, ByteCounter {

    // Constants
    static final long ACCEPTED_TIMEOUT = SECONDS.toMillis(10);

    // Basics
    final NodeSSK myKey;
    final long origUID;
    final InsertTag origTag;
    /** SSK's pubkey */
    final DSAPublicKey pubKey;
    /** SSK's pubkey's hash */
    final byte[] pubKeyHash;
    /** Data (we know at start of insert) - can change if we get a collision */
    byte[] data;
    /** Headers (we know at start of insert) - can change if we get a collision */
    byte[] headers;
    final boolean fromStore;
    final long startTime;
    private boolean hasCollided;
    private boolean hasRecentlyCollided;
    private SSKBlock block;
    private static boolean logMINOR;
    private static boolean logDEBUG;
    static {
      Logger.registerClass(SSKInsertSender.class);
    }
    private final boolean forkOnCacheable;
    private final boolean preferInsert;
    private final boolean ignoreLowBackoff;
    private final boolean realTimeFlag;
    private InsertTag forkedRequestTag;
   
    private int status = -1;
    /** Still running */
    static final int NOT_FINISHED = -1;
    /** Successful insert */
    static final int SUCCESS = 0;
    /** Route not found */
    static final int ROUTE_NOT_FOUND = 1;
    /** Internal error */
    static final int INTERNAL_ERROR = 3;
    /** Timed out waiting for response */
    static final int TIMED_OUT = 4;
    /** Locally Generated a RejectedOverload */
    static final int GENERATED_REJECTED_OVERLOAD = 5;
    /** Could not get off the node at all! */
    static final int ROUTE_REALLY_NOT_FOUND = 6;
   
    SSKInsertSender(SSKBlock block, long uid, InsertTag tag, short htl, PeerNode source, Node node, boolean fromStore, boolean canWriteClientCache, boolean forkOnCacheable, boolean preferInsert, boolean ignoreLowBackoff, boolean realTimeFlag) {
      super(block.getKey(), realTimeFlag, source, node, htl, uid);
      this.fromStore = fromStore;
      this.origUID = uid;
      this.origTag = tag;
      myKey = block.getKey();
      data = block.getRawData();
      headers = block.getRawHeaders();
      pubKey = myKey.getPubKey();
      if(pubKey == null)
        throw new IllegalArgumentException("Must have pubkey to insert data!!");
      // pubKey.fingerprint() is not the same as hash(pubKey.asBytes())). FIXME it should be!
      byte[] pubKeyAsBytes = pubKey.asBytes();
      pubKeyHash = SHA256.digest(pubKeyAsBytes);
      this.block = block;
      startTime = System.currentTimeMillis();
      this.forkOnCacheable = forkOnCacheable;
      this.preferInsert = preferInsert;
      this.ignoreLowBackoff = ignoreLowBackoff;
      this.realTimeFlag = realTimeFlag;
    }

    void start() {
      node.executor.execute(this, "SSKInsertSender for UID "+uid+" on "+node.getDarknetPortNumber()+" at "+System.currentTimeMillis());
    }
   
  @Override
  public void run() {
      freenet.support.Logger.OSThread.logPID(this);
        origTag.startedSender();
        try {
            routeRequests();
        } catch (Throwable t) {
            Logger.error(this, "Caught "+t, t);
            if(status == NOT_FINISHED)
              finish(INTERNAL_ERROR, null);
        } finally {
          if(logMINOR) Logger.minor(this, "Finishing "+this);
            if(status == NOT_FINISHED)
              finish(INTERNAL_ERROR, null);
            origTag.finishedSender();
          if(forkedRequestTag != null)
            forkedRequestTag.finishedSender();
        }
  }

  static final int MAX_HIGH_HTL_FAILURES = 5;
 
    protected void routeRequests() {
        PeerNode next = null;
        // While in no-cache mode, we don't decrement HTL on a RejectedLoop or similar, but we only allow a limited number of such failures before RNFing.
        int highHTLFailureCount = 0;
        boolean starting = true;
        while(true) {
            /*
             * If we haven't routed to any node yet, decrement according to the source.
             * If we have, decrement according to the node which just failed.
             * Because:
             * 1) If we always decrement according to source then we can be at max or min HTL
             * for a long time while we visit *every* peer node. This is BAD!
             * 2) The node which just failed can be seen as the requestor for our purposes.
             */
            // Decrement at this point so we can DNF immediately on reaching HTL 0.
            boolean canWriteStorePrev = node.canWriteDatastoreInsert(htl);
            if((!starting) && (!canWriteStorePrev)) {
              // We always decrement on starting a sender.
              // However, after that, if our HTL is above the no-cache threshold,
              // we do not want to decrement the HTL for trivial rejections (e.g. RejectedLoop),
              // because we would end up caching data too close to the originator.
              // So allow 5 failures and then RNF.
              if(highHTLFailureCount++ >= MAX_HIGH_HTL_FAILURES) {
                if(logMINOR) Logger.minor(this, "Too many failures at non-cacheable HTL");
                    finish(ROUTE_NOT_FOUND, null);
                    return;
              }
              if(logMINOR) Logger.minor(this, "Allowing failure "+highHTLFailureCount+" htl is still "+htl);
            } else {
                htl = node.decrementHTL(hasForwarded ? next : source, htl);
                if(logMINOR) Logger.minor(this, "Decremented HTL to "+htl);
            }
            starting = false;
            if(htl <= 0) {
                // Send an InsertReply back
            if(!hasForwarded)
              origTag.setNotRoutedOnwards();
                finish(SUCCESS, null);
                return;
            }
           
          if(origTag.shouldStop()) {
            finish(SUCCESS, null);
            return;
          }
         
            if( node.canWriteDatastoreInsert(htl) && (!canWriteStorePrev) && forkOnCacheable && forkedRequestTag == null) {
              // FORK! We are now cacheable, and it is quite possible that we have already gone over the ideal sink nodes,
              // in which case if we don't fork we will miss them, and greatly reduce the insert's reachability.
              // So we fork: Create a new UID so we can go over the previous hops again if they happen to be good places to store the data.
             
              // Existing transfers will keep their existing UIDs, since they copied the UID in the constructor.
              // Both local and remote inserts can be forked here: If it has reached this HTL, it means it's already been routed to some nodes.
             
              uid = node.clientCore.makeUID();
              forkedRequestTag = new InsertTag(true, InsertTag.START.REMOTE, source, realTimeFlag, uid, node);
              forkedRequestTag.reassignToSelf();
              forkedRequestTag.startedSender();
              forkedRequestTag.unlockHandler();
        forkedRequestTag.setAccepted();
              Logger.normal(this, "FORKING SSK INSERT "+origUID+" to "+uid);
              nodesRoutedTo.clear();
              node.tracker.lockUID(forkedRequestTag);
            }
           
            // Route it
            next = node.peers.closerPeer(forkedRequestTag == null ? source : null, nodesRoutedTo, target, true, node.isAdvancedModeEnabled(), -1, null,
              null, htl, ignoreLowBackoff ? Node.LOW_BACKOFF : 0, source == null, realTimeFlag, newLoadManagement);
           
            if(next == null) {
                // Backtrack
            if(!hasForwarded)
              origTag.setNotRoutedOnwards();
                finish(ROUTE_NOT_FOUND, null);
                return;
            }
           
            InsertTag thisTag = forkedRequestTag;
            if(forkedRequestTag == null) thisTag = origTag;
           
            innerRouteRequests(next, thisTag);
            return;
        }           
    }
   
    private void handleNoPubkeyAccepted(PeerNode next, InsertTag thisTag) {
      // FIXME implementing two stage timeout would likely involve forking at this point.
      // The problem is the peer has now got everything it needs to run the insert!
     
    // Try to propagate back to source
    Logger.error(this, "Timeout waiting for FNPSSKPubKeyAccepted on "+next);
    next.localRejectedOverload("Timeout2", realTimeFlag);
    // This is a local timeout, they should send it immediately.
    forwardRejectedOverload();
    next.fatalTimeout(thisTag, false);
  }

  private MessageFilter makeSearchFilter(PeerNode next,
      int searchTimeout) {
        /** What are we waiting for now??:
         * - FNPRouteNotFound - couldn't exhaust HTL, but send us the
         *   data anyway please
         * - FNPInsertReply - used up all HTL, yay
         * - FNPRejectOverload - propagating an overload error :(
         * - FNPDataFound - target already has the data, and the data is
         *   an SVK/SSK/KSK, therefore could be different to what we are
         *   inserting.
         * - FNPDataInsertRejected - the insert was invalid
         */
       
        MessageFilter mfInsertReply = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPInsertReply);
        MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRejectedOverload);
        MessageFilter mfRouteNotFound = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRouteNotFound);
        MessageFilter mfDataInsertRejected = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPDataInsertRejected);
        MessageFilter mfSSKDataFoundHeaders = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPSSKDataFoundHeaders);
       
        return mfRouteNotFound.or(mfInsertReply.or(mfRejectedOverload.or(mfDataInsertRejected.or(mfSSKDataFoundHeaders))));
  }

  private DO handleMessage(Message msg, PeerNode next, InsertTag thisTag) {
    if (msg.getSpec() == DMT.FNPRejectedOverload) {
      if(handleRejectedOverload(msg, next, thisTag)) return DO.NEXT_PEER;
      else return DO.WAIT;
    }

    if (msg.getSpec() == DMT.FNPRouteNotFound) {
      handleRouteNotFound(msg, next, thisTag);
      // Finished as far as this node is concerned
      return DO.NEXT_PEER;
    }

    if (msg.getSpec() == DMT.FNPDataInsertRejected) {
      handleDataInsertRejected(msg, next, thisTag);
      return DO.NEXT_PEER; // What else can we do?
    }
   
    if(msg.getSpec() == DMT.FNPSSKDataFoundHeaders) {
      return handleSSKDataFoundHeaders(msg, next, thisTag);
    }
   
    if (msg.getSpec() != DMT.FNPInsertReply) {
      Logger.error(this, "Unknown reply: " + msg);
      finish(INTERNAL_ERROR, next);
      return DO.FINISHED;
    }
       
    // Our task is complete
    next.successNotOverload(realTimeFlag);
    finish(SUCCESS, next);
    return DO.FINISHED;

    }

    private enum DO {
      FINISHED,
      WAIT,
      NEXT_PEER
    }

  private static final long TIMEOUT_AFTER_ACCEPTEDREJECTED_TIMEOUT = SECONDS.toMillis(60);

  @Override
  protected void handleAcceptedRejectedTimeout(final PeerNode next, final UIDTag tag) {
    // It could still be running. So the timeout is fatal to the node.
    // This is a WARNING not an ERROR because it's possible that the problem is we simply haven't been able to send the message yet, because we don't use sendSync().
    // FIXME use a callback to rule this out and log an ERROR.
    Logger.warning(this, "Timeout awaiting Accepted/Rejected "+this+" to "+next);
    // Use the right UID here, in case we fork.
    final long uid = tag.uid;
    tag.handlingTimeout(next);
    // The node didn't accept the request. So we don't need to send them the data.
    // However, we do need to wait a bit longer to try to postpone the fatalTimeout().
    // Somewhat intricate logic to try to avoid fatalTimeout() if at all possible.
    MessageFilter mf = makeAcceptedRejectedFilter(next, TIMEOUT_AFTER_ACCEPTEDREJECTED_TIMEOUT, tag);
    try {
      node.usm.addAsyncFilter(mf, new SlowAsyncMessageFilterCallback() {

        @Override
        public void onMatched(Message m) {
          if(m.getSpec() == DMT.FNPRejectedLoop ||
              m.getSpec() == DMT.FNPRejectedOverload) {
            // Ok.
            next.noLongerRoutingTo(tag, false);
          } else {
            assert(m.getSpec() == DMT.FNPSSKAccepted);
            if(logMINOR)
              Logger.minor(this, "Accepted after timeout on "+SSKInsertSender.this+" - will not send DataInsert, waiting for RejectedTimeout");
            if(logMINOR) Logger.minor(this, "Forked timed out insert but not going to send DataInsert on "+SSKInsertSender.this+" to "+next);
            // We are not going to send the DataInsert.
            // We have moved on, and we don't want inserts to fork unnecessarily.
            // However, we need to send a DataInsertRejected, or two-stage timeout will happen.
            try {
              next.sendAsync(DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_TIMEOUT_WAITING_FOR_ACCEPTED), new AsyncMessageCallback() {

                @Override
                public void sent() {
                  // Ignore.
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected sent after accepted timeout on "+SSKInsertSender.this);
                }

                @Override
                public void acknowledged() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected acknowledged after accepted timeout on "+SSKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }

                @Override
                public void disconnected() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected peer disconnected after accepted timeout on "+SSKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }

                @Override
                public void fatalError() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected fatal error after accepted timeout on "+SSKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }
               
              }, SSKInsertSender.this);
            } catch (NotConnectedException e) {
              next.noLongerRoutingTo(tag, false);
            }
          }
        }

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

        @Override
        public void onTimeout() {
          Logger.error(this, "Fatal: No Accepted/Rejected for "+SSKInsertSender.this);
          next.fatalTimeout(tag, false);
        }

        @Override
        public void onDisconnect(PeerContext ctx) {
          next.noLongerRoutingTo(tag, false);
        }

        @Override
        public void onRestarted(PeerContext ctx) {
          next.noLongerRoutingTo(tag, false);
        }

        @Override
        public int getPriority() {
          return NativeThread.NORM_PRIORITY;
        }
       
      }, this);
    } catch (DisconnectedException e) {
      next.noLongerRoutingTo(tag, false);
    }
  }

    /** @return True if fatal and we should try another node, false if just relayed so
     * we should wait for more responses. */
    private boolean handleRejectedOverload(Message msg, PeerNode next, InsertTag thisTag) {
    // Probably non-fatal, if so, we have time left, can try next one
    if (msg.getBoolean(DMT.IS_LOCAL)) {
      next.localRejectedOverload("ForwardRejectedOverload4", realTimeFlag);
      if(logMINOR) Logger.minor(this,
          "Local RejectedOverload, moving on to next peer");
      // Give up on this one, try another
          next.noLongerRoutingTo(thisTag, false);
      return true;
    } else {
      forwardRejectedOverload();
    }
    return false; // Wait for any further response
  }

  private void handleRouteNotFound(Message msg, PeerNode next, InsertTag thisTag) {
    if(logMINOR) Logger.minor(this, "Rejected: RNF");
    short newHtl = msg.getShort(DMT.HTL);
    if(newHtl < 0) newHtl = 0;
    if (htl > newHtl)
      htl = newHtl;
    next.successNotOverload(realTimeFlag);
      next.noLongerRoutingTo(thisTag, false);
  }

  private void handleDataInsertRejected(Message msg, PeerNode next, InsertTag thisTag) {
    next.successNotOverload(realTimeFlag);
    short reason = msg.getShort(DMT.DATA_INSERT_REJECTED_REASON);
    if(logMINOR) Logger.minor(this, "DataInsertRejected: " + reason);
    if (reason == DMT.DATA_INSERT_REJECTED_VERIFY_FAILED) {
      if (fromStore) {
        // That's odd...
        Logger.error(this,"Verify failed on next node "
            + next + " for DataInsert but we were sending from the store!");
      }
    }
    Logger.error(this, "SSK insert rejected! Reason="
        + DMT.getDataInsertRejectedReason(reason));
      next.noLongerRoutingTo(thisTag, false);
  }

  /** @return True if we got new data and are propagating it. False if something failed
     * and we need to try the next node. */
  private DO handleSSKDataFoundHeaders(Message msg, PeerNode next, InsertTag thisTag) {
   
    /**
     * Data was already on node, and was NOT equal to what we sent. COLLISION!
     *
     * We can either accept the old data or the new data.
     * OLD DATA:
     * - KSK-based stuff is usable. Well, somewhat; a node could spoof KSKs on
     * receiving an insert, (if it knows them in advance), but it cannot just
     * start inserts to overwrite old SSKs.
     * - You cannot "update" an SSK.
     * NEW DATA:
     * - KSK-based stuff not usable. (Some people think this is a good idea!).
     * - Illusion of updatability. (VERY BAD IMHO, because it's not really
     * updatable... FIXME implement TUKs; would determine latest version based
     * on version number, and propagate on request with a certain probability or
     * according to time. However there are good arguments to do updating at a
     * higher level (e.g. key bottleneck argument), and TUKs should probably be
     * distinct from SSKs.
     *
     * For now, accept the "old" i.e. preexisting data.
     */
    Logger.normal(this, "Got collision on "+myKey+" ("+uid+") sending to "+next.getPeer());
   
    headers = ((ShortBuffer) msg.getObject(DMT.BLOCK_HEADERS)).getData();
    // Wait for the data
    MessageFilter mfData = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(SSKInsertHandler.DATA_INSERT_TIMEOUT).setType(DMT.FNPSSKDataFoundData);
    Message dataMessage;
    try {
      dataMessage = node.usm.waitFor(mfData, this);
    } catch (DisconnectedException e) {
      if(logMINOR)
        Logger.minor(this, "Disconnected: "+next+" getting datareply for "+this);
      next.noLongerRoutingTo(thisTag, false);
      return DO.NEXT_PEER;
    }
    if(dataMessage == null) {
      Logger.error(this, "Got headers but not data for datareply for insert from "+this);
      next.noLongerRoutingTo(thisTag, false);
      return DO.NEXT_PEER;
    }
    // collided, overwrite data with remote data
    try {
      data = ((ShortBuffer) dataMessage.getObject(DMT.DATA)).getData();
      block = new SSKBlock(data, headers, block.getKey(), false);
     
      synchronized(this) {
        hasRecentlyCollided = true;
        hasCollided = true;
        notifyAll();
      }
     
      // The node will now propagate the new data. There is no need to move to the next node yet.
      return DO.WAIT;
    } catch (SSKVerifyException e) {
      Logger.error(this, "Invalid SSK from remote on collusion: " + this + ":" +block);
      finish(INTERNAL_ERROR, next);
      return DO.FINISHED;
    }
  }

  @Override
  protected MessageFilter makeAcceptedRejectedFilter(PeerNode next,
      long acceptedTimeout, UIDTag tag) {
    // Use the right UID here, in case we fork.
    final long uid = tag.uid;
        /*
         * Because messages may be re-ordered, it is
         * entirely possible that we get a non-local RejectedOverload,
         * followed by an Accepted. So we must loop here.
         */
       
        MessageFilter mfAccepted = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPSSKAccepted);
        MessageFilter mfRejectedLoop = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPRejectedLoop);
        MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPRejectedOverload);
        return mfAccepted.or(mfRejectedLoop.or(mfRejectedOverload));
  }

  private boolean hasForwardedRejectedOverload;
   
    synchronized boolean receivedRejectedOverload() {
      return hasForwardedRejectedOverload;
    }
   
    /** Forward RejectedOverload to the request originator.
     * DO NOT CALL if have a *local* RejectedOverload.
     */
    @Override
    protected synchronized void forwardRejectedOverload() {
      if(hasForwardedRejectedOverload) return;
      hasForwardedRejectedOverload = true;
       notifyAll();
  }
   
    private void finish(int code, PeerNode next) {
      if(logMINOR) Logger.minor(this, "Finished: "+getStatusString(code)+" on "+this+" from "+(next == null ? "(null)" : next.shortToString()), new Exception("debug"));
     
      if(next != null) {
        if(origTag != null) next.noLongerRoutingTo(origTag, false);
        if(forkedRequestTag != null) next.noLongerRoutingTo(forkedRequestTag, false);
      }
     
      synchronized(this) {
        if(status != NOT_FINISHED && status != TIMED_OUT)
          throw new IllegalStateException("finish() called with "+code+" when was already "+status);
       
        if((code == ROUTE_NOT_FOUND) && !hasForwarded)
          code = ROUTE_REALLY_NOT_FOUND;
       
        if(status != TIMED_OUT) {
          status = code;
          notifyAll();
        }
        }

        if(code == SUCCESS && next != null)
          next.onSuccess(true, true);
       
        if(logMINOR) Logger.minor(this, "Set status code: "+getStatusString());
        // Nothing to wait for, no downstream transfers, just exit.
    }

    @Override
    public synchronized int getStatus() {
        return status;
    }
   
    @Override
    public synchronized short getHTL() {
        return htl;
    }

    @Override
    public synchronized String getStatusString() {
      return getStatusString(status);
    }
   
    /**
     * @return The current status as a string
     */
    public static String getStatusString(int status) {
        if(status == SUCCESS)
            return "SUCCESS";
        if(status == ROUTE_NOT_FOUND)
            return "ROUTE NOT FOUND";
        if(status == NOT_FINISHED)
            return "NOT FINISHED";
        if(status == INTERNAL_ERROR)
          return "INTERNAL ERROR";
        if(status == TIMED_OUT)
          return "TIMED OUT";
        if(status == GENERATED_REJECTED_OVERLOAD)
          return "GENERATED REJECTED OVERLOAD";
        if(status == ROUTE_REALLY_NOT_FOUND)
          return "ROUTE REALLY NOT FOUND";
        return "UNKNOWN STATUS CODE: "+status;
    }

  @Override
  public boolean sentRequest() {
    return hasForwarded;
  }
 
  public synchronized boolean hasRecentlyCollided() {
    boolean status = hasRecentlyCollided;
    hasRecentlyCollided = false;
    return status;
  }
 
  public boolean hasCollided() {
    return hasCollided;
  }
 
  public byte[] getPubkeyHash() {
    return headers;
  }

  public byte[] getHeaders() {
    return headers;
  }
 
  public byte[] getData() {
    return data;
  }

  public SSKBlock getBlock() {
    return block;
  }

  @Override
  public long getUID() {
    return uid;
  }

  private final Object totalBytesSync = new Object();
  private int totalBytesSent;
 
  @Override
  public void sentBytes(int x) {
    synchronized(totalBytesSync) {
      totalBytesSent += x;
    }
    node.nodeStats.insertSentBytes(true, x);
  }
 
  public int getTotalSentBytes() {
    synchronized(totalBytesSync) {
      return totalBytesSent;
    }
  }
 
  private int totalBytesReceived;
 
  @Override
  public void receivedBytes(int x) {
    synchronized(totalBytesSync) {
      totalBytesReceived += x;
    }
    node.nodeStats.insertReceivedBytes(true, x);
  }
 
  public int getTotalReceivedBytes() {
    synchronized(totalBytesSync) {
      return totalBytesReceived;
    }
  }

  @Override
  public void sentPayload(int x) {
    node.sentPayload(x);
    node.nodeStats.insertSentBytes(true, -x);
  }

  @Override
  public int getPriority() {
    return NativeThread.HIGH_PRIORITY;
  }

  @Override
  public String toString() {
    return "SSKInsertSender:" + myKey+":"+uid;
  }

  public PeerNode[] getRoutedTo() {
    return this.nodesRoutedTo.toArray(new PeerNode[nodesRoutedTo.size()]);
  }

  @Override
  protected Message createDataRequest() {
        Message request = DMT.createFNPSSKInsertRequestNew(uid, htl, myKey);
        if(forkOnCacheable != Node.FORK_ON_CACHEABLE_DEFAULT) {
          request.addSubMessage(DMT.createFNPSubInsertForkControl(forkOnCacheable));
        }
        if(ignoreLowBackoff != Node.IGNORE_LOW_BACKOFF_DEFAULT) {
          request.addSubMessage(DMT.createFNPSubInsertIgnoreLowBackoff(ignoreLowBackoff));
        }
        if(preferInsert != Node.PREFER_INSERT_DEFAULT) {
          request.addSubMessage(DMT.createFNPSubInsertPreferInsert(preferInsert));
        }
      request.addSubMessage(DMT.createFNPRealTimeFlag(realTimeFlag));
        return request;
  }

  @Override
  protected long getAcceptedTimeout() {
    return ACCEPTED_TIMEOUT;
  }

  @Override
  protected void timedOutWhileWaiting(double load) {
    htl -= (short)Math.max(0, hopsForFatalTimeoutWaitingForPeer());
    if(htl < 0) htl = 0;
        // Backtrack, i.e. RNF.
    if(!hasForwarded)
      origTag.setNotRoutedOnwards();
        finish(ROUTE_NOT_FOUND, null);
  }
 
  private boolean needPubKey;
 
  protected boolean isAccepted(Message msg) {
    if(msg.getSpec() == DMT.FNPSSKAccepted) {
      needPubKey = msg.getBoolean(DMT.NEED_PUB_KEY);
      return true;
    } else return false;
  }

  @Override
  protected void onAccepted(PeerNode next) {
        if(logMINOR) Logger.minor(this, "Got Accepted on "+this);
       
        InsertTag thisTag = forkedRequestTag;
        if(forkedRequestTag == null) thisTag = origTag;
       
        // Send the headers and data
       
        Message headersMsg = DMT.createFNPSSKInsertRequestHeaders(uid, headers, realTimeFlag);
        Message dataMsg = DMT.createFNPSSKInsertRequestData(uid, data, realTimeFlag);
       
        try {
      next.sendAsync(headersMsg, null, this);
      next.sendSync(dataMsg, this, realTimeFlag);
      sentPayload(data.length);
    } catch (NotConnectedException e1) {
      if(logMINOR) Logger.minor(this, "Not connected to "+next);
      next.noLongerRoutingTo(thisTag, false);
      routeRequests();
      return;
    } catch (SyncSendWaitedTooLongException e) {
      Logger.error(this, "Waited too long to send "+dataMsg+" to "+next+" on "+this);
      next.noLongerRoutingTo(thisTag, false);
      routeRequests();
      return;
    }
       
        // Do we need to send them the pubkey?
       
    if(needPubKey) {
          Message pkMsg = DMT.createFNPSSKPubKey(uid, pubKey, realTimeFlag);
          try {
            next.sendSync(pkMsg, this, realTimeFlag);
          } catch (NotConnectedException e) {
            if(logMINOR) Logger.minor(this, "Node disconnected while sending pubkey: "+next);
        next.noLongerRoutingTo(thisTag, false);
        routeRequests();
        return;
          } catch (SyncSendWaitedTooLongException e) {
            Logger.warning(this, "Took too long to send pubkey to "+next+" on "+this);
        next.noLongerRoutingTo(thisTag, false);
        routeRequests();
        return;
      }
         
          // Wait for the SSKPubKeyAccepted
         
          // FIXME doubled the timeout because handling it properly would involve forking.
          MessageFilter mf1 = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ACCEPTED_TIMEOUT*2).setType(DMT.FNPSSKPubKeyAccepted);
         
          Message newAck;
      try {
        newAck = node.usm.waitFor(mf1, this);
      } catch (DisconnectedException e) {
        if(logMINOR) Logger.minor(this, "Disconnected from "+next);
        next.noLongerRoutingTo(thisTag, false);
        routeRequests();
        return;
      }
         
          if(newAck == null) {
            handleNoPubkeyAccepted(next, thisTag);
        // Try another peer
          routeRequests();
          return;
          }
        }
       
        // We have sent them the pubkey, and the data.
        // Wait for the response.
       
    MessageFilter mf = makeSearchFilter(next, calculateTimeout(htl));
       
        while (true) {
          Message msg;
      try {
        msg = node.usm.waitFor(mf, this);
      } catch (DisconnectedException e) {
        Logger.normal(this, "Disconnected from " + next
            + " while waiting for InsertReply on " + this);
        next.noLongerRoutingTo(thisTag, false);
        break;
      }

      if (msg == null) {
       
        // First timeout.
        Logger.warning(this, "Timeout waiting for reply after Accepted in "+this+" from "+next);
        next.localRejectedOverload("AfterInsertAcceptedTimeout", realTimeFlag);
        forwardRejectedOverload();
        finish(TIMED_OUT, next);
       
        // Wait for second timeout.
        while(true) {
         
          try {
            msg = node.usm.waitFor(mf, this);
          } catch (DisconnectedException e) {
            Logger.normal(this, "Disconnected from " + next
                + " while waiting for InsertReply on " + this);
            next.noLongerRoutingTo(thisTag, false);
            return;
          }
         
          if(msg == null) {
            // Second timeout.
            Logger.error(this, "Fatal timeout waiting for reply after Accepted on "+this+" from "+next);
            next.fatalTimeout(thisTag, false);
            return;
          }
         
          DO action = handleMessage(msg, next, thisTag);
         
          if(action == DO.FINISHED)
            return;
          else if(action == DO.NEXT_PEER) {
            next.noLongerRoutingTo(thisTag, false);
            return; // Don't try others
          }
          // else if(action == DO.WAIT) continue;
         
        }
      }
     
      DO action = handleMessage(msg, next, thisTag);
     
      if(action == DO.FINISHED)
        return;
      else if(action == DO.NEXT_PEER)
        break;
      // else if(action == DO.WAIT) continue;
        }
    routeRequests();
  }

  @Override
  protected boolean isInsert() {
    return true;
  }
 
  @Override
  protected PeerNode sourceForRouting() {
    if(forkedRequestTag != null) return null;
    return source;
  }

  @Override
  protected long ignoreLowBackoff() {
    return ignoreLowBackoff ? Node.LOW_BACKOFF : 0;
  }

}
TOP

Related Classes of freenet.node.SSKInsertSender

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.