- Deprecate unused stream methods and I2NPMessageReader since
      all transports provide encapsulation.
    - Don't throw IOE from byte array methods
    - Use cached null cert in GarlicClove
    - Add method to limit size of buffer to read
    - Don't check checksum at input, in most cases
    - Reuse checksum at output, for unomodified pass-through messages
      (but recalculating it now and logging on a mismatch for testing)
    - Fix DatabaseLookupMessage to internally store the don't include peers as
      a List, not a Set, so it doesn't get reordered and break the checksum
    - Log cleanup
  * NTCP:
    - Zero-copy and limit size when handing buffer to I2NP
    - Log hex dump message on I2NPMessageException, like in SSU
    - Don't close connection on I2NPMessageException
This commit is contained in:
zzz
2011-12-11 21:04:43 +00:00
parent 032b7d8230
commit 8448001a17
22 changed files with 671 additions and 284 deletions

View File

@ -8,17 +8,17 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
/**
* Defines a message containing arbitrary bytes of data
* This is what goes in a GarlicClove.
* It was also previously used for generating test messages.
*
* @author jrandom
*/
public class DataMessage extends I2NPMessageImpl {
public class DataMessage extends FastI2NPMessageImpl {
public final static int MESSAGE_TYPE = 20;
private byte _data[];
@ -29,7 +29,13 @@ public class DataMessage extends I2NPMessageImpl {
public byte[] getData() {
return _data;
}
/**
* @throws IllegalStateException if data previously set, to protect saved checksum
*/
public void setData(byte[] data) {
if (_data != null)
throw new IllegalStateException();
_data = data;
}
@ -37,7 +43,7 @@ public class DataMessage extends I2NPMessageImpl {
return _data.length;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
long size = DataHelper.fromLong(data, curIndex, 4);
@ -55,6 +61,7 @@ public class DataMessage extends I2NPMessageImpl {
else
return 4 + _data.length;
}
/** write the message body to the output array, starting at the given index */
protected int writeMessageBody(byte out[], int curIndex) {
if (_data == null) {
@ -76,14 +83,14 @@ public class DataMessage extends I2NPMessageImpl {
@Override
public int hashCode() {
return DataHelper.hashCode(getData());
return DataHelper.hashCode(_data);
}
@Override
public boolean equals(Object object) {
if ( (object != null) && (object instanceof DataMessage) ) {
DataMessage msg = (DataMessage)object;
return DataHelper.eq(getData(),msg.getData());
return DataHelper.eq(_data, msg._data);
} else {
return false;
}
@ -93,7 +100,7 @@ public class DataMessage extends I2NPMessageImpl {
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[DataMessage: ");
buf.append("\n\tData: ").append(DataHelper.toString(getData(), 64));
buf.append("\n\tData: ").append(DataHelper.toString(_data, 64));
buf.append("]");
return buf.toString();
}

View File

@ -8,9 +8,11 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.i2p.I2PAppContext;
@ -25,13 +27,14 @@ import net.i2p.data.TunnelId;
*
* @author jrandom
*/
public class DatabaseLookupMessage extends I2NPMessageImpl {
public class DatabaseLookupMessage extends FastI2NPMessageImpl {
//private final static Log _log = new Log(DatabaseLookupMessage.class);
public final static int MESSAGE_TYPE = 2;
private Hash _key;
private Hash _fromHash;
private TunnelId _replyTunnel;
private Set<Hash> _dontIncludePeers;
/** this must be kept as a list to preserve the order and not break the checksum */
private List<Hash> _dontIncludePeers;
//private static volatile long _currentLookupPeriod = 0;
//private static volatile int _currentLookupCount = 0;
@ -102,36 +105,109 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
* Defines the key being searched for
*/
public Hash getSearchKey() { return _key; }
public void setSearchKey(Hash key) { _key = key; }
/**
* @throws IllegalStateException if key previously set, to protect saved checksum
*/
public void setSearchKey(Hash key) {
if (_key != null)
throw new IllegalStateException();
_key = key;
}
/**
* Contains the router who requested this lookup
*
*/
public Hash getFrom() { return _fromHash; }
public void setFrom(Hash from) { _fromHash = from; }
/**
* @throws IllegalStateException if from previously set, to protect saved checksum
*/
public void setFrom(Hash from) {
if (_fromHash != null)
throw new IllegalStateException();
_fromHash = from;
}
/**
* Contains the tunnel ID a reply should be sent to
*
*/
public TunnelId getReplyTunnel() { return _replyTunnel; }
public void setReplyTunnel(TunnelId replyTunnel) { _replyTunnel = replyTunnel; }
/**
* Set of peers that a lookup reply should NOT include
*
* @return Set of Hash objects, each of which is the H(routerIdentity) to skip
* @throws IllegalStateException if tunnel previously set, to protect saved checksum
*/
public Set<Hash> getDontIncludePeers() { return _dontIncludePeers; }
public void setDontIncludePeers(Set peers) {
public void setReplyTunnel(TunnelId replyTunnel) {
if (_replyTunnel != null)
throw new IllegalStateException();
_replyTunnel = replyTunnel;
}
/**
* Set of peers that a lookup reply should NOT include.
* WARNING - returns a copy.
*
* @return Set of Hash objects, each of which is the H(routerIdentity) to skip, or null
*/
public Set<Hash> getDontIncludePeers() {
if (_dontIncludePeers == null)
return null;
return new HashSet(_dontIncludePeers);
}
/**
* Replace the dontInclude set with this set.
* WARNING - makes a copy.
* Invalidates the checksum.
*
* @param peers may be null
*/
public void setDontIncludePeers(Collection<Hash> peers) {
_hasChecksum = false;
if (peers != null)
_dontIncludePeers = new HashSet(peers);
_dontIncludePeers = new ArrayList(peers);
else
_dontIncludePeers = null;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
/**
* Add to the set.
* Invalidates the checksum.
*
* @param peer non-null
* @since 0.8.12
*/
public void addDontIncludePeer(Hash peer) {
if (_dontIncludePeers == null)
_dontIncludePeers = new ArrayList();
else if (_dontIncludePeers.contains(peer))
return;
_hasChecksum = false;
_dontIncludePeers.add(peer);
}
/**
* Add to the set.
* Invalidates the checksum.
*
* @param peers non-null
* @since 0.8.12
*/
public void addDontIncludePeers(Collection<Hash> peers) {
_hasChecksum = false;
if (_dontIncludePeers == null) {
_dontIncludePeers = new ArrayList(peers);
} else {
for (Hash peer : peers) {
if (!_dontIncludePeers.contains(peer))
_dontIncludePeers.add(peer);
}
}
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
@ -170,7 +246,7 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
if ( (numPeers < 0) || (numPeers > MAX_NUM_PEERS) )
throw new I2NPMessageException("Invalid number of peers - " + numPeers);
Set<Hash> peers = new HashSet(numPeers);
List<Hash> peers = new ArrayList(numPeers);
for (int i = 0; i < numPeers; i++) {
//byte peer[] = new byte[Hash.HASH_LENGTH];
//System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH);
@ -220,8 +296,7 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
byte len[] = DataHelper.toLong(2, size);
out[curIndex++] = len[0];
out[curIndex++] = len[1];
for (Iterator<Hash> iter = _dontIncludePeers.iterator(); iter.hasNext(); ) {
Hash peer = iter.next();
for (Hash peer : _dontIncludePeers) {
System.arraycopy(peer.getData(), 0, out, curIndex, Hash.HASH_LENGTH);
curIndex += Hash.HASH_LENGTH;
}
@ -233,9 +308,9 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
@Override
public int hashCode() {
return DataHelper.hashCode(getSearchKey()) +
DataHelper.hashCode(getFrom()) +
DataHelper.hashCode(getReplyTunnel()) +
return DataHelper.hashCode(_key) +
DataHelper.hashCode(_fromHash) +
DataHelper.hashCode(_replyTunnel) +
DataHelper.hashCode(_dontIncludePeers);
}
@ -243,10 +318,10 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
public boolean equals(Object object) {
if ( (object != null) && (object instanceof DatabaseLookupMessage) ) {
DatabaseLookupMessage msg = (DatabaseLookupMessage)object;
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
DataHelper.eq(getFrom(),msg.getFrom()) &&
DataHelper.eq(getReplyTunnel(),msg.getReplyTunnel()) &&
DataHelper.eq(_dontIncludePeers,msg.getDontIncludePeers());
return DataHelper.eq(_key, msg._key) &&
DataHelper.eq(_fromHash, msg._fromHash) &&
DataHelper.eq(_replyTunnel, msg._replyTunnel) &&
DataHelper.eq(_dontIncludePeers, msg._dontIncludePeers);
} else {
return false;
}
@ -256,9 +331,9 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[DatabaseLookupMessage: ");
buf.append("\n\tSearch Key: ").append(getSearchKey());
buf.append("\n\tFrom: ").append(getFrom());
buf.append("\n\tReply Tunnel: ").append(getReplyTunnel());
buf.append("\n\tSearch Key: ").append(_key);
buf.append("\n\tFrom: ").append(_fromHash);
buf.append("\n\tReply Tunnel: ").append(_replyTunnel);
buf.append("\n\tDont Include Peers: ");
if (_dontIncludePeers != null)
buf.append(_dontIncludePeers.size());

View File

@ -8,7 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -23,7 +22,7 @@ import net.i2p.data.Hash;
*
* @author jrandom
*/
public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
public class DatabaseSearchReplyMessage extends FastI2NPMessageImpl {
public final static int MESSAGE_TYPE = 3;
private Hash _key;
private List<Hash> _peerHashes;
@ -41,7 +40,15 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
* Defines the key being searched for
*/
public Hash getSearchKey() { return _key; }
public void setSearchKey(Hash key) { _key = key; }
/**
* @throws IllegalStateException if key previously set, to protect saved checksum
*/
public void setSearchKey(Hash key) {
if (_key != null)
throw new IllegalStateException();
_key = key;
}
public int getNumReplies() { return _peerHashes.size(); }
public Hash getReply(int index) { return _peerHashes.get(index); }
@ -51,7 +58,7 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
public Hash getFromHash() { return _from; }
public void setFromHash(Hash from) { _from = from; }
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
@ -114,8 +121,8 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
public boolean equals(Object object) {
if ( (object != null) && (object instanceof DatabaseSearchReplyMessage) ) {
DatabaseSearchReplyMessage msg = (DatabaseSearchReplyMessage)object;
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
DataHelper.eq(getFromHash(),msg.getFromHash()) &&
return DataHelper.eq(_key,msg._key) &&
DataHelper.eq(_from,msg._from) &&
DataHelper.eq(_peerHashes,msg._peerHashes);
} else {
return false;
@ -124,8 +131,8 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
@Override
public int hashCode() {
return DataHelper.hashCode(getSearchKey()) +
DataHelper.hashCode(getFromHash()) +
return DataHelper.hashCode(_key) +
DataHelper.hashCode(_from) +
DataHelper.hashCode(_peerHashes);
}
@ -133,12 +140,12 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[DatabaseSearchReplyMessage: ");
buf.append("\n\tSearch Key: ").append(getSearchKey());
buf.append("\n\tSearch Key: ").append(_key);
buf.append("\n\tReplies: # = ").append(getNumReplies());
for (int i = 0; i < getNumReplies(); i++) {
buf.append("\n\t\tReply [").append(i).append("]: ").append(getReply(i));
}
buf.append("\n\tFrom: ").append(getFromHash());
buf.append("\n\tFrom: ").append(_from);
buf.append("]");
return buf.toString();
}

View File

@ -29,7 +29,7 @@ import net.i2p.data.TunnelId;
*
* @author jrandom
*/
public class DatabaseStoreMessage extends I2NPMessageImpl {
public class DatabaseStoreMessage extends FastI2NPMessageImpl {
public final static int MESSAGE_TYPE = 1;
private Hash _key;
private DatabaseEntry _dbEntry;
@ -61,8 +61,11 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
/**
* This also sets the key
* @throws IllegalStateException if data previously set, to protect saved checksum
*/
public void setEntry(DatabaseEntry entry) {
if (_dbEntry != null)
throw new IllegalStateException();
_dbEntry = entry;
}
@ -94,7 +97,7 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
public Hash getReplyGateway() { return _replyGateway; }
public void setReplyGateway(Hash peer) { _replyGateway = peer; }
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
@ -126,14 +129,16 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
_dbEntry.readBytes(new ByteArrayInputStream(data, curIndex, data.length-curIndex));
} catch (DataFormatException dfe) {
throw new I2NPMessageException("Error reading the leaseSet", dfe);
} catch (IOException ioe) {
throw new I2NPMessageException("Error reading the leaseSet", ioe);
}
} else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
_dbEntry = new RouterInfo();
int compressedSize = (int)DataHelper.fromLong(data, curIndex, 2);
curIndex += 2;
if (compressedSize <= 0 || curIndex + compressedSize > data.length || (curIndex - offset) + compressedSize > dataSize)
if (compressedSize <= 0 || curIndex + compressedSize > data.length || curIndex + compressedSize > dataSize + offset)
throw new I2NPMessageException("Compressed RI length: " + compressedSize +
" but remaining bytes: " + Math.min(data.length - curIndex, dataSize - (curIndex - offset)));
" but remaining bytes: " + Math.min(data.length - curIndex, dataSize + offset -curIndex));
try {
// TODO we could delay decompression, just copy to a new byte array and store in _byteCache
@ -230,9 +235,9 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
public int hashCode() {
return DataHelper.hashCode(getKey()) +
DataHelper.hashCode(_dbEntry) +
(int)getReplyToken() +
DataHelper.hashCode(getReplyTunnel()) +
DataHelper.hashCode(getReplyGateway());
(int) _replyToken +
DataHelper.hashCode(_replyTunnel) +
DataHelper.hashCode(_replyGateway);
}
@Override
@ -241,9 +246,9 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
DatabaseStoreMessage msg = (DatabaseStoreMessage)object;
return DataHelper.eq(getKey(),msg.getKey()) &&
DataHelper.eq(_dbEntry,msg.getEntry()) &&
getReplyToken() == msg.getReplyToken() &&
DataHelper.eq(getReplyTunnel(), msg.getReplyTunnel()) &&
DataHelper.eq(getReplyGateway(), msg.getReplyGateway());
_replyToken == msg._replyToken &&
DataHelper.eq(_replyTunnel, msg._replyTunnel) &&
DataHelper.eq(_replyGateway, msg._replyGateway);
} else {
return false;
}
@ -253,13 +258,13 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[DatabaseStoreMessage: ");
buf.append("\n\tExpiration: ").append(getMessageExpiration());
buf.append("\n\tUnique ID: ").append(getUniqueId());
buf.append("\n\tExpiration: ").append(_expiration);
buf.append("\n\tUnique ID: ").append(_uniqueId);
buf.append("\n\tKey: ").append(getKey());
buf.append("\n\tEntry: ").append(_dbEntry);
buf.append("\n\tReply token: ").append(getReplyToken());
buf.append("\n\tReply tunnel: ").append(getReplyTunnel());
buf.append("\n\tReply gateway: ").append(getReplyGateway());
buf.append("\n\tReply token: ").append(_replyToken);
buf.append("\n\tReply tunnel: ").append(_replyTunnel);
buf.append("\n\tReply gateway: ").append(_replyGateway);
buf.append("]");
return buf.toString();
}

View File

@ -18,7 +18,7 @@ import net.i2p.data.DataStructureImpl;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.data.TunnelId;
import net.i2p.util.Log;
//import net.i2p.util.Log;
/**
@ -32,7 +32,7 @@ import net.i2p.util.Log;
* @author jrandom
*/
public class DeliveryInstructions extends DataStructureImpl {
private final static Log _log = new Log(DeliveryInstructions.class);
//private final static Log _log = new Log(DeliveryInstructions.class);
private boolean _encrypted;
private SessionKey _encryptionKey;
private int _deliveryMode;
@ -57,7 +57,7 @@ public class DeliveryInstructions extends DataStructureImpl {
private final static long FLAG_DELAY = 16;
public DeliveryInstructions() {
setDeliveryMode(-1);
_deliveryMode = -1;
}
/**
@ -132,10 +132,13 @@ public class DeliveryInstructions extends DataStructureImpl {
*/
public void setDelaySeconds(long seconds) { _delaySeconds = seconds; }
/**
* @deprecated unused
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
long flags = DataHelper.readLong(in, 1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
/****
if (flagEncrypted(flags)) {
@ -188,8 +191,8 @@ public class DeliveryInstructions extends DataStructureImpl {
int cur = offset;
long flags = DataHelper.fromLong(data, cur, 1);
cur++;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
/****
if (flagEncrypted(flags)) {
@ -289,8 +292,8 @@ public class DeliveryInstructions extends DataStructureImpl {
val = val | fmode;
if (getDelayRequested())
val = val | FLAG_DELAY;
if (_log.shouldLog(Log.DEBUG))
_log.debug("getFlags() = " + val);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("getFlags() = " + val);
return val;
}
@ -304,8 +307,8 @@ public class DeliveryInstructions extends DataStructureImpl {
****/
switch (getDeliveryMode()) {
case FLAG_MODE_LOCAL:
if (_log.shouldLog(Log.DEBUG))
_log.debug("mode = local");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("mode = local");
break;
case FLAG_MODE_DESTINATION:
if (_destinationHash == null) throw new IllegalStateException("Destination hash is not set");
@ -334,7 +337,8 @@ public class DeliveryInstructions extends DataStructureImpl {
int offset = 0;
offset += getAdditionalInfo(rv, offset);
if (offset != additionalSize)
_log.log(Log.CRIT, "wtf, additionalSize = " + additionalSize + ", offset = " + offset);
//_log.log(Log.CRIT, "wtf, additionalSize = " + additionalSize + ", offset = " + offset);
throw new IllegalStateException("wtf, additionalSize = " + additionalSize + ", offset = " + offset);
return rv;
}
@ -356,22 +360,22 @@ public class DeliveryInstructions extends DataStructureImpl {
switch (getDeliveryMode()) {
case FLAG_MODE_LOCAL:
if (_log.shouldLog(Log.DEBUG))
_log.debug("mode = local");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("mode = local");
break;
case FLAG_MODE_DESTINATION:
if (_destinationHash == null) throw new IllegalStateException("Destination hash is not set");
System.arraycopy(_destinationHash.getData(), 0, rv, offset, Hash.HASH_LENGTH);
offset += Hash.HASH_LENGTH;
if (_log.shouldLog(Log.DEBUG))
_log.debug("mode = destination, hash = " + _destinationHash);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("mode = destination, hash = " + _destinationHash);
break;
case FLAG_MODE_ROUTER:
if (_routerHash == null) throw new IllegalStateException("Router hash is not set");
System.arraycopy(_routerHash.getData(), 0, rv, offset, Hash.HASH_LENGTH);
offset += Hash.HASH_LENGTH;
if (_log.shouldLog(Log.DEBUG))
_log.debug("mode = router, routerHash = " + _routerHash);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("mode = router, routerHash = " + _routerHash);
break;
case FLAG_MODE_TUNNEL:
if ( (_routerHash == null) || (_tunnelId == null) ) throw new IllegalStateException("Router hash or tunnel ID is not set");
@ -379,29 +383,32 @@ public class DeliveryInstructions extends DataStructureImpl {
offset += Hash.HASH_LENGTH;
DataHelper.toLong(rv, offset, 4, _tunnelId.getTunnelId());
offset += 4;
if (_log.shouldLog(Log.DEBUG))
_log.debug("mode = tunnel, tunnelId = " + _tunnelId.getTunnelId()
+ ", routerHash = " + _routerHash);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("mode = tunnel, tunnelId = " + _tunnelId.getTunnelId()
// + ", routerHash = " + _routerHash);
break;
}
if (getDelayRequested()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("delay requested: " + getDelaySeconds());
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("delay requested: " + getDelaySeconds());
DataHelper.toLong(rv, offset, 4, getDelaySeconds());
offset += 4;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("delay NOT requested");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("delay NOT requested");
}
return offset - origOffset;
}
/**
* @deprecated unused
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new DataFormatException("Invalid data: mode = " + _deliveryMode);
long flags = getFlags();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Write flags: " + flags + " mode: " + getDeliveryMode()
+ " =?= " + flagMode(flags));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Write flags: " + flags + " mode: " + getDeliveryMode()
// + " =?= " + flagMode(flags));
byte additionalInfo[] = getAdditionalInfo();
DataHelper.writeLong(out, 1, flags);
if (additionalInfo != null) {
@ -416,9 +423,9 @@ public class DeliveryInstructions extends DataStructureImpl {
public int writeBytes(byte target[], int offset) {
if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new IllegalStateException("Invalid data: mode = " + _deliveryMode);
long flags = getFlags();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Write flags: " + flags + " mode: " + getDeliveryMode()
+ " =?= " + flagMode(flags));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Write flags: " + flags + " mode: " + getDeliveryMode()
// + " =?= " + flagMode(flags));
int origOffset = offset;
DataHelper.toLong(target, offset, 1, flags);
offset++;

View File

@ -8,8 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
@ -19,24 +17,47 @@ import net.i2p.data.DataHelper;
*
* @author jrandom
*/
public class DeliveryStatusMessage extends I2NPMessageImpl {
public class DeliveryStatusMessage extends FastI2NPMessageImpl {
public final static int MESSAGE_TYPE = 10;
private long _id;
private long _arrival;
public DeliveryStatusMessage(I2PAppContext context) {
super(context);
setMessageId(-1);
setArrival(-1);
_id = -1;
_arrival = -1;
}
public long getMessageId() { return _id; }
public void setMessageId(long id) { _id = id; }
/**
* @throws IllegalStateException if id previously set, to protect saved checksum
*/
public void setMessageId(long id) {
if (_id >= 0)
throw new IllegalStateException();
_id = id;
}
/**
* Misnamed, as it is generally (always?) set by the creator to the current time,
* in some future usage it could be set on arrival
*/
public long getArrival() { return _arrival; }
public void setArrival(long arrival) { _arrival = arrival; }
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
/**
* Misnamed, as it is generally (always?) set by the creator to the current time,
* in some future usage it could be set on arrival
*/
public void setArrival(long arrival) {
// To accomodate setting on arrival,
// invalidate the stored checksum instead of throwing ISE
if (_arrival >= 0)
_hasChecksum = false;
_arrival = arrival;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;

View File

@ -0,0 +1,186 @@
package net.i2p.data.i2np;
/*
* 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 net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
import net.i2p.util.SimpleByteCache;
/**
* Ignore, but save, the SHA-256 checksum in the full 16-byte header when read in.
* Use the same checksum when writing out.
*
* This is a savings for NTCP in,
* and for NTCP-in to NTCP-out for TunnelDataMessages.
* It's also a savings for messages embedded in other messages.
* Note that SSU does not use the SHA-256 checksum.
*
* Subclasses must take care to set _hasChecksum to false to invalidate it
* if the message payload changes between reading and writing.
*
* It isn't clear where, if anywhere, we actually need to send a checksum.
* For point-to-point messages over NTCP where we know the router version
* of the peer, we could add a method to skip checksum generation.
* For end-to-end I2NP messages embedded in a Garlic, TGM, etc...
* we would need a flag day.
*
* @since 0.8.12
*/
public abstract class FastI2NPMessageImpl extends I2NPMessageImpl {
protected byte _checksum;
// We skip the fiction that CHECKSUM_LENGTH will ever be anything but 1
protected boolean _hasChecksum;
public FastI2NPMessageImpl(I2PAppContext context) {
super(context);
}
/**
* @deprecated unused
* @throws UnsupportedOperationException
*/
@Override
public void readBytes(InputStream in) throws DataFormatException, IOException {
throw new UnsupportedOperationException();
}
/**
* @deprecated unused
* @throws UnsupportedOperationException
*/
@Override
public int readBytes(InputStream in, int type, byte buffer[]) throws I2NPMessageException, IOException {
throw new UnsupportedOperationException();
}
/**
* Ignore, but save, the checksum, to be used later if necessary.
*
* @param maxLen read no more than this many bytes from data starting at offset, even if it is longer
* This includes the type byte only if type < 0
* @throws IllegalStateException if called twice, to protect saved checksum
*/
@Override
public int readBytes(byte data[], int type, int offset, int maxLen) throws I2NPMessageException {
if (_hasChecksum)
throw new IllegalStateException(getClass().getSimpleName() + " read twice");
int headerSize = HEADER_LENGTH;
if (type >= 0)
headerSize--;
if (maxLen < headerSize)
throw new I2NPMessageException("Payload is too short " + maxLen);
int cur = offset;
if (type < 0) {
type = (int)DataHelper.fromLong(data, cur, 1);
cur++;
}
_uniqueId = DataHelper.fromLong(data, cur, 4);
cur += 4;
_expiration = DataHelper.fromLong(data, cur, DataHelper.DATE_LENGTH);
cur += DataHelper.DATE_LENGTH;
int size = (int)DataHelper.fromLong(data, cur, 2);
cur += 2;
_checksum = data[cur];
cur++;
if (cur + size > data.length || headerSize + size > maxLen)
throw new I2NPMessageException("Payload is too short ["
+ "data.len=" + data.length
+ "maxLen=" + maxLen
+ " offset=" + offset
+ " cur=" + cur
+ " wanted=" + size + "]: " + getClass().getSimpleName());
int sz = Math.min(size, maxLen - headerSize);
readMessage(data, cur, sz, type);
cur += sz;
_hasChecksum = true;
if (VERIFY_TEST && _log.shouldLog(Log.INFO))
_log.info("Ignored c/s " + getClass().getSimpleName());
return cur - offset;
}
/**
* @deprecated unused
* @throws UnsupportedOperationException
*/
@Override
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
throw new UnsupportedOperationException();
}
/**
* This tests the reuse-checksum feature.
* The results are that mostly UnknownI2NPMessages (from inside a TGM),
* with a lot of DeliveryStatusMessages,
* and a few DatabaseLookupMessages that get reused.
* The last two are tiny, but the savings at the gateway should help.
*/
private static final boolean VERIFY_TEST = true;
/**
* If available, use the previously-computed or previously-read checksum for speed
*/
@Override
public int toByteArray(byte buffer[]) {
if (_hasChecksum)
return toByteArrayWithSavedChecksum(buffer);
if (VERIFY_TEST && _log.shouldLog(Log.INFO))
_log.info("Generating new c/s " + getClass().getSimpleName());
return super.toByteArray(buffer);
}
/**
* Use a previously-computed checksum for speed
*/
protected int toByteArrayWithSavedChecksum(byte buffer[]) {
try {
int writtenLen = writeMessageBody(buffer, HEADER_LENGTH);
if (VERIFY_TEST) {
byte[] h = SimpleByteCache.acquire(32);
_context.sha().calculateHash(buffer, HEADER_LENGTH, writtenLen - HEADER_LENGTH, h, 0);
if (h[0] != _checksum) {
_log.log(Log.CRIT, "Please report " + getClass().getSimpleName() +
" size " + writtenLen +
" saved c/s " + Integer.toHexString(_checksum & 0xff) +
" calc " + Integer.toHexString(h[0] & 0xff), new Exception());
_log.log(Log.CRIT, "DUMP:\n" + HexDump.dump(buffer, HEADER_LENGTH, writtenLen - HEADER_LENGTH));
_log.log(Log.CRIT, "RAW:\n" + Base64.encode(buffer, HEADER_LENGTH, writtenLen - HEADER_LENGTH));
_checksum = h[0];
} else if (_log.shouldLog(Log.INFO)) {
_log.info("Using saved c/s " + getClass().getSimpleName() + ' ' + _checksum);
}
SimpleByteCache.release(h);
}
int payloadLen = writtenLen - HEADER_LENGTH;
int off = 0;
DataHelper.toLong(buffer, off, 1, getType());
off += 1;
DataHelper.toLong(buffer, off, 4, _uniqueId);
off += 4;
DataHelper.toLong(buffer, off, DataHelper.DATE_LENGTH, _expiration);
off += DataHelper.DATE_LENGTH;
DataHelper.toLong(buffer, off, 2, payloadLen);
off += 2;
buffer[off] = _checksum;
return writtenLen;
} catch (I2NPMessageException ime) {
_context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime);
}
}
}

View File

@ -24,23 +24,25 @@ import net.i2p.util.Log;
* Contains one deliverable message encrypted to a router along with instructions
* and a certificate 'paying for' the delivery.
*
* Note that certificates are always the null certificate at this time, others are unimplemented.
*
* @author jrandom
*/
public class GarlicClove extends DataStructureImpl {
private Log _log;
private RouterContext _context;
private final Log _log;
//private final RouterContext _context;
private DeliveryInstructions _instructions;
private I2NPMessage _msg;
private long _cloveId;
private Date _expiration;
private Certificate _certificate;
private I2NPMessageHandler _handler;
private final I2NPMessageHandler _handler;
public GarlicClove(RouterContext context) {
_context = context;
//_context = context;
_log = context.logManager().getLog(GarlicClove.class);
_handler = new I2NPMessageHandler(context);
setCloveId(-1);
_cloveId = -1;
}
public DeliveryInstructions getInstructions() { return _instructions; }
@ -54,6 +56,9 @@ public class GarlicClove extends DataStructureImpl {
public Certificate getCertificate() { return _certificate; }
public void setCertificate(Certificate cert) { _certificate = cert; }
/**
* @deprecated unused, use byte array method to avoid copying
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
_instructions = new DeliveryInstructions();
_instructions.readBytes(in);
@ -86,8 +91,6 @@ public class GarlicClove extends DataStructureImpl {
_msg = _handler.lastRead();
} catch (I2NPMessageException ime) {
throw new DataFormatException("Unable to read the message from a garlic clove", ime);
} catch (IOException ioe) {
throw new DataFormatException("Not enough data to read the clove", ioe);
}
_cloveId = DataHelper.fromLong(source, cur, 4);
cur += 4;
@ -95,14 +98,18 @@ public class GarlicClove extends DataStructureImpl {
cur += DataHelper.DATE_LENGTH;
if (_log.shouldLog(Log.DEBUG))
_log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration);
_certificate = new Certificate();
cur += _certificate.readBytes(source, cur);
//_certificate = new Certificate();
//cur += _certificate.readBytes(source, cur);
_certificate = Certificate.create(source, cur);
cur += _certificate.size();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read cert: " + _certificate);
return cur - offset;
}
/**
* @deprecated unused, use byte array method to avoid copying
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
StringBuilder error = null;
if (_instructions == null) {

View File

@ -8,8 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
@ -18,7 +16,7 @@ import net.i2p.data.DataHelper;
*
* @author jrandom
*/
public class GarlicMessage extends I2NPMessageImpl {
public class GarlicMessage extends FastI2NPMessageImpl {
public final static int MESSAGE_TYPE = 11;
private byte[] _data;
@ -29,11 +27,17 @@ public class GarlicMessage extends I2NPMessageImpl {
public byte[] getData() {
return _data;
}
/**
* @throws IllegalStateException if data previously set, to protect saved checksum
*/
public void setData(byte[] data) {
if (_data != null)
throw new IllegalStateException();
_data = data;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;

View File

@ -26,16 +26,53 @@ public interface I2NPMessage extends DataStructure {
* Read the body into the data structures, after the initial type byte, using
* the current class's format as defined by the I2NP specification
*
* Unused - All transports provide encapsulation and so we have byte arrays available.
*
* @param in stream to read from
* @param type I2NP message type
* starting at type if type is < 0 (16 byte header)
* starting at ID if type is >= 0 (15 byte header)
* @param type I2NP message type. If less than zero, read the type from data
* @param buffer scratch buffer to be used when reading and parsing
* @return size of the message read (including headers)
* @throws I2NPMessageException if the stream doesn't contain a valid message
* that this class can read.
* @throws IOException if there is a problem reading from the stream
* @deprecated unused
*/
public int readBytes(InputStream in, int type, byte buffer[]) throws I2NPMessageException, IOException;
public int readBytes(byte data[], int type, int offset) throws I2NPMessageException, IOException;
/**
* Read the body into the data structures, after the initial type byte, using
* the current class's format as defined by the I2NP specification
*
* @param data the data
* @param type I2NP message type. If less than zero, read the type from data
* @param offset where to start
* starting at type if type is < 0 (16 byte header)
* starting at ID if type is >= 0 (15 byte header)
* @return size of the message read (including headers)
* @throws I2NPMessageException if there is no valid message
* @throws IOException if there is a problem reading from the stream
*/
public int readBytes(byte data[], int type, int offset) throws I2NPMessageException;
/**
* Read the body into the data structures, after the initial type byte, using
* the current class's format as defined by the I2NP specification
*
* @param data the data, may or may not include the type
* @param type I2NP message type. If less than zero, read the type from data
* @param offset where to start
* starting at type if type is < 0 (16 byte header)
* starting at ID if type is >= 0 (15 byte header)
* @param maxLen read no more than this many bytes from data starting at offset, even if it is longer
* This includes the type byte only if type < 0
* @return size of the message read (including headers)
* @throws I2NPMessageException if there is no valid message
* @throws IOException if there is a problem reading from the stream
* @since 0.8.12
*/
public int readBytes(byte data[], int type, int offset, int maxLen) throws I2NPMessageException;
/**
* Read the body into the data structures, after the initial type byte and
@ -50,8 +87,8 @@ public interface I2NPMessage extends DataStructure {
* that this class can read.
* @throws IOException if there is a problem reading from the stream
*/
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException;
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException, IOException;
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException;
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException;
/**
* Return the unique identifier for this type of I2NP message, as defined in
@ -73,22 +110,25 @@ public interface I2NPMessage extends DataStructure {
public void setMessageExpiration(long exp);
/** How large the message is, including any checksums */
/** How large the message is, including any checksums, i.e. full 16 byte header */
public int getMessageSize();
/** How large the raw message is */
public int getRawMessageSize();
/** How large the raw message is with the short 5 byte header */
public int getRawMessageSize();
/**
* write the message to the buffer, returning the number of bytes written.
* the data is formatted so as to be self contained, with the type, size,
* expiration, unique id, as well as a checksum bundled along.
* Full 16 byte header.
*/
public int toByteArray(byte buffer[]);
/**
* write the message to the buffer, returning the number of bytes written.
* the data is is not self contained - it does not include the size,
* unique id, or any checksum, but does include the type and expiration.
* Short 5 byte header.
*/
public int toRawByteArray(byte buffer[]);
}

View File

@ -39,7 +39,11 @@ public class I2NPMessageHandler {
/**
* Read an I2NPMessage from the stream and return the fully populated object.
*
* @throws IOException if there is an IO problem reading from the stream
* This is only called by I2NPMessageReader which is unused.
* All transports provide encapsulation and so we have byte arrays available.
*
* @deprecated use the byte array method to avoid an extra copy if you have it
*
* @throws I2NPMessageException if there is a problem handling the particular
* message - if it is an unknown type or has improper formatting, etc.
*/
@ -54,15 +58,12 @@ public class I2NPMessageHandler {
// throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
try {
_lastSize = msg.readBytes(in, type, _messageBuffer);
} catch (IOException ioe) {
throw ioe;
} catch (I2NPMessageException ime) {
throw ime;
} catch (Exception e) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error reading the stream", e);
throw new IOException("Unknown error reading the " + msg.getClass().getName()
+ ": " + e.getMessage());
throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e);
}
_lastReadEnd = System.currentTimeMillis();
return msg;
@ -79,19 +80,31 @@ public class I2NPMessageHandler {
}
/**
* Read an I2NPMessage from the stream and return the fully populated object.
* Read an I2NPMessage from the byte array and return the fully populated object.
*
* @throws IOException if there is an IO problem reading from the stream
* @throws I2NPMessageException if there is a problem handling the particular
* message - if it is an unknown type or has improper formatting, etc.
*/
public I2NPMessage readMessage(byte data[]) throws IOException, I2NPMessageException {
readMessage(data, 0);
public I2NPMessage readMessage(byte data[]) throws I2NPMessageException {
readMessage(data, 0, data.length);
return lastRead();
}
public int readMessage(byte data[], int offset) throws IOException, I2NPMessageException {
public int readMessage(byte data[], int offset) throws I2NPMessageException {
return readMessage(data, offset, data.length - offset);
}
/**
* Set a limit on the max to read from the data buffer, so that
* we can use a large buffer but prevent the reader from reading off the end.
*
* @param maxLen read no more than this many bytes from data starting at offset, even if it is longer
* must be at least 16
* @since 0.8.12
*/
public int readMessage(byte data[], int offset, int maxLen) throws I2NPMessageException {
int cur = offset;
// we will assume that maxLen is >= 1 here. It's checked to be >= 16 in readBytes()
int type = (int)DataHelper.fromLong(data, cur, 1);
cur++;
_lastReadBegin = System.currentTimeMillis();
@ -110,17 +123,14 @@ public class I2NPMessageHandler {
// + sz + " all zeros? " + allZero + ")");
//}
try {
_lastSize = msg.readBytes(data, type, cur);
_lastSize = msg.readBytes(data, type, cur, maxLen - 1);
cur += _lastSize;
} catch (IOException ioe) {
throw ioe;
} catch (I2NPMessageException ime) {
throw ime;
} catch (Exception e) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error reading the stream", e);
throw new IOException("Unknown error reading the " + msg.getClass().getName()
+ ": " + e.getMessage());
throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e);
}
_lastReadEnd = System.currentTimeMillis();
_lastRead = msg;

View File

@ -30,12 +30,19 @@ import net.i2p.util.SimpleByteCache;
public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPMessage {
protected final Log _log;
protected final I2PAppContext _context;
private long _expiration;
private long _uniqueId;
protected long _expiration;
protected long _uniqueId;
public final static long DEFAULT_EXPIRATION_MS = 1*60*1000; // 1 minute by default
public final static int CHECKSUM_LENGTH = 1; //Hash.HASH_LENGTH;
/** 16 */
public static final int HEADER_LENGTH = 1 // type
+ 4 // uniqueId
+ DataHelper.DATE_LENGTH // expiration
+ 2 // payload length
+ CHECKSUM_LENGTH;
// Whether SSU used the full header or a truncated header.
// We are stuck with the short header, can't change it now.
//private static final boolean RAW_FULL_SIZE = false;
@ -62,8 +69,10 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
}
/**
* Read the whole message (including the type) and throw it away.
* @deprecated Unused, why would you do this
* Read the whole message but only if it's exactly 1024 bytes.
* Unused - All transports provide encapsulation and so we have byte arrays available.
*
* @deprecated unused
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
try {
@ -77,6 +86,9 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
* Read the header, then read the rest into buffer, then call
* readMessage in the implemented message type
*
* This does a copy from the stream to the buffer, so if you already
* have a byte array, use the other readBytes() instead.
*
*<pre>
* Specifically:
* 1 byte type (if caller didn't read already, as specified by the type param
@ -87,9 +99,12 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
* size bytes of payload (read by readMessage() in implementation)
*</pre>
*
* Unused - All transports provide encapsulation and so we have byte arrays available.
*
* @param type the message type or -1 if we should read it here
* @param buffer temp buffer to use
* @return total length of the message
* @deprecated unused
*/
public int readBytes(InputStream in, int type, byte buffer[]) throws I2NPMessageException, IOException {
try {
@ -156,7 +171,24 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
* @param type the message type or -1 if we should read it here
* @return total length of the message
*/
public int readBytes(byte data[], int type, int offset) throws I2NPMessageException, IOException {
public int readBytes(byte data[], int type, int offset) throws I2NPMessageException {
return readBytes(data, type, offset, data.length - offset);
}
/**
* Set a limit on the max to read from the data buffer, so that
* we can use a large buffer but prevent the reader from reading off the end.
*
* @param maxLen read no more than this many bytes from data starting at offset, even if it is longer
* This includes the type byte only if type < 0
* @since 0.8.12
*/
public int readBytes(byte data[], int type, int offset, int maxLen) throws I2NPMessageException {
int headerSize = HEADER_LENGTH;
if (type >= 0)
headerSize--;
if (maxLen < headerSize)
throw new I2NPMessageException("Payload is too short " + maxLen);
int cur = offset;
if (type < 0) {
type = (int)DataHelper.fromLong(data, cur, 1);
@ -174,15 +206,17 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
cur += CHECKSUM_LENGTH;
//h.setData(hdata);
if (cur + size > data.length)
if (cur + size > data.length || headerSize + size > maxLen)
throw new I2NPMessageException("Payload is too short ["
+ "data.len=" + data.length
+ "maxLen=" + maxLen
+ " offset=" + offset
+ " cur=" + cur
+ " wanted=" + size + "]: " + getClass().getName());
+ " wanted=" + size + "]: " + getClass().getSimpleName());
int sz = Math.min(size, maxLen - headerSize);
byte[] calc = SimpleByteCache.acquire(Hash.HASH_LENGTH);
_context.sha().calculateHash(data, cur, size, calc, 0);
_context.sha().calculateHash(data, cur, sz, calc, 0);
boolean eq = DataHelper.eq(hdata, 0, calc, 0, CHECKSUM_LENGTH);
SimpleByteCache.release(calc);
if (!eq)
@ -191,14 +225,19 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
//long start = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reading bytes: type = " + type + " / uniqueId : " + _uniqueId + " / expiration : " + _expiration);
readMessage(data, cur, size, type);
cur += size;
readMessage(data, cur, sz, type);
cur += sz;
//long time = _context.clock().now() - start;
//if (time > 50)
// _context.statManager().addRateData("i2np.readTime", time, time);
return cur - offset;
}
/**
* Don't do this if you need a byte array - use toByteArray()
*
* @deprecated unused
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
int size = getMessageSize();
if (size < 15 + CHECKSUM_LENGTH) throw new DataFormatException("Unable to build the message");
@ -242,31 +281,18 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
int written = toByteArray(data);
if (written != data.length) {
_log.log(Log.CRIT, "Error writing out " + data.length + " (written: " + written + ", msgSize: " + getMessageSize() +
", writtenLen: " + calculateWrittenLength() + ") for " + getClass().getName());
", writtenLen: " + calculateWrittenLength() + ") for " + getClass().getSimpleName());
return null;
}
return data;
}
public int toByteArray(byte buffer[]) {
//long start = _context.clock().now();
int prefixLen = 1 // type
+ 4 // uniqueId
+ DataHelper.DATE_LENGTH // expiration
+ 2 // payload length
+ CHECKSUM_LENGTH; // walnuts
//byte prefix[][] = new byte[][] { DataHelper.toLong(1, getType()),
// DataHelper.toLong(4, _uniqueId),
// DataHelper.toLong(DataHelper.DATE_LENGTH, _expiration),
// new byte[2],
// new byte[CHECKSUM_LENGTH]};
//byte suffix[][] = new byte[][] { };
try {
int writtenLen = writeMessageBody(buffer, prefixLen);
int payloadLen = writtenLen - prefixLen;
int writtenLen = writeMessageBody(buffer, HEADER_LENGTH);
int payloadLen = writtenLen - HEADER_LENGTH;
byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH);
_context.sha().calculateHash(buffer, prefixLen, payloadLen, h, 0);
_context.sha().calculateHash(buffer, HEADER_LENGTH, payloadLen, h, 0);
int off = 0;
DataHelper.toLong(buffer, off, 1, getType());
@ -280,25 +306,22 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
System.arraycopy(h, 0, buffer, off, CHECKSUM_LENGTH);
SimpleByteCache.release(h);
//long time = _context.clock().now() - start;
//if (time > 50)
// _context.statManager().addRateData("i2np.writeTime", time, time);
return writtenLen;
} catch (I2NPMessageException ime) {
_context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
throw new IllegalStateException("Unable to serialize the message (" + getClass().getName()
+ "): " + ime.getMessage());
throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime);
}
}
/** calculate the message body's length (not including the header and footer */
protected abstract int calculateWrittenLength();
/**
* write the message body to the output array, starting at the given index.
* @return the index into the array after the last byte written
*/
protected abstract int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException;
/*
protected int toByteArray(byte out[], byte[][] prefix, byte[][] suffix) throws I2NPMessageException {
int curIndex = 0;
@ -338,12 +361,11 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
return writeMessageBody(buffer, off);
} catch (I2NPMessageException ime) {
_context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
throw new IllegalStateException("Unable to serialize the message (" + getClass().getName()
+ "): " + ime.getMessage());
throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime);
}
}
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException {
// ignore the handler (overridden in subclasses if necessary
try {
readMessage(data, offset, dataSize, type);
@ -388,8 +410,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
msg.readMessage(buffer, offset, dataSize, type, handler);
msg.setMessageExpiration(expiration);
return msg;
} catch (IOException ioe) {
throw new I2NPMessageException("IO error reading raw message", ioe);
} catch (IllegalArgumentException iae) {
throw new I2NPMessageException("Corrupt message (negative expiration)", iae);
}

View File

@ -23,6 +23,15 @@ import net.i2p.util.Log;
* thrown, or the connection being closed. Routers should use this rather
* than read from the stream themselves.
*
* Deprecated - unused.
* This was used by the old TCP transport.
* Both the NTCP and SSU transports provide encapsulation
* of I2NP messages, so they use I2NPMessageHandlers directly.
* If we ever add a transport that does not provide encapsulation,
* this will be useful again.
*
* @deprecated unused
*
* @author jrandom
*/
public class I2NPMessageReader {

View File

@ -1,7 +1,5 @@
package net.i2p.data.i2np;
import java.io.IOException;
import net.i2p.I2PAppContext;
/**

View File

@ -1,7 +1,5 @@
package net.i2p.data.i2np;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
@ -9,6 +7,14 @@ import net.i2p.data.ByteArray;
* Base for TBM, TBRM, VTBM, VTBRM
* Retrofitted over them.
* There's really no difference between the build and build reply.
*
* TBM and VBTM (but not TBRM and VTBRM?) messages are modified
* in-place by doing a single setRecord(), and retransmitted.
* Therefore they are NOT good candidates to use FastI2NPMessageImpl;
* the checksum would have to be invalidated with every setRecord().
* Which we could do in TBM and VTBM but not TBRM and VTBRM,
* but keep it simple for now.
*
* @since 0.8.8
*/
public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
@ -41,7 +47,7 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
protected int calculateWrittenLength() { return RECORD_SIZE * RECORD_COUNT; }
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
if (type != getType())
throw new I2NPMessageException("Message type is incorrect for this message");
if (dataSize != calculateWrittenLength())

View File

@ -1,7 +1,5 @@
package net.i2p.data.i2np;
import java.io.IOException;
import net.i2p.I2PAppContext;
/**

View File

@ -8,8 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
@ -20,15 +18,20 @@ import net.i2p.util.Log;
/**
* Defines the message sent between routers as part of the tunnel delivery
*
* The tunnel ID is changed in-place by TunnelParticipant.send(), so
* we can't reuse the checksum on output, but we still subclass
* FastI2NPMessageImpl so we don't verify the checksum on input...
* because this is a high-usage class.
*
*/
public class TunnelDataMessage extends I2NPMessageImpl {
public class TunnelDataMessage extends FastI2NPMessageImpl {
private long _tunnelId;
private TunnelId _tunnelIdObj;
private byte[] _data;
private ByteArray _dataBuf;
public final static int MESSAGE_TYPE = 18;
private static final int DATA_SIZE = 1024;
public static final int DATA_SIZE = 1024;
/** if we can't deliver a tunnel message in 10s, fuck it */
private static final int EXPIRATION_PERIOD = 10*1000;
@ -104,13 +107,26 @@ public class TunnelDataMessage extends I2NPMessageImpl {
}
public long getTunnelId() { return _tunnelId; }
public void setTunnelId(long id) { _tunnelId = id; }
/**
* (correctly) Invalidates stored checksum
*/
public void setTunnelId(long id) {
_hasChecksum = false;
_tunnelId = id;
}
public TunnelId getTunnelIdObj() {
if (_tunnelIdObj == null)
_tunnelIdObj = new TunnelId(_tunnelId); // not thread safe, but immutable, so who cares
return _tunnelIdObj;
}
/**
* (correctly) Invalidates stored checksum
*/
public void setTunnelId(TunnelId id) {
_hasChecksum = false;
_tunnelIdObj = id;
_tunnelId = id.getTunnelId();
}
@ -124,13 +140,18 @@ public class TunnelDataMessage extends I2NPMessageImpl {
return _data;
}
/**
* @throws IllegalStateException if data previously set, to protect saved checksum
*/
public void setData(byte data[]) {
if (_data != null)
throw new IllegalStateException();
if ( (data == null) || (data.length <= 0) )
throw new IllegalArgumentException("Empty tunnel payload?");
_data = data;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
@ -214,8 +235,8 @@ public class TunnelDataMessage extends I2NPMessageImpl {
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[TunnelDataMessage:");
buf.append(" MessageId: ").append(getUniqueId());
buf.append(" Tunnel ID: ").append(getTunnelId());
buf.append(" MessageId: ").append(_uniqueId);
buf.append(" Tunnel ID: ").append(_tunnelId);
buf.append("]");
return buf.toString();
}

View File

@ -8,8 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.TunnelId;
@ -20,7 +18,7 @@ import net.i2p.util.Log;
* format: { tunnelId, sizeof(i2npMessage.toByteArray()), i2npMessage.toByteArray() }
*
*/
public class TunnelGatewayMessage extends I2NPMessageImpl {
public class TunnelGatewayMessage extends FastI2NPMessageImpl {
private TunnelId _tunnelId;
private I2NPMessage _msg;
private byte _msgData[];
@ -37,16 +35,32 @@ public class TunnelGatewayMessage extends I2NPMessageImpl {
}
public TunnelId getTunnelId() { return _tunnelId; }
public void setTunnelId(TunnelId id) { _tunnelId = id; }
/**
* @throws IllegalStateException if id previously set, to protect saved checksum
*/
public void setTunnelId(TunnelId id) {
if (_tunnelId != null)
throw new IllegalStateException();
_tunnelId = id;
}
/**
* Warning, at the IBGW, where the message was read in,
* this will be an UnknownI2NPMessage.
* If you need a real message class, use UnknownI2NPMessage.convert().
*
* Note that if you change the expiration on the embedded message it will
* mess up the checksum of this message, so don't do that.
*/
public I2NPMessage getMessage() { return _msg; }
/**
* @throws IllegalStateException if msg previously set, to protect saved checksum
*/
public void setMessage(I2NPMessage msg) {
if (_msg != null)
throw new IllegalStateException();
if (msg == null)
throw new IllegalArgumentException("wtf, dont set me to null");
_msg = msg;
@ -90,7 +104,7 @@ public class TunnelGatewayMessage extends I2NPMessageImpl {
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
//I2NPMessageHandler h = new I2NPMessageHandler(_context);
//readMessage(data, offset, dataSize, type, h);
readMessage(data, offset, dataSize, type, null);
@ -103,7 +117,7 @@ public class TunnelGatewayMessage extends I2NPMessageImpl {
* @param handler unused, may be null
*/
@Override
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException, IOException {
public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException {
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
int curIndex = offset;
@ -137,7 +151,7 @@ public class TunnelGatewayMessage extends I2NPMessageImpl {
// If a zero-hop, the checksum will be verified in convert().
int utype = data[curIndex++] & 0xff;
UnknownI2NPMessage umsg = new UnknownI2NPMessage(_context, utype);
umsg.readBytesIgnoreChecksum(data, curIndex);
umsg.readBytes(data, utype, curIndex);
_msg = umsg;
}

View File

@ -8,8 +8,6 @@ package net.i2p.data.i2np;
*
*/
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
@ -32,11 +30,9 @@ import net.i2p.util.SimpleByteCache;
*
* @since 0.7.12 but broken before 0.8.12
*/
public class UnknownI2NPMessage extends I2NPMessageImpl {
public class UnknownI2NPMessage extends FastI2NPMessageImpl {
private byte _data[];
private final int _type;
// we assume CHECKSUM_LENGTH = 1
private byte _checksum;
/** @param type 0-255 */
public UnknownI2NPMessage(I2PAppContext context, int type) {
@ -44,7 +40,12 @@ public class UnknownI2NPMessage extends I2NPMessageImpl {
_type = type;
}
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
/**
* @throws IllegalStateException if data previously set, to protect saved checksum
*/
public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException {
if (_data != null)
throw new IllegalStateException();
if (type != _type) throw new I2NPMessageException("Message type is incorrect for this message");
if (dataSize > MAX_SIZE)
throw new I2NPMessageException("wtf, size=" + dataSize);
@ -77,51 +78,9 @@ public class UnknownI2NPMessage extends I2NPMessageImpl {
*/
public int getType() { return _type; }
/**
* Read the full message including the header.
* This is the same as I2NPMessageImpl.readBytes(), except
* start after the type field, and
* do NOT verify the checksum, but simply save it for later
* so it can be verified in convert() if required.
*
*<pre>
* Standard message format AFTER the type field
* 4 byte ID
* 8 byte expiration
* 2 byte size
* 1 byte checksum (saved in case we need to check later)
* size bytes of payload, read by readMessage()
*</pre>
*
* @param offset starting at the ID (must skip the type)
* @since 0.8.12
*/
public void readBytesIgnoreChecksum(byte data[], int offset) throws I2NPMessageException, IOException {
int cur = offset;
setUniqueId(DataHelper.fromLong(data, cur, 4));
cur += 4;
setMessageExpiration(DataHelper.fromLong(data, cur, DataHelper.DATE_LENGTH));
cur += DataHelper.DATE_LENGTH;
int size = (int)DataHelper.fromLong(data, cur, 2);
cur += 2;
_checksum = data[cur];
cur++;
if (cur + size > data.length)
throw new I2NPMessageException("Payload is too short ["
+ "data.len=" + data.length
+ " offset=" + offset
+ " cur=" + cur
+ " wanted=" + size + ']');
readMessage(data, cur, size, _type);
}
/**
* Attempt to convert this message to a known message class.
* Must have been created with readBytesIgnoreChecksum previously,
* as this does the delayed verification using the saved checksum.
* This does the delayed verification using the saved checksum.
*
* Used by TunnelGatewayZeroHop.
*
@ -129,6 +88,8 @@ public class UnknownI2NPMessage extends I2NPMessageImpl {
* @since 0.8.12
*/
public I2NPMessage convert() throws I2NPMessageException {
if (_data == null || !_hasChecksum)
throw new I2NPMessageException("Illegal state");
I2NPMessage msg = I2NPMessageImpl.createMessage(_context, _type);
if (msg instanceof UnknownI2NPMessage)
throw new I2NPMessageException("Unable to convert unknown type " + _type);
@ -138,13 +99,9 @@ public class UnknownI2NPMessage extends I2NPMessageImpl {
SimpleByteCache.release(calc);
if (!eq)
throw new I2NPMessageException("Bad checksum on " + _data.length + " byte msg type " + _type);
try {
msg.readMessage(_data, 0, _data.length, _type);
} catch (IOException ioe) {
throw new I2NPMessageException("Unable to convert type " + _type, ioe);
}
msg.setUniqueId(getUniqueId());
msg.setMessageExpiration(getMessageExpiration());
msg.readMessage(_data, 0, _data.length, _type);
msg.setUniqueId(_uniqueId);
msg.setMessageExpiration(_expiration);
return msg;
}

View File

@ -1,7 +1,5 @@
package net.i2p.data.i2np;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
@ -30,7 +28,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage {
public int getType() { return MESSAGE_TYPE; }
@Override
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
// message type will be checked in super()
int r = (int)DataHelper.fromLong(data, offset, 1);
if (r <= 0 || r > MAX_RECORD_COUNT)

View File

@ -1,7 +1,5 @@
package net.i2p.data.i2np;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
@ -32,7 +30,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
public int getType() { return MESSAGE_TYPE; }
@Override
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException {
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
// message type will be checked in super()
int r = (int)DataHelper.fromLong(data, offset, 1);
if (r <= 0 || r > MAX_RECORD_COUNT)

View File

@ -28,6 +28,7 @@ import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/**
@ -1226,13 +1227,15 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener {
//public long getReadTime() { return _curReadState.getReadTime(); }
/**
* Just a byte array now (used to have a BAIS in it too,
* but that required an extra copy in the message handler)
*/
private static class DataBuf {
final byte data[];
final ByteArrayInputStream bais;
public DataBuf() {
data = new byte[BUFFER_SIZE];
bais = new ByteArrayInputStream(data);
}
}
@ -1247,7 +1250,6 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener {
}
private static void releaseReadBuf(DataBuf buf) {
buf.bais.reset();
_dataReadBufs.offer(buf);
}
@ -1387,15 +1389,14 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener {
if (val == _expectedCrc) {
try {
I2NPMessageHandler h = acquireHandler(_context);
//I2NPMessage read = h.readMessage(new ByteArrayInputStream(_data, 0, _size));
// the _bais is mark()ed at 0 on construction, and on init() we
// reset() it back to that position, so this read always starts
// at the beginning of the _data buffer. the I2NPMessageHandler
// also only reads the first I2NP message found, and does not
// depend upon EOF to stop reading, so its ok that the _bais could
// in theory return more data than _size bytes, since h.readMessage
// stops when it should.
I2NPMessage read = h.readMessage(_dataBuf.bais);
// Don't do readMessage(InputStream). I2NPMessageImpl.readBytes() copies the data
// from a stream to a temp buffer.
// We could extend BAIS to adjust the protected count variable to _size
// so that readBytes() doesn't read too far, but it could still read too far.
// So use the new handler method that limits the size.
h.readMessage(_dataBuf.data, 0, _size);
I2NPMessage read = h.lastRead();
long timeToRecv = System.currentTimeMillis() - _stateBegin;
releaseHandler(h);
if (_log.shouldLog(Log.INFO))
@ -1414,33 +1415,31 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener {
_lastReceiveTime = System.currentTimeMillis();
_messagesRead++;
}
// get it ready for the next I2NP message
init();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error parsing I2NP message", ioe);
_context.statManager().addRateData("ntcp.corruptI2NPIOE", 1);
close();
// handler and databuf are lost
return;
} catch (I2NPMessageException ime) {
if (_log.shouldLog(Log.WARN))
if (_log.shouldLog(Log.WARN)) {
_log.warn("Error parsing I2NP message", ime);
_log.warn("DUMP:\n" + HexDump.dump(_dataBuf.data, 0, _size));
_log.warn("RAW:\n" + Base64.encode(_dataBuf.data, 0, _size));
}
_context.statManager().addRateData("ntcp.corruptI2NPIME", 1);
// FIXME don't close the con, possible attack vector?
close();
// handler and databuf are lost
return;
// Don't close the con, possible attack vector, not necessarily the peer's fault,
// and should be recoverable
// handler and databuf are lost if we do this
//close();
//return;
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + _expectedCrc + ") size=" + _size + " blocks " + _blocks);
_context.statManager().addRateData("ntcp.corruptI2NPCRC", 1);
// FIXME don't close the con, possible attack vector?
close();
// databuf is lost
return;
// This probably can't be spoofed from somebody else, but do we really need to close it?
// This is rare.
//close();
// databuf is lost if we do this
//return;
}
// get it ready for the next I2NP message
init();
}
}