* Streaming, I2CP, Client Message sending:

Pass message timeout through new I2CP message
       SendMessageExpiresMessage, so that the router
       uses the same expiration as the streaming lib.
       Should help reliability.
     * I2CP:
       Implement new I2CP message ReconfigureSessionMessage.
       Will be used for tunnel reduction.
This commit is contained in:
zzz
2009-01-20 17:22:56 +00:00
parent ab92206b77
commit 6be54942ec
14 changed files with 343 additions and 39 deletions

View File

@@ -9,6 +9,7 @@ package net.i2p.client;
*
*/
import java.util.Date;
import java.util.Set;
import net.i2p.I2PAppContext;
@@ -28,6 +29,7 @@ import net.i2p.data.i2cp.DestroySessionMessage;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.ReportAbuseMessage;
import net.i2p.data.i2cp.SendMessageMessage;
import net.i2p.data.i2cp.SendMessageExpiresMessage;
import net.i2p.data.i2cp.SessionConfig;
import net.i2p.util.Log;
@@ -91,8 +93,13 @@ class I2CPMessageProducer {
*
*/
public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag,
SessionKey key, Set tags, SessionKey newKey) throws I2PSessionException {
SendMessageMessage msg = new SendMessageMessage();
SessionKey key, Set tags, SessionKey newKey, long expires) throws I2PSessionException {
SendMessageMessage msg;
if (expires > 0) {
msg = new SendMessageExpiresMessage();
((SendMessageExpiresMessage)msg).setExpiration(new Date(expires));
} else
msg = new SendMessageMessage();
msg.setDestination(dest);
msg.setSessionId(session.getSessionId());
msg.setNonce(nonce);

View File

@@ -70,6 +70,7 @@ public interface I2PSession {
*/
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException;
/** Receive a message that the router has notified the client about, returning
* the payload.

View File

@@ -550,10 +550,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* Pass off the error to the listener
*/
void propogateError(String msg, Throwable error) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "Error occurred: " + msg + " - " + error.getMessage());
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + " cause", error);
if (_log.shouldLog(Log.ERROR))
_log.error(getPrefix() + "Error occurred: " + msg + " - " + error.getMessage());
if (_log.shouldLog(Log.ERROR))
_log.error(getPrefix() + " cause", error);
if (_sessionListener != null) _sessionListener.errorOccurred(this, msg, error);
}

View File

@@ -107,15 +107,19 @@ class I2PSessionImpl2 extends I2PSessionImpl {
return sendMessage(dest, payload, 0, payload.length);
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException {
return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64));
return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0);
}
@Override
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent);
return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent, 0);
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0);
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
if (isClosed()) throw new I2PSessionException("Already closed");
@@ -142,7 +146,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
_context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0);
return sendBestEffort(dest, payload, keyUsed, tagsSent);
return sendBestEffort(dest, payload, keyUsed, tagsSent, expires);
}
/**
@@ -168,7 +172,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private static final int NUM_TAGS = 50;
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException {
SessionKey key = null;
SessionKey newKey = null;
@@ -176,6 +180,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
Set sentTags = null;
int oldTags = 0;
long begin = _context.clock().now();
/***********
if (I2CPMessageProducer.END_TO_END_CRYPTO) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("begin sendBestEffort");
key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
@@ -220,6 +225,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
} else {
// not using end to end crypto, so don't ever bundle any tags
}
**********/
if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce");
@@ -233,14 +239,14 @@ class I2PSessionImpl2 extends I2PSessionImpl {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key);
if (keyUsed != null) {
if (I2CPMessageProducer.END_TO_END_CRYPTO) {
if (newKey != null)
keyUsed.setData(newKey.getData());
else
keyUsed.setData(key.getData());
} else {
//if (I2CPMessageProducer.END_TO_END_CRYPTO) {
// if (newKey != null)
// keyUsed.setData(newKey.getData());
// else
// keyUsed.setData(key.getData());
//} else {
keyUsed.setData(SessionKey.INVALID_KEY.getData());
}
//}
}
if (tagsSent != null) {
if (sentTags != null) {
@@ -261,7 +267,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ state.getNonce() + " for best effort "
+ " sync took " + (inSendingSync-beforeSendingSync)
+ " add took " + (afterSendingSync-inSendingSync));
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires);
// since this is 'best effort', all we're waiting for is a status update
// saying that the router received it - in theory, that should come back

View File

@@ -21,6 +21,7 @@ import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.i2cp.I2CPMessage;
@@ -78,6 +79,17 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
leaseSet.setEncryptionKey(li.getPublicKey());
leaseSet.setSigningKey(li.getSigningPublicKey());
String sk = session.getOptions().getProperty("i2cp.sessionKey");
if (sk != null) {
SessionKey key = new SessionKey();
try {
key.fromBase64(sk);
leaseSet.encrypt(key);
_context.keyRing().put(session.getMyDestination().calculateHash(), key);
} catch (DataFormatException dfe) {
_log.error("Bad session key: " + sk);
}
}
try {
leaseSet.sign(session.getPrivateKey());
session.getProducer().createLeaseSet(session, leaseSet, li.getSigningPrivateKey(), li.getPrivateKey());
@@ -137,4 +149,4 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
&& DataHelper.eq(_signingPrivKey, li.getSigningPrivateKey());
}
}
}
}

View File

@@ -18,7 +18,7 @@ import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Handle messages from the server for the client
* Handle messages from the server for the client or vice versa
*
*/
public class I2CPMessageHandler {
@@ -75,6 +75,8 @@ public class I2CPMessageHandler {
return new RequestLeaseSetMessage();
case SendMessageMessage.MESSAGE_TYPE:
return new SendMessageMessage();
case SendMessageExpiresMessage.MESSAGE_TYPE:
return new SendMessageExpiresMessage();
case SessionStatusMessage.MESSAGE_TYPE:
return new SessionStatusMessage();
case GetDateMessage.MESSAGE_TYPE:

View File

@@ -0,0 +1,103 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Defines the message a client sends to a router when
* updating the config on an existing session.
*
* @author zzz
*/
public class ReconfigureSessionMessage extends I2CPMessageImpl {
private final static Log _log = new Log(ReconfigureSessionMessage.class);
public final static int MESSAGE_TYPE = 2;
private SessionId _sessionId;
private SessionConfig _sessionConfig;
public ReconfigureSessionMessage() {
_sessionId = null;
_sessionConfig = null;
}
public SessionId getSessionId() {
return _sessionId;
}
public void setSessionId(SessionId id) {
_sessionId = id;
}
public SessionConfig getSessionConfig() {
return _sessionConfig;
}
public void setSessionConfig(SessionConfig config) {
_sessionConfig = config;
}
@Override
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
_sessionConfig = new SessionConfig();
_sessionConfig.readBytes(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
@Override
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if (_sessionId == null || _sessionConfig == null)
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(64);
try {
_sessionId.writeBytes(os);
_sessionConfig.writeBytes(os);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
}
public int getType() {
return MESSAGE_TYPE;
}
@Override
public boolean equals(Object object) {
if ((object != null) && (object instanceof ReconfigureSessionMessage)) {
ReconfigureSessionMessage msg = (ReconfigureSessionMessage) object;
return DataHelper.eq(getSessionId(), msg.getSessionId())
&& DataHelper.eq(getSessionConfig(), msg.getSessionConfig());
}
return false;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[ReconfigureSessionMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tSessionConfig: ").append(getSessionConfig());
buf.append("]");
return buf.toString();
}
}

View File

@@ -0,0 +1,117 @@
package net.i2p.data.i2cp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Payload;
import net.i2p.util.Log;
/**
* Same as SendMessageMessage, but with an expiration to be passed to the router
*
* @author zzz
*/
public class SendMessageExpiresMessage extends SendMessageMessage {
private final static Log _log = new Log(SendMessageExpiresMessage.class);
public final static int MESSAGE_TYPE = 36;
private SessionId _sessionId;
private Destination _destination;
private Payload _payload;
private Date _expiration;
public SendMessageExpiresMessage() {
super();
setExpiration(null);
}
public Date getExpiration() {
return _expiration;
}
public void setExpiration(Date d) {
_expiration = d;
}
/**
* Read the body into the data structures
*
* @throws IOException
*/
@Override
public void readMessage(InputStream in, int length, int type) throws I2CPMessageException, IOException {
super.readMessage(in, length, type);
try {
_expiration = DataHelper.readDate(in);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Unable to load the message data", dfe);
}
}
/**
* Write out the full message to the stream, including the 4 byte size and 1
* byte type header. Override the parent so we can be more mem efficient
*
* @throws IOException
*/
@Override
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException {
if ((getSessionId() == null) || (getDestination() == null) || (getPayload() == null) || (getNonce() <= 0) || (_expiration == null))
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
int len = 2 + getDestination().size() + getPayload().getSize() + 4 + 4 + DataHelper.DATE_LENGTH;
try {
DataHelper.writeLong(out, 4, len);
DataHelper.writeLong(out, 1, getType());
getSessionId().writeBytes(out);
getDestination().writeBytes(out);
getPayload().writeBytes(out);
DataHelper.writeLong(out, 4, getNonce());
DataHelper.writeDate(out, _expiration);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing the msg", dfe);
}
}
public int getType() {
return MESSAGE_TYPE;
}
@Override
public boolean equals(Object object) {
if ((object != null) && (object instanceof SendMessageExpiresMessage)) {
SendMessageExpiresMessage msg = (SendMessageExpiresMessage) object;
return super.equals(object)
&& DataHelper.eq(getExpiration(), msg.getExpiration());
}
return false;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[SendMessageMessage: ");
buf.append("\n\tSessionId: ").append(getSessionId());
buf.append("\n\tNonce: ").append(getNonce());
buf.append("\n\tDestination: ").append(getDestination());
buf.append("\n\tExpiration: ").append(getExpiration());
buf.append("\n\tPayload: ").append(getPayload());
buf.append("]");
return buf.toString();
}
}