/* 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; } }