From 25b0603fdeb62c05ba88b3a7c57d423c627e1c2e Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 9 Dec 2011 17:36:49 +0000 Subject: [PATCH] * I2NP: - Earlier detection and better logging of truncated TunnelGatewayMessage and DatabaseStoreMessage - Fix and enhance UnknownI2NPMessage implementation - Don't deserialize or verify the checksum of the embeddedI2NP message in the TunnelGatewayMessage at the IBGW, just use UnknownI2NPMessage and pass it along, except if zero hop; Still to do: similar thing at OBEP - Round expiration times when converting to/from seconds for SSU - Cleanups and javadoc --- history.txt | 20 +++ .../i2p/data/i2np/DatabaseStoreMessage.java | 33 ++++- .../src/net/i2p/data/i2np/GarlicMessage.java | 2 - .../net/i2p/data/i2np/I2NPMessageHandler.java | 33 ++--- .../net/i2p/data/i2np/I2NPMessageImpl.java | 64 ++++++---- .../net/i2p/data/i2np/TunnelDataMessage.java | 2 - .../i2p/data/i2np/TunnelGatewayMessage.java | 54 ++++++-- .../net/i2p/data/i2np/UnknownI2NPMessage.java | 116 +++++++++++++----- .../src/net/i2p/router/RouterVersion.java | 2 +- .../i2p/router/tunnel/FragmentHandler.java | 7 ++ .../tunnel/OutboundMessageDistributor.java | 2 + .../i2p/router/tunnel/TunnelDispatcher.java | 4 +- .../router/tunnel/TunnelGatewayZeroHop.java | 23 +++- 13 files changed, 274 insertions(+), 88 deletions(-) diff --git a/history.txt b/history.txt index 924400a90..9418dcfbc 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,23 @@ +2011-12-09 zzz + * Base64: Add decodestring command in main() + * Console, i2psnark: More button CSS tweaks + * I2NP: + - Earlier detection and better logging of + truncated TunnelGatewayMessage and DatabaseStoreMessage + - Fix and enhance UnknownI2NPMessage implementation + - Don't deserialize or verify the checksum of the + embeddedI2NP message in the TunnelGatewayMessage + at the IBGW, just use UnknownI2NPMessage and pass it along, + except if zero hop; Still to do: similar thing at OBEP + - Cleanups and javadoc + * LeaseSet: Fix size calculations + * UDP: + - Fix major bug from 2005 that corrupted outbound messages + that were an exact multiple of the fragment size. + - Round expiration times when converting to seconds + - Zero-copy of single-fragment messages in MessageReceiver + - Optimizations, log tweaks, comments + 2011-12-06 zzz * Router: - More refactoring tasks to their own files diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java index 6ac4b4367..1b51f3a10 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java @@ -24,6 +24,9 @@ import net.i2p.data.TunnelId; * Defines the message a router sends to another router to test the network * database reachability, as well as the reply message sent back. * + * TODO: Don't decompress and recompress RouterInfos at the OBEP and IBGW. + * Could this even change the message length or corrupt things? + * * @author jrandom */ public class DatabaseStoreMessage extends I2NPMessageImpl { @@ -128,14 +131,22 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { _dbEntry = new RouterInfo(); int compressedSize = (int)DataHelper.fromLong(data, curIndex, 2); curIndex += 2; + if (compressedSize <= 0 || curIndex + compressedSize > data.length || (curIndex - offset) + compressedSize > dataSize) + throw new I2NPMessageException("Compressed RI length: " + compressedSize + + " but remaining bytes: " + Math.min(data.length - curIndex, dataSize - (curIndex - offset))); try { + // TODO we could delay decompression, just copy to a new byte array and store in _byteCache + // May not be necessary since the IBGW now uses UnknownI2NPMessage. + // DSMs at the OBEP are generally garlic wrapped, so the OBEP won't see it. + // If we do delay it, getEntry() will have to check if _dbEntry is null and _byteCache + // is non-null, and then decompress. byte decompressed[] = DataHelper.decompress(data, curIndex, compressedSize); _dbEntry.readBytes(new ByteArrayInputStream(decompressed)); } catch (DataFormatException dfe) { throw new I2NPMessageException("Error reading the routerInfo", dfe); } catch (IOException ioe) { - throw new I2NPMessageException("Compressed routerInfo was corrupt", ioe); + throw new I2NPMessageException("Corrupt compressed routerInfo size = " + compressedSize, ioe); } } else { throw new I2NPMessageException("Invalid type of key read from the structure - " + type); @@ -145,17 +156,29 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { } - /** calculate the message body's length (not including the header and footer */ + /** + * calculate the message body's length (not including the header and footer) + * + * @throws IllegalStateException + */ protected int calculateWrittenLength() { + // TODO if _byteCache is non-null, don't check _dbEntry + if (_dbEntry == null) + throw new IllegalStateException("Missing entry"); int len = Hash.HASH_LENGTH + 1 + 4; // key+type+replyToken if (_replyToken > 0) len += 4 + Hash.HASH_LENGTH; // replyTunnel+replyGateway int type = _dbEntry.getType(); if (type == DatabaseEntry.KEY_TYPE_LEASESET) { - _byteCache = _dbEntry.toByteArray(); + if (_byteCache == null) { + _byteCache = _dbEntry.toByteArray(); + } } else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) { - byte uncompressed[] = _dbEntry.toByteArray(); - _byteCache = DataHelper.compress(uncompressed); + // only decompress once + if (_byteCache == null) { + byte uncompressed[] = _dbEntry.toByteArray(); + _byteCache = DataHelper.compress(uncompressed); + } len += 2; } else { throw new IllegalStateException("Invalid key type " + type); diff --git a/router/java/src/net/i2p/data/i2np/GarlicMessage.java b/router/java/src/net/i2p/data/i2np/GarlicMessage.java index ce1466e83..1e4d3339f 100644 --- a/router/java/src/net/i2p/data/i2np/GarlicMessage.java +++ b/router/java/src/net/i2p/data/i2np/GarlicMessage.java @@ -12,7 +12,6 @@ import java.io.IOException; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; -import net.i2p.util.Log; /** * Defines the wrapped garlic message @@ -20,7 +19,6 @@ import net.i2p.util.Log; * @author jrandom */ public class GarlicMessage extends I2NPMessageImpl { - private final static Log _log = new Log(GarlicMessage.class); public final static int MESSAGE_TYPE = 11; private byte[] _data; diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java index a230eb929..25f5c9ad6 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java @@ -49,8 +49,9 @@ public class I2NPMessageHandler { int type = (int)DataHelper.readLong(in, 1); _lastReadBegin = System.currentTimeMillis(); I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type); - if (msg == null) - throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message"); + // can't be null + //if (msg == null) + // throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message"); try { _lastSize = msg.readBytes(in, type, _messageBuffer); } catch (IOException ioe) { @@ -88,24 +89,26 @@ public class I2NPMessageHandler { readMessage(data, 0); return lastRead(); } + public int readMessage(byte data[], int offset) throws IOException, I2NPMessageException { int cur = offset; int type = (int)DataHelper.fromLong(data, cur, 1); cur++; _lastReadBegin = System.currentTimeMillis(); I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type); - if (msg == null) { - int sz = data.length-offset; - boolean allZero = false; - for (int i = offset; i < data.length; i++) { - if (data[i] != 0) { - allZero = false; - break; - } - } - throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz=" - + sz + " all zeros? " + allZero + ")"); - } + // can't be null + //if (msg == null) { + // int sz = data.length-offset; + // boolean allZero = false; + // for (int i = offset; i < data.length; i++) { + // if (data[i] != 0) { + // allZero = false; + // break; + // } + // } + // throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz=" + // + sz + " all zeros? " + allZero + ")"); + //} try { _lastSize = msg.readBytes(data, type, cur); cur += _lastSize; @@ -127,6 +130,7 @@ public class I2NPMessageHandler { public long getLastReadTime() { return _lastReadEnd - _lastReadBegin; } public int getLastSize() { return _lastSize; } +/**** public static void main(String args[]) { try { I2NPMessage msg = new I2NPMessageHandler(I2PAppContext.getGlobalContext()).readMessage(new FileInputStream(args[0])); @@ -135,4 +139,5 @@ public class I2NPMessageHandler { e.printStackTrace(); } } +****/ } diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java index 19990a7c8..ce7a50fa4 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java @@ -28,7 +28,7 @@ import net.i2p.util.SimpleByteCache; * @author jrandom */ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPMessage { - private final Log _log; + protected final Log _log; protected final I2PAppContext _context; private long _expiration; private long _uniqueId; @@ -36,12 +36,16 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM public final static long DEFAULT_EXPIRATION_MS = 1*60*1000; // 1 minute by default public final static int CHECKSUM_LENGTH = 1; //Hash.HASH_LENGTH; - private static final boolean RAW_FULL_SIZE = false; + // 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; /** unused */ private static final Map _builders = new ConcurrentHashMap(1); + /** @deprecated unused */ public static final void registerBuilder(Builder builder, int type) { _builders.put(Integer.valueOf(type), builder); } + /** interface for extending the types of messages handled - unused */ public interface Builder { /** instantiate a new I2NPMessage to be populated shortly */ @@ -120,7 +124,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM boolean eq = DataHelper.eq(checksum, 0, calc, 0, CHECKSUM_LENGTH); SimpleByteCache.release(calc); if (!eq) - throw new I2NPMessageException("Hash does not match for " + getClass().getName()); + throw new I2NPMessageException("Bad checksum on " + size + " byte I2NP " + getClass().getSimpleName()); //long start = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) @@ -182,7 +186,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM boolean eq = DataHelper.eq(hdata, 0, calc, 0, CHECKSUM_LENGTH); SimpleByteCache.release(calc); if (!eq) - throw new I2NPMessageException("Hash does not match for " + getClass().getName()); + throw new I2NPMessageException("Bad checksum on " + size + " byte I2NP " + getClass().getSimpleName()); //long start = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) @@ -220,10 +224,15 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM public synchronized int getMessageSize() { return calculateWrittenLength()+15 + CHECKSUM_LENGTH; // 16 bytes in the header } + + /** + * The raw header consists of a one-byte type and a 4-byte expiration in seconds only. + * Used by SSU only! + */ public synchronized int getRawMessageSize() { - if (RAW_FULL_SIZE) - return getMessageSize(); - else + //if (RAW_FULL_SIZE) + // return getMessageSize(); + //else return calculateWrittenLength()+5; } @@ -310,15 +319,21 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM */ - /** used by SSU only */ + /** + * Write the message with a short 5-byte header. + * THe header consists of a one-byte type and a 4-byte expiration in seconds only. + * Used by SSU only! + */ public int toRawByteArray(byte buffer[]) { - if (RAW_FULL_SIZE) - return toByteArray(buffer); + //if (RAW_FULL_SIZE) + // return toByteArray(buffer); try { int off = 0; DataHelper.toLong(buffer, off, 1, getType()); off += 1; - DataHelper.toLong(buffer, off, 4, _expiration/1000); // seconds + // January 19 2038? No, unsigned, good until Feb. 7 2106 + // in seconds, round up so we don't lose time every hop + DataHelper.toLong(buffer, off, 4, (_expiration + 500) / 1000); off += 4; return writeMessageBody(buffer, off); } catch (I2NPMessageException ime) { @@ -344,24 +359,30 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM } *****/ - /** used by SSU only */ + /** + * Read the message with a short 5-byte header. + * THe header consists of a one-byte type and a 4-byte expiration in seconds only. + * Used by SSU only! + */ public static I2NPMessage fromRawByteArray(I2PAppContext ctx, byte buffer[], int offset, int len, I2NPMessageHandler handler) throws I2NPMessageException { int type = (int)DataHelper.fromLong(buffer, offset, 1); offset++; I2NPMessageImpl msg = (I2NPMessageImpl)createMessage(ctx, type); if (msg == null) throw new I2NPMessageException("Unknown message type: " + type); - if (RAW_FULL_SIZE) { - try { - msg.readBytes(buffer, type, offset); - } catch (IOException ioe) { - throw new I2NPMessageException("Error reading the " + msg, ioe); - } - return msg; - } + //if (RAW_FULL_SIZE) { + // try { + // msg.readBytes(buffer, type, offset); + // } catch (IOException ioe) { + // throw new I2NPMessageException("Error reading the " + msg, ioe); + // } + // return msg; + //} try { - long expiration = DataHelper.fromLong(buffer, offset, 4) * 1000; // seconds + // January 19 2038? No, unsigned, good until Feb. 7 2106 + // in seconds, round up so we don't lose time every hop + long expiration = (DataHelper.fromLong(buffer, offset, 4) * 1000) + 500; offset += 4; int dataSize = len - 1 - 4; msg.readMessage(buffer, offset, dataSize, type, handler); @@ -377,6 +398,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM /** * Yes, this is fairly ugly, but its the only place it ever happens. * + * @return non-null, returns an UnknownI2NPMessage if unknown type */ public static I2NPMessage createMessage(I2PAppContext context, int type) throws I2NPMessageException { switch (type) { diff --git a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java index ef4930594..703deb108 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java @@ -22,7 +22,6 @@ import net.i2p.util.Log; * */ public class TunnelDataMessage extends I2NPMessageImpl { - private Log _log; private long _tunnelId; private TunnelId _tunnelIdObj; private byte[] _data; @@ -101,7 +100,6 @@ public class TunnelDataMessage extends I2NPMessageImpl { public TunnelDataMessage(I2PAppContext context) { super(context); - _log = context.logManager().getLog(TunnelDataMessage.class); setMessageExpiration(context.clock().now() + EXPIRATION_PERIOD); } diff --git a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java index 43fc4bce6..23963c1c6 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java @@ -21,11 +21,10 @@ import net.i2p.util.Log; * */ public class TunnelGatewayMessage extends I2NPMessageImpl { - private Log _log; private TunnelId _tunnelId; private I2NPMessage _msg; private byte _msgData[]; - private Exception _creator; + //private Exception _creator; public final static int MESSAGE_TYPE = 19; /** if we can't deliver a tunnel message in 10s, fuck it */ @@ -33,7 +32,6 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { public TunnelGatewayMessage(I2PAppContext context) { super(context); - _log = context.logManager().getLog(TunnelGatewayMessage.class); setMessageExpiration(context.clock().now() + EXPIRATION_PERIOD); //_creator = new Exception("i made this"); } @@ -41,7 +39,13 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { public TunnelId getTunnelId() { return _tunnelId; } public void setTunnelId(TunnelId id) { _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(). + */ public I2NPMessage getMessage() { return _msg; } + public void setMessage(I2NPMessage msg) { if (msg == null) throw new IllegalArgumentException("wtf, dont set me to null"); @@ -61,7 +65,7 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { /** write the message body to the output array, starting at the given index */ protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException { if ( (_tunnelId == null) || ( (_msg == null) && (_msgData == null) ) ) { - _log.log(Log.CRIT, "failing to write out gateway message, created by: ", _creator); + _log.log(Log.CRIT, "failing to write out gateway message"); throw new I2NPMessageException("Not enough data to write out (id=" + _tunnelId + " data=" + _msg + ")"); } @@ -87,9 +91,17 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException { - I2NPMessageHandler h = new I2NPMessageHandler(_context); - readMessage(data, offset, dataSize, type, h); + //I2NPMessageHandler h = new I2NPMessageHandler(_context); + //readMessage(data, offset, dataSize, type, h); + readMessage(data, offset, dataSize, type, null); } + + /** + * Note that for efficiency at the IBGW, this does not fully deserialize the included + * I2NP Message. It just puts it in an UnknownI2NPMessage. + * + * @param handler unused, may be null + */ @Override public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException, IOException { if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message"); @@ -101,12 +113,32 @@ public class TunnelGatewayMessage extends I2NPMessageImpl { if (_tunnelId.getTunnelId() <= 0) throw new I2NPMessageException("Invalid tunnel Id " + _tunnelId); - DataHelper.fromLong(data, curIndex, 2); + int len = (int) DataHelper.fromLong(data, curIndex, 2); curIndex += 2; - curIndex = handler.readMessage(data, curIndex); - _msg = handler.lastRead(); - if (_msg == null) - throw new I2NPMessageException("wtf, message read has no payload?"); + if (len <= 1 || curIndex + len > data.length || len > dataSize - 6) + throw new I2NPMessageException("I2NP length in TGM: " + len + + " but remaining bytes: " + Math.min(data.length - curIndex, dataSize - 6)); + + // OLD WAY full message parsing and instantiation + //handler.readMessage(data, curIndex); + //_msg = handler.lastRead(); + //if (_msg == null) + // throw new I2NPMessageException("wtf, message read has no payload?"); + + // NEW WAY save lots of effort at the IBGW by reading as an UnknownI2NPMessage instead + // This will save a lot of object churn and processing, + // primarily for unencrypted msgs (V)TBRM, DatabaseStoreMessage, and DSRMs. + // DatabaseStoreMessages in particluar are intensive for readBytes() + // since the RI is decompressed. + // For a zero-hop IB tunnel, where we do need the real thing, + // it is converted to a real message class in TunnelGatewayZeroHop + // using UnknownI2NPMessage.convert() in TunnelGatewayZeroHop. + // We also skip processing the checksum as it's covered by the TGM checksum. + // 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); + _msg = umsg; } public int getType() { return MESSAGE_TYPE; } diff --git a/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java b/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java index 898a408ad..ee3e3d3bf 100644 --- a/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java +++ b/router/java/src/net/i2p/data/i2np/UnknownI2NPMessage.java @@ -12,6 +12,8 @@ import java.io.IOException; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.util.SimpleByteCache; /** * This is the same as DataMessage but with a variable message type. @@ -23,11 +25,13 @@ import net.i2p.data.DataHelper; * There is no setData() method, the only way to create one of these is to * read it with readMessage() (i.e., it came from some other router) * - * @since 0.7.12 + * @since 0.7.12 but broken before 0.8.12 */ public class UnknownI2NPMessage extends I2NPMessageImpl { private byte _data[]; - private int _type; + private final int _type; + // we assume CHECKSUM_LENGTH = 1 + private byte _checksum; /** @param type 0-255 */ public UnknownI2NPMessage(I2PAppContext context, int type) { @@ -35,41 +39,25 @@ public class UnknownI2NPMessage extends I2NPMessageImpl { _type = type; } - /** warning - only public for equals() */ - public byte[] getData() { - return _data; - } - public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException { if (type != _type) throw new I2NPMessageException("Message type is incorrect for this message"); - int curIndex = offset; - long size = DataHelper.fromLong(data, curIndex, 4); - curIndex += 4; - if (size > MAX_SIZE) - throw new I2NPMessageException("wtf, size=" + size); - _data = new byte[(int)size]; - System.arraycopy(data, curIndex, _data, 0, (int)size); + if (dataSize > MAX_SIZE) + throw new I2NPMessageException("wtf, size=" + dataSize); + _data = new byte[dataSize]; + System.arraycopy(data, offset, _data, 0, dataSize); } /** calculate the message body's length (not including the header and footer */ protected int calculateWrittenLength() { if (_data == null) - return 4; + return 0; else - return 4 + _data.length; + return _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) { - out[curIndex++] = 0x0; - out[curIndex++] = 0x0; - out[curIndex++] = 0x0; - out[curIndex++] = 0x0; - } else { - byte len[] = DataHelper.toLong(4, _data.length); - System.arraycopy(len, 0, out, curIndex, 4); - curIndex += 4; + if (_data != null) { System.arraycopy(_data, 0, out, curIndex, _data.length); curIndex += _data.length; } @@ -79,16 +67,88 @@ public class UnknownI2NPMessage extends I2NPMessageImpl { /** @return 0-255 */ 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) + * @return total length of the message + * @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. + * + * Used by TunnelGatewayZeroHop. + * + * @throws I2NPMessageException if the conversion fails + * @since 0.8.12 + */ + public I2NPMessage convert() throws I2NPMessageException { + I2NPMessage msg = I2NPMessageImpl.createMessage(_context, _type); + if (msg instanceof UnknownI2NPMessage) + throw new I2NPMessageException("Unable to convert unknown type " + _type); + byte[] calc = SimpleByteCache.acquire(Hash.HASH_LENGTH); + _context.sha().calculateHash(_data, 0, _data.length, calc, 0); + boolean eq = _checksum == calc[0]; + 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()); + return msg; + } + @Override public int hashCode() { - return _type + DataHelper.hashCode(getData()); + return _type + DataHelper.hashCode(_data); } @Override public boolean equals(Object object) { if ( (object != null) && (object instanceof UnknownI2NPMessage) ) { UnknownI2NPMessage msg = (UnknownI2NPMessage)object; - return _type == msg.getType() && DataHelper.eq(getData(), msg.getData()); + return _type == msg.getType() && DataHelper.eq(_data, msg._data); } else { return false; } @@ -99,7 +159,7 @@ public class UnknownI2NPMessage extends I2NPMessageImpl { StringBuilder buf = new StringBuilder(); buf.append("[UnknownI2NPMessage: "); buf.append("\n\tType: ").append(_type); - buf.append("\n\tLength: ").append(calculateWrittenLength() - 4); + buf.append("\n\tLength: ").append(calculateWrittenLength()); buf.append("]"); return buf.toString(); } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 8ab24107c..80cfb9c84 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 17; + public final static long BUILD = 18; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java index 91f2517a2..0d25b8cfa 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java @@ -477,6 +477,13 @@ class FragmentHandler { if (_log.shouldLog(Log.DEBUG)) _log.debug("RECV(" + data.length + "): "); // + Base64.encode(data) //+ " " + _context.sha().calculateHash(data).toBase64()); + + // TODO read in as unknown message for outbound tunnels, + // since this will just be packaged in a TunnelGatewayMessage. + // Not a big savings since most everything is a GarlicMessage + // and so the readMessage() call is fast. + // The unencrypted messages at the OBEP are (V)TBMs + // and perhaps an occasional DatabaseLookupMessage I2NPMessage m = new I2NPMessageHandler(_context).readMessage(data); noteReception(m.getUniqueId(), fragmentCount-1, "complete: ");// + msg.toString()); noteCompletion(m.getUniqueId()); diff --git a/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java index 0bd5e4b07..a54b62684 100644 --- a/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java +++ b/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java @@ -61,6 +61,8 @@ class OutboundMessageDistributor { if (_context.routerHash().equals(target.getIdentity().calculateHash())) { if (_log.shouldLog(Log.DEBUG)) _log.debug("queueing inbound message to ourselves: " + m); + // TODO if UnknownI2NPMessage, convert it. + // See FragmentHandler.receiveComplete() _context.inNetMessagePool().add(m, null, null); return; } else { diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java index 3c77917fd..389915637 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java @@ -452,7 +452,7 @@ public class TunnelDispatcher implements Service { + " as the wrapper's expiration is in " + DataHelper.formatDuration(msg.getMessageExpiration()-before) + " and/or the content's expiration is in " + DataHelper.formatDuration(msg.getMessage().getMessageExpiration()-before) + " with messageId " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() + " and message type " - + msg.getMessage().getClass().getName()); + + msg.getMessage().getClass().getSimpleName()); return; } //_context.messageHistory().tunnelDispatched("message " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() + " on tunnel " @@ -471,7 +471,7 @@ public class TunnelDispatcher implements Service { + DataHelper.formatDuration(msg.getMessage().getMessageExpiration()-_context.clock().now()) + " messageId " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() - + " messageType: " + msg.getMessage().getClass().getName() + + " messageType: " + msg.getMessage().getClass().getSimpleName() + " existing = " + _inboundGateways.size(), new Exception("source")); } diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java index 01775b23b..a4c048eb9 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java @@ -3,7 +3,9 @@ package net.i2p.router.tunnel; import net.i2p.data.Hash; import net.i2p.data.TunnelId; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.I2NPMessageException; import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.data.i2np.UnknownI2NPMessage; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -30,12 +32,29 @@ class TunnelGatewayZeroHop extends TunnelGateway { /** * Add a message to be sent down the tunnel, where we are the inbound gateway. + * This requires converting the message included in the TGM from an + * UnknownI2NPMessage to the correct message class. + * See TunnelGatewayMessage for details. * * @param msg message received to be sent through the tunnel */ @Override public void add(TunnelGatewayMessage msg) { - add(msg.getMessage(), null, null); + I2NPMessage imsg = msg.getMessage(); + if (_config.isInbound()) { + if (imsg instanceof UnknownI2NPMessage) { + // Do the delayed deserializing - convert to a standard message class + try { + UnknownI2NPMessage umsg = (UnknownI2NPMessage) imsg; + imsg = umsg.convert(); + } catch (I2NPMessageException ime) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to convert to std. msg. class at zero-hop IBGW", ime); + return; + } + } + } + add(imsg, null, null); } /** @@ -50,7 +69,7 @@ class TunnelGatewayZeroHop extends TunnelGateway { @Override public void add(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("zero hop gateway: distribute " + (_config.isInbound() ? "inbound " : " outbound ") + _log.debug("zero hop gateway: distribute " + (_config.isInbound() ? "inbound" : " outbound") + " to " + (toRouter != null ? toRouter.toBase64().substring(0,4) : "" ) + "." + (toTunnel != null ? toTunnel.getTunnelId() + "" : "") + ": " + msg);