package net.i2p.router.tunnel; import java.util.List; import net.i2p.data.Base64; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.util.ByteCache; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; /** * Do the simplest thing possible for preprocessing - for each message available, * turn it into the minimum number of fragmented preprocessed blocks, sending * each of those out. This does not coallesce message fragments or delay for more * optimal throughput. * * See FragmentHandler Javadoc for tunnel message fragment format * * Not instantiated directly except in unit tests; see BatchedPreprocessor * */ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { protected final RouterContext _context; protected final Log _log; public static final int PREPROCESSED_SIZE = 1024; protected static final int IV_SIZE = HopProcessor.IV_LENGTH; /** * Here in tunnels, we take from the cache but never add to it. * In other words, we take advantage of other places in the router also using 1024-byte ByteCaches * (since ByteCache only maintains once instance for each size) * Used in BatchedPreprocessor; see add'l comments there */ protected static final ByteCache _dataCache = ByteCache.getInstance(32, PREPROCESSED_SIZE); public TrivialPreprocessor(RouterContext ctx) { _context = ctx; _log = ctx.logManager().getLog(getClass()); } /** how long do we want to wait before flushing */ public long getDelayAmount() { return 0; } /** * Return true if there were messages remaining, and we should queue up * a delayed flush to clear them * * NOTE: Unused here, see BatchedPreprocessor override, super is not called. */ public boolean preprocessQueue(List<PendingGatewayMessage> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { throw new UnsupportedOperationException("unused, right?"); } protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {} /** * Wrap the preprocessed fragments with the necessary padding / checksums * to act as a tunnel message. * * @param fragmentLength fragments[0:fragmentLength] is used */ protected void preprocess(byte fragments[], int fragmentLength) { byte iv[] = SimpleByteCache.acquire(IV_SIZE); _context.random().nextBytes(iv); // payload ready, now H(instructions+payload+IV) System.arraycopy(iv, 0, fragments, fragmentLength, IV_SIZE); byte[] hashBuf = SimpleByteCache.acquire(Hash.HASH_LENGTH); //Hash h = _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE); _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE, hashBuf, 0); //Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE); //_log.debug("before shift: " + Base64.encode(target)); // now shiiiiiift int distance = PREPROCESSED_SIZE - fragmentLength; System.arraycopy(fragments, 0, fragments, distance, fragmentLength); //if (_log.shouldLog(Log.DEBUG)) // _log.debug(msg.getMessageId() + ": fragments begin at " + distance + " (size=" // + payloadLength + " offset=" + offset +")"); java.util.Arrays.fill(fragments, 0, distance, (byte)0x0); //_log.debug("after shift: " + Base64.encode(target)); int offset = 0; System.arraycopy(iv, 0, fragments, offset, IV_SIZE); offset += IV_SIZE; //System.arraycopy(h.getData(), 0, fragments, offset, 4); System.arraycopy(hashBuf, 0, fragments, offset, 4); offset += 4; //_log.debug("before pad : " + Base64.encode(target)); SimpleByteCache.release(hashBuf); SimpleByteCache.release(iv); // fits in a single message, so may be smaller than the full size int numPadBytes = PREPROCESSED_SIZE // max - IV_SIZE // hmm.. - 4 // 4 bytes of the SHA256 - 1 // the 0x00 after the padding - fragmentLength; // the size of the fragments (instructions+payload) //_log.debug("# pad bytes: " + numPadBytes + " payloadLength: " + payloadLength + " instructions: " + instructionsLength); if (numPadBytes > 0) { fillRandomNonZero(fragments, offset, numPadBytes); offset += numPadBytes; } fragments[offset] = 0x0; // no more padding offset++; if (_log.shouldLog(Log.DEBUG)) _log.debug("Preprocessing beginning of the fragment instructions at " + offset); } /** * Efficiently fill with nonzero random data * Don't waste too much entropy or call random() too often. * @since 0.8.5 */ private void fillRandomNonZero(byte[] b, int off, int len) { // get about as much as we think we will need, overestimate some final int est = len + (len / 128) + 3; final byte[] tmp = new byte[est]; _context.random().nextBytes(tmp); int extra = len; for (int i = 0; i < len; i++) { while (tmp[i] == 0) { if (extra < est) tmp[i] = tmp[extra++]; // use from the extra we have at the end else tmp[i] = (byte)(_context.random().nextInt() & 0xFF); // waste 3/4 of the entropy } } System.arraycopy(tmp, 0, b, off, len); } /** is this a follw up byte? */ private static final byte MASK_IS_SUBSEQUENT = FragmentHandler.MASK_IS_SUBSEQUENT; /** how should this be delivered? shift this 5 the right and get TYPE_* */ //private static final byte MASK_TYPE = FragmentHandler.MASK_TYPE; /** is this the first of a fragmented message? */ private static final byte MASK_FRAGMENTED = FragmentHandler.MASK_FRAGMENTED; /** * are there follow up headers? * @deprecated unimplemented */ @Deprecated private static final byte MASK_EXTENDED = FragmentHandler.MASK_EXTENDED; private static final byte MASK_TUNNEL = (byte)(FragmentHandler.TYPE_TUNNEL << 5); private static final byte MASK_ROUTER = (byte)(FragmentHandler.TYPE_ROUTER << 5); protected int writeFirstFragment(PendingGatewayMessage msg, byte target[], int offset) { boolean fragmented = false; int instructionsLength = getInstructionsSize(msg); int payloadLength = msg.getData().length - msg.getOffset(); if (offset + payloadLength + instructionsLength + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) { fragmented = true; instructionsLength += 4; // messageId payloadLength = PREPROCESSED_SIZE - IV_SIZE - 1 - 4 - instructionsLength - offset; if (payloadLength <= 0) throw new RuntimeException("Fragment too small! payloadLen=" + payloadLength + " target.length=" + target.length + " offset="+offset + " msg.length=" + msg.getData().length + " msg.offset=" + msg.getOffset() + " instructionsLength=" + instructionsLength + " for " + msg); } if (payloadLength <= 0) throw new RuntimeException("Full size too small! payloadLen=" + payloadLength + " target.length=" + target.length + " offset="+offset + " msg.length=" + msg.getData().length + " msg.offset=" + msg.getOffset() + " instructionsLength=" + instructionsLength + " for " + msg); // first fragment, or full message target[offset] = 0x0; if (msg.getToTunnel() != null) target[offset] |= MASK_TUNNEL; else if (msg.getToRouter() != null) target[offset] |= MASK_ROUTER; if (fragmented) target[offset] |= MASK_FRAGMENTED; if (_log.shouldLog(Log.DEBUG)) _log.debug("CONTROL: " + Integer.toHexString(target[offset])); offset++; if (msg.getToTunnel() != null) { DataHelper.toLong(target, offset, 4, msg.getToTunnel().getTunnelId()); offset += 4; } if (msg.getToRouter() != null) { System.arraycopy(msg.getToRouter().getData(), 0, target, offset, Hash.HASH_LENGTH); offset += Hash.HASH_LENGTH; } if (fragmented) { DataHelper.toLong(target, offset, 4, msg.getMessageId()); if (_log.shouldLog(Log.DEBUG)) _log.debug("writing messageId= " + msg.getMessageId() + " at offset " + offset); offset += 4; } DataHelper.toLong(target, offset, 2, payloadLength); offset += 2; //_log.debug("raw data : " + Base64.encode(msg.getData())); System.arraycopy(msg.getData(), msg.getOffset(), target, offset, payloadLength); if (_log.shouldLog(Log.DEBUG)) _log.debug("initial fragment[" + msg.getMessageId() + "/" + msg.getFragmentNumber()+ "/" + (PREPROCESSED_SIZE - offset - payloadLength) + "/" + payloadLength + "]: " ); //+ Base64.encode(target, offset, payloadLength)); offset += payloadLength; msg.setOffset(msg.getOffset() + payloadLength); if (fragmented) msg.incrementFragmentNumber(); return offset; } protected int writeSubsequentFragment(PendingGatewayMessage msg, byte target[], int offset) { boolean isLast = true; int instructionsLength = getInstructionsSize(msg); int payloadLength = msg.getData().length - msg.getOffset(); if (payloadLength + instructionsLength + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) { isLast = false; payloadLength = PREPROCESSED_SIZE - IV_SIZE - 1 - 4 - instructionsLength; } // first fragment, or full message target[offset] = 0x0; target[offset] |= MASK_IS_SUBSEQUENT; target[offset] |= (byte)(msg.getFragmentNumber() << 1); // max 63 fragments if (isLast) target[offset] |= 1; if (_log.shouldLog(Log.DEBUG)) _log.debug("CONTROL: " + Integer.toHexString(target[offset]) + "/" + Base64.encode(target, offset, 1) + " at offset " + offset); offset++; DataHelper.toLong(target, offset, 4, msg.getMessageId()); offset += 4; DataHelper.toLong(target, offset, 2, payloadLength); offset += 2; System.arraycopy(msg.getData(), msg.getOffset(), target, offset, payloadLength); if (_log.shouldLog(Log.DEBUG)) _log.debug("subsequent fragment[" + msg.getMessageId() + "/" + msg.getFragmentNumber()+ "/" + offset + "/" + payloadLength + "]: " ); //+ Base64.encode(target, offset, payloadLength)); offset += payloadLength; if (!isLast) msg.incrementFragmentNumber(); msg.setOffset(msg.getOffset() + payloadLength); return offset; } /** * @return generally 3 or 35 or 39 for first fragment, 7 for subsequent fragments. * * Does NOT include 4 for the message ID if the message will be fragmented; * call getInstructionAugmentationSize() for that. */ protected static int getInstructionsSize(PendingGatewayMessage msg) { if (msg.getFragmentNumber() > 0) return 7; // control byte int header = 1; // tunnel ID if (msg.getToTunnel() != null) header += 4; // router hash if (msg.getToRouter() != null) header += 32; // size header += 2; return header; } /** @return 0 or 4 */ protected static int getInstructionAugmentationSize(PendingGatewayMessage msg, int offset, int instructionsSize) { int payloadLength = msg.getData().length - msg.getOffset(); if (offset + payloadLength + instructionsSize + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) { // requires fragmentation, so include the messageId return 4; } return 0; } }