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();
}
}