package net.i2p.router.tunnel; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.TunnelId; import net.i2p.util.ByteCache; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; /** * Gather fragments of I2NPMessages at a tunnel endpoint, making them available * for reading when complete. * * Warning - this is all unsynchronized here - receivers must implement synchronization * */ class FragmentedMessage { private final I2PAppContext _context; private final Log _log; private final long _messageId; private Hash _toRouter; private TunnelId _toTunnel; private final ByteArray _fragments[]; private boolean _lastReceived; private int _highFragmentNum; private final long _createdOn; private boolean _completed; private long _releasedAfter; private SimpleTimer2.TimedEvent _expireEvent; private static final ByteCache _cache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE); // 64 is pretty absurd, 32 is too, most likely private static final int MAX_FRAGMENTS = 64; private static final int MAX_FRAGMENT_SIZE = 996; public FragmentedMessage(I2PAppContext ctx, long messageId) { _context = ctx; _log = ctx.logManager().getLog(FragmentedMessage.class); _messageId = messageId; _fragments = new ByteArray[MAX_FRAGMENTS]; _highFragmentNum = -1; _releasedAfter = -1; _createdOn = ctx.clock().now(); } /** * Receive a followup fragment, though one of these may arrive at the endpoint * prior to the fragment # 0. * * @param fragmentNum sequence number within the message (1 - 63) * @param payload data for the fragment non-null * @param offset index into the payload where the fragment data starts (past headers/etc) * @param length how much past the offset should we snag? * @param isLast is this the last fragment in the message? */ public boolean receive(int fragmentNum, byte payload[], int offset, int length, boolean isLast) { if (fragmentNum <= 0 || fragmentNum >= MAX_FRAGMENTS) { if (_log.shouldLog(Log.WARN)) _log.warn("Bad followon fragment # == " + fragmentNum + " for messageId " + _messageId); return false; } if (length <= 0 || length > MAX_FRAGMENT_SIZE) { if (_log.shouldLog(Log.WARN)) _log.warn("Length is impossible (" + length + ") for messageId " + _messageId); return false; } if (offset + length > payload.length) { if (_log.shouldLog(Log.WARN)) _log.warn("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + _messageId); return false; } if (_log.shouldLog(Log.DEBUG)) _log.debug("Receive message " + _messageId + " fragment " + fragmentNum + " with " + length + " bytes (last? " + isLast + ") offset = " + offset); // we should just use payload[] and use an offset/length on it ByteArray ba = _cache.acquire(); //new ByteArray(payload, offset, length); //new byte[length]); System.arraycopy(payload, offset, ba.getData(), 0, length); ba.setValid(length); ba.setOffset(0); //System.arraycopy(payload, offset, ba.getData(), 0, length); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("fragment[" + fragmentNum + "/" + offset + "/" + length + "]: " // + Base64.encode(ba.getData(), ba.getOffset(), ba.getValid())); _fragments[fragmentNum] = ba; _lastReceived = _lastReceived || isLast; if (fragmentNum > _highFragmentNum) _highFragmentNum = fragmentNum; if (isLast && fragmentNum <= 0) { if (_log.shouldLog(Log.ERROR)) _log.error("hmm, isLast and fragmentNum=" + fragmentNum + " for message " + _messageId); return false; } return true; } /** * Receive the first fragment (#0) and related metadata. This may not be the first * one to arrive at the endpoint however. * * @param payload data for the fragment non-null * @param offset index into the payload where the fragment data starts (past headers/etc) * @param length how much past the offset should we snag? * @param isLast is this the last fragment in the message? * @param toRouter what router is this destined for (may be null) * @param toTunnel what tunnel is this destined for (may be null) */ public boolean receive(byte payload[], int offset, int length, boolean isLast, Hash toRouter, TunnelId toTunnel) { if (length <= 0 || length > MAX_FRAGMENT_SIZE) { if (_log.shouldLog(Log.WARN)) _log.warn("Length is impossible (" + length + ") for messageId " + _messageId); return false; } if (offset + length > payload.length) { if (_log.shouldLog(Log.WARN)) _log.warn("Length is impossible (" + length + "/" + offset + " out of " + payload.length + ") for messageId " + _messageId); return false; } if (_log.shouldLog(Log.DEBUG)) _log.debug("Receive message " + _messageId + " with " + length + " bytes (last? " + isLast + ") targetting " + toRouter + " / " + toTunnel + " offset=" + offset); ByteArray ba = _cache.acquire(); // new ByteArray(payload, offset, length); // new byte[length]); System.arraycopy(payload, offset, ba.getData(), 0, length); ba.setValid(length); ba.setOffset(0); //System.arraycopy(payload, offset, ba.getData(), 0, length); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("fragment[0/" + offset + "/" + length + "]: " // + Base64.encode(ba.getData(), ba.getOffset(), ba.getValid())); _fragments[0] = ba; _lastReceived = _lastReceived || isLast; _toRouter = toRouter; _toTunnel = toTunnel; if (_highFragmentNum < 0) _highFragmentNum = 0; return true; } public long getMessageId() { return _messageId; } public Hash getTargetRouter() { return _toRouter; } public TunnelId getTargetTunnel() { return _toTunnel; } public int getFragmentCount() { int found = 0; for (int i = 0; i < _fragments.length; i++) if (_fragments[i] != null) found++; return found; } /** used in the fragment handler so we can cancel the expire event on success */ public SimpleTimer2.TimedEvent getExpireEvent() { return _expireEvent; } public void setExpireEvent(SimpleTimer2.TimedEvent evt) { _expireEvent = evt; } /** have we received all of the fragments? */ public boolean isComplete() { if (!_lastReceived) return false; for (int i = 0; i <= _highFragmentNum; i++) if (_fragments[i] == null) return false; return true; } public int getCompleteSize() { if (!_lastReceived) throw new IllegalStateException("don't get the completed size when we're not complete!"); if (_releasedAfter > 0) { RuntimeException e = new RuntimeException("use after free in FragmentedMessage"); _log.error("FM completeSize()", e); throw e; } int size = 0; for (int i = 0; i <= _highFragmentNum; i++) { ByteArray ba = _fragments[i]; // NPE seen here, root cause unknown if (ba == null) throw new IllegalStateException("don't get the completed size when we're not complete! - null fragment i=" + i + " of " + _highFragmentNum); size += ba.getValid(); } return size; } /** how long has this fragmented message been alive? */ public long getLifetime() { return _context.clock().now() - _createdOn; } public boolean getReleased() { return _completed; } /**** public void writeComplete(OutputStream out) throws IOException { if (_releasedAfter > 0) { RuntimeException e = new RuntimeException("use after free in FragmentedMessage"); _log.error("FM writeComplete()", e); throw e; } for (int i = 0; i <= _highFragmentNum; i++) { ByteArray ba = _fragments[i]; out.write(ba.getData(), ba.getOffset(), ba.getValid()); } _completed = true; } ****/ /** */ private void writeComplete(byte target[], int offset) { if (_releasedAfter > 0) { RuntimeException e = new RuntimeException("use after free in FragmentedMessage"); _log.error("FM writeComplete() 2", e); throw e; } for (int i = 0; i <= _highFragmentNum; i++) { ByteArray ba = _fragments[i]; System.arraycopy(ba.getData(), ba.getOffset(), target, offset, ba.getValid()); offset += ba.getValid(); } _completed = true; } public byte[] toByteArray() { synchronized (this) { if (_releasedAfter > 0) return null; byte rv[] = new byte[getCompleteSize()]; writeComplete(rv, 0); releaseFragments(); return rv; } } public long getReleasedAfter() { return _releasedAfter; } public void failed() { synchronized (this) { releaseFragments(); } } /** * Called as one of the endpoints for the tunnel cache pipeline (see TunnelDataMessage) * */ private void releaseFragments() { if (_releasedAfter > 0) { RuntimeException e = new RuntimeException("double free in FragmentedMessage"); _log.error("FM releaseFragments()", e); throw e; } _releasedAfter = getLifetime(); for (int i = 0; i <= _highFragmentNum; i++) { ByteArray ba = _fragments[i]; if ( (ba != null) && (ba.getData().length == TrivialPreprocessor.PREPROCESSED_SIZE) ) { _cache.release(ba); _fragments[i] = null; } } } /**** public InputStream getInputStream() { return new FragmentInputStream(); } private class FragmentInputStream extends InputStream { private int _fragment; private int _offset; public FragmentInputStream() { _fragment = 0; _offset = 0; } public int read() throws IOException { while (true) { ByteArray ba = _fragments[_fragment]; if (ba == null) return -1; if (_offset >= ba.getValid()) { _fragment++; _offset = 0; } else { byte rv = ba.getData()[ba.getOffset()+_offset]; _offset++; return rv; } } } } ****/ /** toString */ @Override public String toString() { StringBuilder buf = new StringBuilder(128); buf.append("Fragments for ").append(_messageId).append(": "); for (int i = 0; i <= _highFragmentNum; i++) { ByteArray ba = _fragments[i]; if (ba != null) buf.append(i).append(":").append(ba.getValid()).append(" bytes "); else buf.append(i).append(":missing "); } buf.append(" highest received: ").append(_highFragmentNum); buf.append(" last received? ").append(_lastReceived); buf.append(" lifetime: ").append(DataHelper.formatDuration(_context.clock().now()-_createdOn)); if (_toRouter != null) { buf.append(" targetting ").append(_toRouter.toBase64().substring(0,4)); if (_toTunnel != null) buf.append(":").append(_toTunnel.getTunnelId()); } if (_completed) buf.append(" completed"); if (_releasedAfter > 0) buf.append(" released after " + DataHelper.formatDuration(_releasedAfter)); return buf.toString(); } /***** public static void main(String args[]) { try { I2PAppContext ctx = I2PAppContext.getGlobalContext(); DataMessage m = new DataMessage(ctx); m.setData(new byte[1024]); java.util.Arrays.fill(m.getData(), (byte)0xFF); m.setMessageExpiration(ctx.clock().now() + 60*1000); m.setUniqueId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); byte data[] = m.toByteArray(); I2NPMessage r0 = new I2NPMessageHandler(ctx).readMessage(data); System.out.println("peq? " + r0.equals(m)); FragmentedMessage msg = new FragmentedMessage(ctx); msg.receive(m.getUniqueId(), data, 0, 500, false, null, null); msg.receive(m.getUniqueId(), 1, data, 500, 500, false); msg.receive(m.getUniqueId(), 2, data, 1000, data.length-1000, true); if (!msg.isComplete()) throw new RuntimeException("Not complete?"); byte recv[] = msg.toByteArray(); I2NPMessage r = new I2NPMessageHandler(ctx).readMessage(recv); System.out.println("eq? " + m.equals(r)); } catch (Exception e) { e.printStackTrace(); } } ******/ }