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 java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.DataStructureImpl; import net.i2p.data.Hash; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; /** * Defines the base message implementation. * * @author jrandom */ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPMessage { private final Log _log; protected final I2PAppContext _context; private long _expiration; private 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; 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 */ public I2NPMessage build(I2PAppContext ctx); } public I2NPMessageImpl(I2PAppContext context) { _context = context; _log = context.logManager().getLog(I2NPMessageImpl.class); _expiration = _context.clock().now() + DEFAULT_EXPIRATION_MS; _uniqueId = _context.random().nextLong(MAX_ID_VALUE); //_context.statManager().createRateStat("i2np.writeTime", "How long it takes to write an I2NP message", "I2NP", new long[] { 10*60*1000, 60*60*1000 }); //_context.statManager().createRateStat("i2np.readTime", "How long it takes to read an I2NP message", "I2NP", new long[] { 10*60*1000, 60*60*1000 }); } /** * Read the whole message (including the type) and throw it away. * @deprecated Unused, why would you do this */ public void readBytes(InputStream in) throws DataFormatException, IOException { try { readBytes(in, -1, new byte[1024]); } catch (I2NPMessageException ime) { throw new DataFormatException("Bad bytes", ime); } } /** * Read the header, then read the rest into buffer, then call * readMessage in the implemented message type * *
     *  Specifically:
     *    1 byte type (if caller didn't read already, as specified by the type param
     *    4 byte ID
     *    8 byte expiration
     *    2 byte size
     *    1 byte checksum
     *    size bytes of payload (read by readMessage() in implementation)
     *
* * @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 */ public int readBytes(InputStream in, int type, byte buffer[]) throws I2NPMessageException, IOException { try { if (type < 0) type = (int)DataHelper.readLong(in, 1); _uniqueId = DataHelper.readLong(in, 4); _expiration = DataHelper.readLong(in, DataHelper.DATE_LENGTH); int size = (int)DataHelper.readLong(in, 2); byte checksum[] = new byte[CHECKSUM_LENGTH]; int read = DataHelper.read(in, checksum); if (read != CHECKSUM_LENGTH) throw new I2NPMessageException("checksum is too small [" + read + "]"); //Hash h = new Hash(); //h.readBytes(in); if (buffer.length < size) { if (size > MAX_SIZE) throw new I2NPMessageException("size=" + size); buffer = new byte[size]; } int cur = 0; while (cur < size) { int numRead = in.read(buffer, cur, size- cur); if (numRead == -1) { throw new I2NPMessageException("Payload is too short [" + numRead + ", wanted " + size + "]"); } cur += numRead; } byte[] calc = SimpleByteCache.acquire(Hash.HASH_LENGTH); _context.sha().calculateHash(buffer, 0, size, calc, 0); //boolean eq = calc.equals(h); 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()); //long start = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Reading bytes: type = " + type + " / uniqueId : " + _uniqueId + " / expiration : " + _expiration); readMessage(buffer, 0, size, type); //long time = _context.clock().now() - start; //if (time > 50) // _context.statManager().addRateData("i2np.readTime", time, time); return CHECKSUM_LENGTH + 1 + 2 + 4 + DataHelper.DATE_LENGTH + size; } catch (DataFormatException dfe) { throw new I2NPMessageException("Error reading the message header", dfe); } } /** * Read the header, then read the rest into buffer, then call * readMessage in the implemented message type * *
     *  Specifically:
     *    1 byte type (if caller didn't read already, as specified by the type param
     *    4 byte ID
     *    8 byte expiration
     *    2 byte size
     *    1 byte checksum
     *    size bytes of payload (read by readMessage() in implementation)
     *
* * @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 { 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; //Hash h = new Hash(); byte hdata[] = new byte[CHECKSUM_LENGTH]; System.arraycopy(data, cur, hdata, 0, CHECKSUM_LENGTH); cur += CHECKSUM_LENGTH; //h.setData(hdata); if (cur + size > data.length) throw new I2NPMessageException("Payload is too short [" + "data.len=" + data.length + " offset=" + offset + " cur=" + cur + " wanted=" + size + "]: " + getClass().getName()); byte[] calc = SimpleByteCache.acquire(Hash.HASH_LENGTH); _context.sha().calculateHash(data, cur, size, calc, 0); 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()); //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; //long time = _context.clock().now() - start; //if (time > 50) // _context.statManager().addRateData("i2np.readTime", time, time); return cur - offset; } public void writeBytes(OutputStream out) throws DataFormatException, IOException { int size = getMessageSize(); if (size < 15 + CHECKSUM_LENGTH) throw new DataFormatException("Unable to build the message"); byte buf[] = new byte[size]; int read = toByteArray(buf); if (read < 0) throw new DataFormatException("Unable to build the message"); out.write(buf, 0, read); } /** * Replay resistent message Id */ public long getUniqueId() { return _uniqueId; } public void setUniqueId(long id) { _uniqueId = id; } /** * Date after which the message should be dropped (and the associated uniqueId forgotten) * */ public long getMessageExpiration() { return _expiration; } public void setMessageExpiration(long exp) { _expiration = exp; } public synchronized int getMessageSize() { return calculateWrittenLength()+15 + CHECKSUM_LENGTH; // 16 bytes in the header } public synchronized int getRawMessageSize() { if (RAW_FULL_SIZE) return getMessageSize(); else return calculateWrittenLength()+5; } @Override public byte[] toByteArray() { byte data[] = new byte[getMessageSize()]; 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()); 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; byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH); _context.sha().calculateHash(buffer, prefixLen, payloadLen, h, 0); 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; 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()); } } /** 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; for (int i = 0; i < prefix.length; i++) { System.arraycopy(prefix[i], 0, out, curIndex, prefix[i].length); curIndex += prefix[i].length; } curIndex = writeMessageBody(out, curIndex); for (int i = 0; i < suffix.length; i++) { System.arraycopy(suffix[i], 0, out, curIndex, suffix[i].length); curIndex += suffix[i].length; } return curIndex; } */ /** used by SSU only */ public int toRawByteArray(byte 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 off += 4; 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()); } } public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException, IOException { // ignore the handler (overridden in subclasses if necessary try { readMessage(data, offset, dataSize, type); } catch (IllegalArgumentException iae) { throw new I2NPMessageException("Error reading the message", iae); } } /***** public static I2NPMessage fromRawByteArray(I2PAppContext ctx, byte buffer[], int offset, int len) throws I2NPMessageException { return fromRawByteArray(ctx, buffer, offset, len, new I2NPMessageHandler(ctx)); } *****/ /** 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; } try { long expiration = DataHelper.fromLong(buffer, offset, 4) * 1000; // seconds offset += 4; int dataSize = len - 1 - 4; 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); } } /** * Yes, this is fairly ugly, but its the only place it ever happens. * */ public static I2NPMessage createMessage(I2PAppContext context, int type) throws I2NPMessageException { switch (type) { case DatabaseStoreMessage.MESSAGE_TYPE: return new DatabaseStoreMessage(context); case DatabaseLookupMessage.MESSAGE_TYPE: return new DatabaseLookupMessage(context); case DatabaseSearchReplyMessage.MESSAGE_TYPE: return new DatabaseSearchReplyMessage(context); case DeliveryStatusMessage.MESSAGE_TYPE: return new DeliveryStatusMessage(context); // unused since forever (0.5?) //case DateMessage.MESSAGE_TYPE: // return new DateMessage(context); case GarlicMessage.MESSAGE_TYPE: return new GarlicMessage(context); case TunnelDataMessage.MESSAGE_TYPE: return new TunnelDataMessage(context); case TunnelGatewayMessage.MESSAGE_TYPE: return new TunnelGatewayMessage(context); case DataMessage.MESSAGE_TYPE: return new DataMessage(context); // unused since 0.6.1.10 case TunnelBuildMessage.MESSAGE_TYPE: return new TunnelBuildMessage(context); case TunnelBuildReplyMessage.MESSAGE_TYPE: return new TunnelBuildReplyMessage(context); // since 0.7.10 case VariableTunnelBuildMessage.MESSAGE_TYPE: return new VariableTunnelBuildMessage(context); // since 0.7.10 case VariableTunnelBuildReplyMessage.MESSAGE_TYPE: return new VariableTunnelBuildReplyMessage(context); default: // unused Builder builder = _builders.get(Integer.valueOf(type)); if (builder != null) return builder.build(context); return new UnknownI2NPMessage(context, type); } } }