/* 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 freenet.crypt.CryptFormatException;
import freenet.crypt.DSAPublicKey;
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.PeerRestartedException;
import freenet.io.xfer.WaitedTooLongException;
import freenet.keys.NodeSSK;
import freenet.keys.SSKBlock;
import freenet.keys.SSKVerifyException;
import freenet.store.KeyCollisionException;
import freenet.support.Logger;
import freenet.support.ShortBuffer;
import freenet.support.Logger.LogLevel;
import freenet.support.io.NativeThread;
/**
* Handles an incoming SSK insert.
* SSKs need their own insert/request classes, see comments in SSKInsertSender.
*/
public class SSKInsertHandler implements PrioRunnable, ByteCounter {
private static boolean logMINOR;
static final int DATA_INSERT_TIMEOUT = 30000;
final Node node;
final long uid;
final PeerNode source;
final NodeSSK key;
final long startTime;
private SSKBlock block;
private DSAPublicKey pubKey;
private short htl;
private SSKInsertSender sender;
private byte[] data;
private byte[] headers;
private boolean canCommit;
final InsertTag tag;
private final boolean canWriteDatastore;
private final boolean forkOnCacheable;
private final boolean preferInsert;
private final boolean ignoreLowBackoff;
private final boolean realTimeFlag;
private boolean collided = false;
SSKInsertHandler(NodeSSK key, byte[] data, byte[] headers, short htl, PeerNode source, long id, Node node, long startTime, InsertTag tag, boolean canWriteDatastore, boolean forkOnCacheable, boolean preferInsert, boolean ignoreLowBackoff, boolean realTimeFlag) {
this.node = node;
this.uid = id;
this.source = source;
this.startTime = startTime;
this.key = key;
this.htl = htl;
this.data = data;
this.headers = headers;
this.tag = tag;
this.canWriteDatastore = canWriteDatastore;
byte[] pubKeyHash = key.getPubKeyHash();
pubKey = node.getPubKey.getKey(pubKeyHash, false, false, null);
canCommit = false;
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
this.forkOnCacheable = forkOnCacheable;
this.preferInsert = preferInsert;
this.ignoreLowBackoff = ignoreLowBackoff;
this.realTimeFlag = realTimeFlag;
}
@Override
public String toString() {
return super.toString()+" for "+uid;
}
@Override
public void run() {
freenet.support.Logger.OSThread.logPID(this);
try {
realRun();
} catch (Throwable t) {
Logger.error(this, "Caught "+t, t);
} finally {
if(logMINOR) Logger.minor(this, "Exiting InsertHandler.run() for "+uid);
tag.unlockHandler();
}
}
private void realRun() {
// Send Accepted
Message accepted = DMT.createFNPSSKAccepted(uid, pubKey == null);
try {
source.sendAsync(accepted, null, this);
} catch (NotConnectedException e1) {
if(logMINOR) Logger.minor(this, "Lost connection to source");
return;
}
if(tag.shouldSlowDown()) {
try {
source.sendAsync(DMT.createFNPRejectedOverload(uid, false, false, realTimeFlag), null, this);
} catch (NotConnectedException e) {
// Ignore.
}
}
while(headers == null || data == null || pubKey == null) {
MessageFilter mfDataInsertRejected = MessageFilter.create().setType(DMT.FNPDataInsertRejected).setField(DMT.UID, uid).setSource(source).setTimeout(DATA_INSERT_TIMEOUT);
MessageFilter mf = mfDataInsertRejected;
if(headers == null) {
MessageFilter m = MessageFilter.create().setType(DMT.FNPSSKInsertRequestHeaders).setField(DMT.UID, uid).setSource(source).setTimeout(DATA_INSERT_TIMEOUT);
mf = m.or(mf);
}
if(data == null) {
MessageFilter m = MessageFilter.create().setType(DMT.FNPSSKInsertRequestData).setField(DMT.UID, uid).setSource(source).setTimeout(DATA_INSERT_TIMEOUT);
mf = m.or(mf);
}
if(pubKey == null) {
MessageFilter m = MessageFilter.create().setType(DMT.FNPSSKPubKey).setField(DMT.UID, uid).setSource(source).setTimeout(DATA_INSERT_TIMEOUT);
mf = m.or(mf);
}
Message msg;
try {
msg = node.usm.waitFor(mf, this);
} catch (DisconnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source on "+uid);
return;
}
if(msg == null) {
Logger.normal(this, "Failed to receive all parts (data="+(data==null?"null":"ok")+" headers="+(headers==null?"null":"ok")+" pk="+pubKey+") for "+uid);
Message failed = DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_RECEIVE_FAILED);
try {
source.sendSync(failed, this, realTimeFlag);
} catch (NotConnectedException e) {
// Ignore
} catch (SyncSendWaitedTooLongException e) {
// Ignore
}
return;
} else if(msg.getSpec() == DMT.FNPSSKInsertRequestHeaders) {
headers = ((ShortBuffer)msg.getObject(DMT.BLOCK_HEADERS)).getData();
} else if(msg.getSpec() == DMT.FNPSSKInsertRequestData) {
data = ((ShortBuffer)msg.getObject(DMT.DATA)).getData();
} else if(msg.getSpec() == DMT.FNPSSKPubKey) {
byte[] pubkeyAsBytes = ((ShortBuffer)msg.getObject(DMT.PUBKEY_AS_BYTES)).getData();
try {
pubKey = DSAPublicKey.create(pubkeyAsBytes);
if(logMINOR) Logger.minor(this, "Got pubkey on "+uid+" : "+pubKey);
Message confirm = DMT.createFNPSSKPubKeyAccepted(uid);
try {
source.sendAsync(confirm, null, this);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source on "+uid);
return;
}
} catch (CryptFormatException e) {
Logger.error(this, "Invalid pubkey from "+source+" on "+uid);
msg = DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_SSK_ERROR);
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException ee) {
// Ignore
} catch (SyncSendWaitedTooLongException ee) {
// Ignore
}
return;
}
} else if(msg.getSpec() == DMT.FNPDataInsertRejected) {
try {
source.sendAsync(DMT.createFNPDataInsertRejected(uid, msg.getShort(DMT.DATA_INSERT_REJECTED_REASON)), null, this);
} catch (NotConnectedException e) {
// Ignore.
}
return;
} else {
Logger.error(this, "Unexpected message? "+msg+" on "+this);
}
}
try {
key.setPubKey(pubKey);
block = new SSKBlock(data, headers, key, false);
} catch (SSKVerifyException e1) {
Logger.error(this, "Invalid SSK from "+source, e1);
Message msg = DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_SSK_ERROR);
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException e) {
// Ignore
} catch (SyncSendWaitedTooLongException e) {
// Ignore
}
return;
}
SSKBlock storedBlock = node.fetch(key, false, false, false, canWriteDatastore, false, null);
if((storedBlock != null) && !storedBlock.equals(block)) {
try {
RequestHandler.sendSSK(storedBlock.getRawHeaders(), storedBlock.getRawData(), false, pubKey, source, uid, this, realTimeFlag);
} catch (NotConnectedException e1) {
if(logMINOR) Logger.minor(this, "Lost connection to source on "+uid);
return;
} catch (WaitedTooLongException e1) {
Logger.error(this, "Took too long to send ssk datareply to "+uid+" (because of throttling)");
return;
} catch (PeerRestartedException e) {
if(logMINOR) Logger.minor(this, "Source restarted on "+uid);
return;
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send ssk datareply to "+uid);
return;
}
block = storedBlock;
}
if(logMINOR) Logger.minor(this, "Got block for "+key+" for "+uid);
if(htl > 0)
sender = node.makeInsertSender(block, htl, uid, tag, source, false, false, canWriteDatastore, forkOnCacheable, preferInsert, ignoreLowBackoff, realTimeFlag);
boolean receivedRejectedOverload = false;
while(true) {
synchronized(sender) {
try {
if(sender.getStatus() == SSKInsertSender.NOT_FINISHED)
sender.wait(5000);
} catch (InterruptedException e) {
// Ignore
}
}
if((!receivedRejectedOverload) && sender.receivedRejectedOverload()) {
receivedRejectedOverload = true;
// Forward it
// Does not need to be sent synchronously since is non-terminal.
Message m = DMT.createFNPRejectedOverload(uid, false, true, realTimeFlag);
try {
source.sendAsync(m, null, this);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source");
return;
}
}
if(sender.hasRecentlyCollided()) {
// Forward collision
data = sender.getData();
headers = sender.getHeaders();
collided = true;
try {
block = new SSKBlock(data, headers, key, true);
} catch (SSKVerifyException e1) {
// Is verified elsewhere...
throw new Error("Impossible: " + e1, e1);
}
try {
RequestHandler.sendSSK(headers, data, false, pubKey, source, uid, this, realTimeFlag);
} catch (NotConnectedException e1) {
if(logMINOR) Logger.minor(this, "Lost connection to source on "+uid);
return;
} catch (WaitedTooLongException e1) {
Logger.error(this, "Took too long to send ssk datareply to "+uid+" because of bwlimiting");
return;
} catch (PeerRestartedException e) {
Logger.error(this, "Peer restarted on "+uid);
return;
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send ssk datareply to "+uid);
return;
}
}
int status = sender.getStatus();
if(status == SSKInsertSender.NOT_FINISHED) {
continue;
}
// Local RejectedOverload's (fatal).
// Internal error counts as overload. It'd only create a timeout otherwise, which is the same thing anyway.
// We *really* need a good way to deal with nodes that constantly R_O!
if((status == SSKInsertSender.TIMED_OUT) ||
(status == SSKInsertSender.GENERATED_REJECTED_OVERLOAD) ||
(status == SSKInsertSender.INTERNAL_ERROR)) {
// Unlock early for originator, late for target; see UIDTag comments.
tag.unlockHandler();
Message msg = DMT.createFNPRejectedOverload(uid, true, true, realTimeFlag);
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source");
return;
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send "+msg+" to "+source);
return;
}
// Might as well store it anyway.
if((status == SSKInsertSender.TIMED_OUT) ||
(status == SSKInsertSender.GENERATED_REJECTED_OVERLOAD))
canCommit = true;
finish(status);
return;
}
if((status == SSKInsertSender.ROUTE_NOT_FOUND) || (status == SSKInsertSender.ROUTE_REALLY_NOT_FOUND)) {
// Unlock early for originator, late for target; see UIDTag comments.
tag.unlockHandler();
Message msg = DMT.createFNPRouteNotFound(uid, sender.getHTL());
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source");
return;
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send "+msg+" to source");
}
canCommit = true;
finish(status);
return;
}
if(status == SSKInsertSender.SUCCESS) {
// Unlock early for originator, late for target; see UIDTag comments.
tag.unlockHandler();
Message msg = DMT.createFNPInsertReply(uid);
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Lost connection to source");
return;
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send "+msg+" to "+source);
}
canCommit = true;
finish(status);
return;
}
// Otherwise...?
Logger.error(this, "Unknown status code: "+sender.getStatusString());
// Unlock early for originator, late for target; see UIDTag comments.
tag.unlockHandler();
Message msg = DMT.createFNPRejectedOverload(uid, true, true, realTimeFlag);
try {
source.sendSync(msg, this, realTimeFlag);
} catch (NotConnectedException e) {
// Ignore
} catch (SyncSendWaitedTooLongException e) {
Logger.error(this, "Took too long to send "+msg+" to "+source);
}
finish(status);
return;
}
}
/**
* If canCommit, and we have received all the data, and it
* verifies, then commit it.
*/
private void finish(int code) {
if(logMINOR) Logger.minor(this, "Finishing");
if(canCommit) {
commit();
}
if(code != SSKInsertSender.TIMED_OUT && code != SSKInsertSender.GENERATED_REJECTED_OVERLOAD &&
code != SSKInsertSender.INTERNAL_ERROR && code != SSKInsertSender.ROUTE_REALLY_NOT_FOUND) {
int totalSent = getTotalSentBytes();
int totalReceived = getTotalReceivedBytes();
if(sender != null) {
totalSent += sender.getTotalSentBytes();
totalReceived += sender.getTotalReceivedBytes();
}
if(logMINOR) Logger.minor(this, "Remote SSK insert cost "+totalSent+ '/' +totalReceived+" bytes ("+code+ ')');
node.nodeStats.remoteSskInsertBytesSentAverage.report(totalSent);
node.nodeStats.remoteSskInsertBytesReceivedAverage.report(totalReceived);
if(code == SSKInsertSender.SUCCESS) {
// Can report both sides
node.nodeStats.successfulSskInsertBytesSentAverage.report(totalSent);
node.nodeStats.successfulSskInsertBytesReceivedAverage.report(totalReceived);
}
}
}
private void commit() {
try {
node.store(block, node.shouldStoreDeep(key, source, sender == null ? new PeerNode[0] : sender.getRoutedTo()), collided, false, canWriteDatastore, false);
} catch (KeyCollisionException e) {
Logger.normal(this, "Collision on "+this);
}
}
private final Object totalBytesSync = new Object();
private int totalBytesSent;
private int totalBytesReceived;
@Override
public void sentBytes(int x) {
synchronized(totalBytesSync) {
totalBytesSent += x;
}
node.nodeStats.insertSentBytes(true, x);
}
@Override
public void receivedBytes(int x) {
synchronized(totalBytesSync) {
totalBytesReceived += x;
}
node.nodeStats.insertReceivedBytes(true, x);
}
public int getTotalSentBytes() {
return totalBytesSent;
}
public int getTotalReceivedBytes() {
return totalBytesReceived;
}
@Override
public void sentPayload(int x) {
node.sentPayload(x);
node.nodeStats.insertSentBytes(true, -x);
}
@Override
public int getPriority() {
return NativeThread.HIGH_PRIORITY;
}
}