All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.i2p.data.i2np.I2NPMessageImpl Maven / Gradle / Ivy

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 {
    protected final Log _log;
    protected final I2PAppContext _context;
    protected long _expiration;

    /**
     *  Warning, lazily initialized by readBytes(), writeBytes(), toByteArray(),
     *  getUniqueId(), and setUniqueId(); otherwise will be -1.
     *  Extending classes should take care when accessing this field;
     *  to ensure initialization, use getUniqueId() instead.
     */
    protected long _uniqueId = -1;

    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;

    /** unused */
    private static final Map _builders = new ConcurrentHashMap(1);

    /** @deprecated unused */
    @Deprecated
    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;
        //_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.
     *  Unused - All transports provide encapsulation and so we have byte arrays available.
     *
     *  @deprecated unused
     *  @throws UnsupportedOperationException always
     */
    @Deprecated
    public void readBytes(InputStream in) {
        throw new UnsupportedOperationException();
    }

    /**
     *  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 { 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 type the message type or -1 if we should read it here * @return total length of the message * @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 = data[cur] & 0xff; 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; 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); byte[] calc = SimpleByteCache.acquire(Hash.HASH_LENGTH); // Compare the checksum in data to the checksum of the data after the checksum _context.sha().calculateHash(data, cur + CHECKSUM_LENGTH, sz, calc, 0); boolean eq = DataHelper.eq(data, cur, calc, 0, CHECKSUM_LENGTH); cur += CHECKSUM_LENGTH; SimpleByteCache.release(calc); if (!eq) throw new I2NPMessageException("Bad checksum on " + size + " byte I2NP " + getClass().getSimpleName()); //long start = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Reading bytes: type = " + type + " / uniqueId : " + _uniqueId + " / expiration : " + _expiration); 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 * @throws UnsupportedOperationException always */ @Deprecated public void writeBytes(OutputStream out) { throw new UnsupportedOperationException(); } /** * Replay resistant message Id */ public long getUniqueId() { // Lazy initialization of value if (_uniqueId < 0) { _uniqueId = _context.random().nextLong(MAX_ID_VALUE); } return _uniqueId; } /** * The ID is set to a random value when written but it can be overridden here. */ 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; } /** * The expiration is set to one minute from now in the constructor but it can be overridden here. */ public void setMessageExpiration(long exp) { _expiration = exp; } 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() { 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().getSimpleName()); return null; } return data; } /** * 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 for NTCP 1. * * @return the length written */ public int toByteArray(byte buffer[]) { return toByteArray(buffer, 0); } /** * Write the message to the buffer, returning the new offset (NOT the length). * 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 for NTCP 1. * * @param off the offset to start writing at * @return the new offset (NOT the length) * @since 0.9.36 with off param */ public int toByteArray(byte buffer[], int off) { int start = off; try { int rv = writeMessageBody(buffer, off + HEADER_LENGTH); int payloadLen = rv - (off + HEADER_LENGTH); byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH); _context.sha().calculateHash(buffer, off + HEADER_LENGTH, payloadLen, h, 0); buffer[off++] = (byte) getType(); // Lazy initialization of value if (_uniqueId < 0) { _uniqueId = _context.random().nextLong(MAX_ID_VALUE); } 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); return rv; } catch (I2NPMessageException ime) { _context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime); 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 (NOT the length) */ protected abstract int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException; /** * 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! * * @return the new offset (NOT the length) */ public int toRawByteArray(byte buffer[]) { try { int off = 0; buffer[off++] = (byte) getType(); // 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) { _context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime); throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime); } } /** * Write the message to the buffer, returning the new offset (NOT the length). * 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 9 byte header for NTCP 2. * * @param off the offset to start writing at * @return the new offset (NOT the length) * @since 0.9.36 */ public int toRawByteArrayNTCP2(byte buffer[], int off) { try { buffer[off++] = (byte) getType(); // Lazy initialization of value if (_uniqueId < 0) { _uniqueId = _context.random().nextLong(MAX_ID_VALUE); } DataHelper.toLong(buffer, off, 4, _uniqueId); off += 4; // 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) { _context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime); 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 { // 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)); } *****/ /** * 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 = buffer[offset] & 0xff; offset++; I2NPMessage msg = createMessage(ctx, type); try { // 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); msg.setMessageExpiration(expiration); return msg; } catch (IllegalArgumentException iae) { throw new I2NPMessageException("Corrupt message (negative expiration)", iae); } } /** * Read the message with a short 9-byte header. * THe header consists of a one-byte type, 4-byte ID, and a 4-byte expiration in seconds only. * Used by NTCP2 only! * @since 0.9.35 */ public static I2NPMessage fromRawByteArrayNTCP2(I2PAppContext ctx, byte buffer[], int offset, int len, I2NPMessageHandler handler) throws I2NPMessageException { int type = buffer[offset] & 0xff; offset++; I2NPMessage msg = createMessage(ctx, type); try { long id = DataHelper.fromLong(buffer, offset, 4); offset += 4; // 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 - 9; msg.readMessage(buffer, offset, dataSize, type, handler); msg.setUniqueId(id); msg.setMessageExpiration(expiration); return msg; } catch (IllegalArgumentException iae) { throw new I2NPMessageException("Corrupt message (negative expiration)", iae); } } /** * 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) { 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy