package net.i2p.router.transport.udp; import java.util.Date; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.OutNetMessage; import net.i2p.router.util.CDPQEntry; import net.i2p.util.Log; /** * Maintain the outbound fragmentation for resending, for a single message. * * All methods are thread-safe. * */ class OutboundMessageState implements CDPQEntry { private final I2PAppContext _context; private final Log _log; /** may be null if we are part of the establishment */ private final OutNetMessage _message; private final I2NPMessage _i2npMessage; /** will be null, unless we are part of the establishment */ private final PeerState _peer; private final long _expiration; private final byte[] _messageBuf; /** fixed fragment size across the message */ private final int _fragmentSize; /** bitmask, 0 if acked, all 0 = complete */ private long _fragmentAcks; private final int _numFragments; private final long _startedOn; private long _nextSendTime; private int _pushCount; private int _maxSends; // we can't use the ones in _message since it is null for injections private long _enqueueTime; private long _seqNum; public static final int MAX_MSG_SIZE = 32 * 1024; private static final long EXPIRATION = 10*1000; /** * "injected" message from the establisher. * * Called from UDPTransport. * @throws IllegalArgumentException if too big or if msg or peer is null */ public OutboundMessageState(I2PAppContext context, I2NPMessage msg, PeerState peer) { this(context, null, msg, peer); } /** * Normal constructor. * * Called from OutboundMessageFragments. * @throws IllegalArgumentException if too big or if msg or peer is null */ public OutboundMessageState(I2PAppContext context, OutNetMessage m, PeerState peer) { this(context, m, m.getMessage(), peer); } /** * Internal. * @param m null if msg is "injected" * @throws IllegalArgumentException if too big or if msg or peer is null */ private OutboundMessageState(I2PAppContext context, OutNetMessage m, I2NPMessage msg, PeerState peer) { if (msg == null || peer == null) throw new IllegalArgumentException(); _context = context; _log = _context.logManager().getLog(OutboundMessageState.class); _message = m; _i2npMessage = msg; _peer = peer; _startedOn = _context.clock().now(); _nextSendTime = _startedOn; _expiration = _startedOn + EXPIRATION; //_expiration = msg.getExpiration(); // now "fragment" it int totalSize = _i2npMessage.getRawMessageSize(); if (totalSize > MAX_MSG_SIZE) throw new IllegalArgumentException("Size too large! " + totalSize); _messageBuf = new byte[totalSize]; _i2npMessage.toRawByteArray(_messageBuf); _fragmentSize = _peer.fragmentSize(); int numFragments = totalSize / _fragmentSize; if (numFragments * _fragmentSize < totalSize) numFragments++; // This should never happen, as 534 bytes * 64 fragments > 32KB, and we won't bid on > 32KB if (numFragments > InboundMessageState.MAX_FRAGMENTS) throw new IllegalArgumentException("Fragmenting a " + totalSize + " message into " + numFragments + " fragments - too many!"); _numFragments = numFragments; // all 1's where we care _fragmentAcks = _numFragments < 64 ? mask(_numFragments) - 1L : -1L; } /** * @param fragment 0-63 */ private static long mask(int fragment) { return 1L << fragment; } public OutNetMessage getMessage() { return _message; } public long getMessageId() { return _i2npMessage.getUniqueId(); } public PeerState getPeer() { return _peer; } public boolean isExpired() { return _expiration < _context.clock().now(); } public synchronized boolean isComplete() { return _fragmentAcks == 0; } public synchronized int getUnackedSize() { int rv = 0; if (isComplete()) return rv; int lastSize = _messageBuf.length % _fragmentSize; if (lastSize == 0) lastSize = _fragmentSize; for (int i = 0; i < _numFragments; i++) { if (needsSending(i)) { if (i + 1 == _numFragments) rv += lastSize; else rv += _fragmentSize; } } return rv; } public synchronized boolean needsSending(int fragment) { return (_fragmentAcks & mask(fragment)) != 0; } public long getLifetime() { return _context.clock().now() - _startedOn; } /** * Ack all the fragments in the ack list. * * @return true if the message was completely ACKed */ public synchronized boolean acked(ACKBitfield bitfield) { // stupid brute force, but the cardinality should be trivial int highest = bitfield.highestReceived(); for (int i = 0; i <= highest && i < _numFragments; i++) { if (bitfield.received(i)) _fragmentAcks &= ~mask(i); } return isComplete(); } public long getNextSendTime() { return _nextSendTime; } public void setNextSendTime(long when) { _nextSendTime = when; } /** * The max number of sends for any fragment, which is the * same as the push count, at least as it's coded now. */ public synchronized int getMaxSends() { return _maxSends; } /** * The number of times we've pushed some fragments, which is the * same as the max sends, at least as it's coded now. */ public synchronized int getPushCount() { return _pushCount; } /** * Note that we have pushed the message fragments. * Increments push count (and max sends... why?) * @return true if this is the first push */ public synchronized boolean push() { boolean rv = _pushCount == 0; // these will never be different... _pushCount++; _maxSends = _pushCount; return rv; } /** * How many fragments in the message. */ public int getFragmentCount() { return _numFragments; } /** * The size of the I2NP message. Does not include any SSU overhead. */ public int getMessageSize() { return _messageBuf.length; } /** * The size in bytes of the fragment * * @param fragmentNum the number of the fragment * @return the size of the fragment specified by the number */ public int fragmentSize(int fragmentNum) { if (fragmentNum + 1 == _numFragments) { int valid = _messageBuf.length; if (valid <= _fragmentSize) return valid; // bugfix 0.8.12 int mod = valid % _fragmentSize; return mod == 0 ? _fragmentSize : mod; } else { return _fragmentSize; } } /** * Write a part of the the message onto the specified buffer. * * @param out target to write * @param outOffset into outOffset to begin writing * @param fragmentNum fragment to write (0 indexed) * @return bytesWritten */ public int writeFragment(byte out[], int outOffset, int fragmentNum) { int start = _fragmentSize * fragmentNum; int toSend = fragmentSize(fragmentNum); int end = start + toSend; if (end <= _messageBuf.length && outOffset + toSend <= out.length) { System.arraycopy(_messageBuf, start, out, outOffset, toSend); return toSend; } else { if (_log.shouldLog(Log.WARN)) _log.warn("Error: " + start + '/' + end + '/' + outOffset + '/' + out.length); } return -1; } /** * For CDQ * @since 0.9.3 */ public void setEnqueueTime(long now) { _enqueueTime = now; } /** * For CDQ * @since 0.9.3 */ public long getEnqueueTime() { return _enqueueTime; } /** * For CDQ * @since 0.9.3 */ public void drop() { _peer.getTransport().failed(this, false); } /** * For CDPQ * @since 0.9.3 */ public void setSeqNum(long num) { _seqNum = num; } /** * For CDPQ * @since 0.9.3 */ public long getSeqNum() { return _seqNum; } /** * For CDPQ * @return OutNetMessage priority or 1000 for injected * @since 0.9.3 */ public int getPriority() { return _message != null ? _message.getPriority() : 1000; } @Override public String toString() { StringBuilder buf = new StringBuilder(256); buf.append("OB Message ").append(_i2npMessage.getUniqueId()); buf.append(" type ").append(_i2npMessage.getType()); buf.append(" with ").append(_numFragments).append(" fragments"); buf.append(" of size ").append(_messageBuf.length); buf.append(" volleys: ").append(_maxSends); buf.append(" lifetime: ").append(getLifetime()); if (!isComplete()) { buf.append(" pending fragments: "); for (int i = 0; i < _numFragments; i++) { if (needsSending(i)) buf.append(i).append(' '); } } //buf.append(" to: ").append(_peer.toString()); return buf.toString(); } }