diff --git a/router/java/src/net/i2p/data/i2np/DataMessage.java b/router/java/src/net/i2p/data/i2np/DataMessage.java index 81cde69cf..abbc59fe9 100644 --- a/router/java/src/net/i2p/data/i2np/DataMessage.java +++ b/router/java/src/net/i2p/data/i2np/DataMessage.java @@ -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(); } diff --git a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java index bad38627b..02fa58db3 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java @@ -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 _dontIncludePeers; + /** this must be kept as a list to preserve the order and not break the checksum */ + private List _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; } + + /** + * @throws IllegalStateException if tunnel previously set, to protect saved checksum + */ + public void setReplyTunnel(TunnelId replyTunnel) { + if (_replyTunnel != null) + throw new IllegalStateException(); + _replyTunnel = replyTunnel; + } /** - * Set of peers that a lookup reply should NOT include + * 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 + * @return Set of Hash objects, each of which is the H(routerIdentity) to skip, or null */ - public Set getDontIncludePeers() { return _dontIncludePeers; } - public void setDontIncludePeers(Set peers) { + public Set 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 peers) { + _hasChecksum = false; if (peers != null) - _dontIncludePeers = new HashSet(peers); + _dontIncludePeers = new ArrayList(peers); else _dontIncludePeers = null; } + + /** + * 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 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, 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; @@ -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 peers = new HashSet(numPeers); + List 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 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()); diff --git a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java index 44ee294e6..35d704e43 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java @@ -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 _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(); } diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java index 1b51f3a10..df209c936 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java @@ -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(); } diff --git a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java index f6721c5e7..b2fa416ab 100644 --- a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java +++ b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java @@ -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++; diff --git a/router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java b/router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java index 63cb924b0..6831d8fcd 100644 --- a/router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java +++ b/router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java @@ -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; } + + /** + * 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, 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; diff --git a/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java new file mode 100644 index 000000000..307fd68a2 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java @@ -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); + } + } +} diff --git a/router/java/src/net/i2p/data/i2np/GarlicClove.java b/router/java/src/net/i2p/data/i2np/GarlicClove.java index 1067fb640..834412065 100644 --- a/router/java/src/net/i2p/data/i2np/GarlicClove.java +++ b/router/java/src/net/i2p/data/i2np/GarlicClove.java @@ -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) { diff --git a/router/java/src/net/i2p/data/i2np/GarlicMessage.java b/router/java/src/net/i2p/data/i2np/GarlicMessage.java index 1e4d3339f..c4cfd29c9 100644 --- a/router/java/src/net/i2p/data/i2np/GarlicMessage.java +++ b/router/java/src/net/i2p/data/i2np/GarlicMessage.java @@ -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; diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessage.java b/router/java/src/net/i2p/data/i2np/I2NPMessage.java index e2e5290ef..6dadbefae 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessage.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessage.java @@ -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 */ + + /** 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[]); } diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java index 25f5c9ad6..477eb1b66 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java @@ -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; diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java index ce7a50fa4..914f2f708 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java @@ -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. + * *
      *  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)
      *
* + * 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); } diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java index 74b93b34f..43dd7bc1c 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java @@ -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 { diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java index 14748367b..fb53cbd45 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java @@ -1,7 +1,5 @@ package net.i2p.data.i2np; -import java.io.IOException; - import net.i2p.I2PAppContext; /** diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java b/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java index 2c1fb9e9f..bb3f579a3 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java @@ -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()) diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java index dadc88fbf..18ae1b4a5 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java @@ -1,7 +1,5 @@ package net.i2p.data.i2np; -import java.io.IOException; - import net.i2p.I2PAppContext; /** diff --git a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java index 703deb108..3199cefac 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java @@ -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(); } diff --git a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java index 23963c1c6..7952c6df0 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java @@ -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; } diff --git a/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java b/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java index cfd0d414d..791f8126a 100644 --- a/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java +++ b/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java @@ -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. - * - *
-     *  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()
-     *
- * - * @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; } diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java index b6d7deae2..9f7dea7a7 100644 --- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java +++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java @@ -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) diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java index 1b64ecd2d..fb7bc5b6a 100644 --- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java @@ -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) diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 114cbc40b..4c0b7b23b 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -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(); } }