package net.i2p.client.streaming.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Signature;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log;
/**
* This contains solely the data that goes out on the wire,
* including the local and remote port which is embedded in
* the I2CP overhead, not in the packet itself.
* This is the class used for inbound packets.
* For local state saved for outbound packets, see the PacketLocal extension.
*
* <p>
*
* Contain a single packet transferred as part of a streaming connection.
* The data format is as follows:<ul>
* <li>{@link #getSendStreamId sendStreamId} [4 byte value]</li>
* <li>{@link #getReceiveStreamId receiveStreamId} [4 byte value]</li>
* <li>{@link #getSequenceNum sequenceNum} [4 byte unsigned integer]</li>
* <li>{@link #getAckThrough ackThrough} [4 byte unsigned integer]</li>
* <li>number of NACKs [1 byte unsigned integer]</li>
* <li>that many {@link #getNacks NACKs}</li>
* <li>{@link #getResendDelay resendDelay} [1 byte integer]</li>
* <li>flags [2 byte value]</li>
* <li>option data size [2 byte integer]</li>
* <li>option data specified by those flags [0 or more bytes]</li>
* <li>payload [remaining packet size]</li>
* </ul>
*
* <p>The flags field above specifies some metadata about the packet, and in
* turn may require certain additional data to be included. The flags are
* as follows (with any data structures specified added to the options area
* in the given order):</p><ol>
* <li>{@link #FLAG_SYNCHRONIZE}: no option data</li>
* <li>{@link #FLAG_CLOSE}: no option data</li>
* <li>{@link #FLAG_RESET}: no option data</li>
* <li>{@link #FLAG_SIGNATURE_INCLUDED}: {@link net.i2p.data.Signature}</li>
* <li>{@link #FLAG_SIGNATURE_REQUESTED}: no option data</li>
* <li>{@link #FLAG_FROM_INCLUDED}: {@link net.i2p.data.Destination}</li>
* <li>{@link #FLAG_DELAY_REQUESTED}: 2 byte integer</li>
* <li>{@link #FLAG_MAX_PACKET_SIZE_INCLUDED}: 2 byte integer</li>
* <li>{@link #FLAG_PROFILE_INTERACTIVE}: no option data</li>
* <li>{@link #FLAG_ECHO}: no option data</li>
* <li>{@link #FLAG_NO_ACK}: no option data - this appears to be unused, we always ack, even for the first packet</li>
* </ol>
*
* <p>If the signature is included, it uses the Destination's DSA key
* to sign the entire header and payload with the space in the options
* for the signature being set to all zeroes.</p>
*
* <p>If the sequenceNum is 0 and the SYN is not set, this is a plain ACK
* packet that should not be ACKed</p>
*
* NOTE: All setters unsynchronized.
*
*/
class Packet {
protected final I2PSession _session;
private long _sendStreamId;
private long _receiveStreamId;
private long _sequenceNum;
private long _ackThrough;
protected long _nacks[];
private int _resendDelay;
private int _flags;
private ByteArray _payload;
// the next four are set only if the flags say so
protected Signature _optionSignature;
protected Destination _optionFrom;
private int _optionDelay;
private int _optionMaxSize;
private int _localPort;
private int _remotePort;
/**
* The receiveStreamId will be set to this when the packet doesn't know
* what ID will be assigned by the remote peer (aka this is the initial
* synchronize packet)
*
*/
public static final long STREAM_ID_UNKNOWN = 0l;
public static final long MAX_STREAM_ID = 0xffffffffl;
/**
* This packet is creating a new socket connection (if the receiveStreamId
* is STREAM_ID_UNKNOWN) or it is acknowledging a request to
* create a connection and in turn is accepting the socket.
*
*/
public static final int FLAG_SYNCHRONIZE = (1 << 0);
/**
* The sender of this packet will not be sending any more payload data.
*/
public static final int FLAG_CLOSE = (1 << 1);
/**
* This packet is being sent to signify that the socket does not exist
* (or, if in response to an initial synchronize packet, that the
* connection was refused).
*
*/
public static final int FLAG_RESET = (1 << 2);
/**
* This packet contains a DSA signature from the packet's sender. This
* signature is within the packet options. All synchronize packets must
* have this flag set.
*
*/
public static final int FLAG_SIGNATURE_INCLUDED = (1 << 3);
/**
* This packet wants the recipient to include signatures on subsequent
* packets sent to the creator of this packet.
*/
public static final int FLAG_SIGNATURE_REQUESTED = (1 << 4);
/**
* This packet includes the full I2P destination of the packet's sender.
* The initial synchronize packet must have this flag set.
*/
public static final int FLAG_FROM_INCLUDED = (1 << 5);
/**
* This packet includes an explicit request for the recipient to delay
* sending any packets with data for a given amount of time.
*
*/
public static final int FLAG_DELAY_REQUESTED = (1 << 6);
/**
* This packet includes a request that the recipient not send any
* subsequent packets with payloads greater than a specific size.
* If not set and no prior value was delivered, the maximum value
* will be assumed (approximately 32KB).
*
*/
public static final int FLAG_MAX_PACKET_SIZE_INCLUDED = (1 << 7);
/**
* If set, this packet is travelling as part of an interactive flow,
* meaning it is more lag sensitive than throughput sensitive. aka
* send data ASAP rather than waiting around to send full packets.
*
*/
public static final int FLAG_PROFILE_INTERACTIVE = (1 << 8);
/**
* If set, this packet is a ping (if sendStreamId is set) or a
* ping reply (if receiveStreamId is set).
*/
public static final int FLAG_ECHO = (1 << 9);
/**
* If set, this packet doesn't really want to ack anything
*/
public static final int FLAG_NO_ACK = (1 << 10);
public static final int DEFAULT_MAX_SIZE = 32*1024;
protected static final int MAX_DELAY_REQUEST = 65535;
public static final int MIN_DELAY_CHOKE = 60001;
public static final int SEND_DELAY_CHOKE = 61000;
/**
* Does no initialization.
* See readPacket() for inbound packets, and the setters for outbound packets.
*/
public Packet(I2PSession session) {
_session = session;
}
/** @since 0.9.21 */
public I2PSession getSession() {
return _session;
}
private boolean _sendStreamIdSet = false;
/** what stream do we send data to the peer on?
* @return stream ID we use to send data
*/
public long getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(long id) {
// allow resetting to the same id (race)
if ( (_sendStreamIdSet) && (_sendStreamId > 0) && _sendStreamId != id)
throw new RuntimeException("Send stream ID already set [" + _sendStreamId + ", " + id + "]");
_sendStreamIdSet = true;
_sendStreamId = id;
}
private boolean _receiveStreamIdSet = false;
/**
* stream the replies should be sent on. this should be 0 if the
* connection is still being built.
* @return stream ID we use to get data, zero if the connection is still being built.
*/
public long getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(long id) {
// allow resetting to the same id (race)
if ( (_receiveStreamIdSet) && (_receiveStreamId > 0) && _receiveStreamId != id)
throw new RuntimeException("Receive stream ID already set [" + _receiveStreamId + ", " + id + "]");
_receiveStreamIdSet = true;
_receiveStreamId = id;
}
/** 0-indexed sequence number for this Packet in the sendStream
* @return 0-indexed sequence number for current Packet in current sendStream
*/
public long getSequenceNum() { return _sequenceNum; }
public void setSequenceNum(long num) { _sequenceNum = num; }
/**
* The highest packet sequence number that received
* on the receiveStreamId. This field is ignored on the initial
* connection packet (where receiveStreamId is the unknown id) or
* if FLAG_NO_ACK is set.
*
* @return The highest packet sequence number received on receiveStreamId, or -1 if FLAG_NO_ACK
*/
public long getAckThrough() {
if (isFlagSet(FLAG_NO_ACK))
return -1;
else
return _ackThrough;
}
/**
* @param id if < 0, sets FLAG_NO_ACK
*/
public void setAckThrough(long id) {
if (id < 0)
setFlag(FLAG_NO_ACK);
_ackThrough = id;
}
/**
* List of packet sequence numbers below the getAckThrough() value
* have not been received. this may be null.
*
* @return List of packet sequence numbers not ACKed, or null if there are none.
*/
public long[] getNacks() { return _nacks; }
public void setNacks(long nacks[]) { _nacks = nacks; }
/**
* How long is the creator of this packet going to wait before
* resending this packet (if it hasn't yet been ACKed). The
* value is seconds since the packet was created.
*
* Unused.
* Broken before release 0.7.8
* Not to be used without sanitizing for huge values.
* Setters from options did not divide by 1000, and the options default
* is 1000, so the value sent in the 1-byte field was always
* 1000 & 0xff = 0xe8 = 232
*
* @return Delay before resending a packet in seconds.
*/
public int getResendDelay() { return _resendDelay; }
/**
* Unused.
* Broken before release 0.7.8
* See above
*/
public void setResendDelay(int numSeconds) { _resendDelay = numSeconds; }
public static final int MAX_PAYLOAD_SIZE = 32*1024;
/** get the actual payload of the message. may be null
* @return the payload of the message, null if none.
*/
public ByteArray getPayload() { return _payload; }
public void setPayload(ByteArray payload) {
_payload = payload;
if ( (payload != null) && (payload.getValid() > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("Too large payload: " + payload.getValid());
}
public int getPayloadSize() {
return (_payload == null ? 0 : _payload.getValid());
}
/** does nothing right now */
public void releasePayload() {
//_payload = null;
}
public ByteArray acquirePayload() {
_payload = new ByteArray(new byte[Packet.MAX_PAYLOAD_SIZE]);
return _payload;
}
/** is a particular flag set on this packet?
* @param flag bitmask of any flag(s)
* @return true if set, false if not.
*/
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
/**
* @param flag bitmask of any flag(s)
*/
public void setFlag(int flag) { _flags |= flag; }
/**
* @param flag bitmask of any flag(s)
* @param set true to set, false to clear
*/
public void setFlag(int flag, boolean set) {
if (set)
_flags |= flag;
else
_flags &= ~flag;
}
private void setFlags(int flags) { _flags = flags; }
/**
* The signature on the packet (only included if the flag for it is set)
*
* Warning, may be typed wrong on incoming packets for EdDSA
* before verifySignature() is called.
*
* @return signature on the packet if the flag for signatures is set
*/
public Signature getOptionalSignature() { return _optionSignature; }
/**
* This also sets flag FLAG_SIGNATURE_INCLUDED
*/
public void setOptionalSignature(Signature sig) {
setFlag(FLAG_SIGNATURE_INCLUDED, sig != null);
_optionSignature = sig;
}
/** the sender of the packet (only included if the flag for it is set)
* @return the sending Destination
*/
public Destination getOptionalFrom() { return _optionFrom; }
/**
* This sets the from field in the packet to the Destination for the session
* provided in the constructor.
* This also sets flag FLAG_FROM_INCLUDED
*/
public void setOptionalFrom() {
setFlag(FLAG_FROM_INCLUDED, true);
_optionFrom = _session.getMyDestination();
}
/**
* How many milliseconds the sender of this packet wants the recipient
* to wait before sending any more data (only valid if the flag for it is
* set)
* @return How long the sender wants the recipient to wait before sending any more data in ms.
*/
public int getOptionalDelay() { return _optionDelay; }
/**
* Caller must also call setFlag(FLAG_DELAY_REQUESTED)
*/
public void setOptionalDelay(int delayMs) {
if (delayMs > MAX_DELAY_REQUEST)
_optionDelay = MAX_DELAY_REQUEST;
else if (delayMs < 0)
_optionDelay = 0;
else
_optionDelay = delayMs;
}
/**
* What is the largest payload the sender of this packet wants to receive?
*
* @return Maximum payload size sender can receive (MRU)
*/
public int getOptionalMaxSize() { return _optionMaxSize; }
/**
* This also sets flag FLAG_MAX_PACKET_SIZE_INCLUDED
*/
public void setOptionalMaxSize(int numBytes) {
setFlag(FLAG_MAX_PACKET_SIZE_INCLUDED, numBytes > 0);
_optionMaxSize = numBytes;
}
/**
* @return Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
* @since 0.8.9
*/
public int getLocalPort() {
return _localPort;
}
/**
* Must be called to change the port, not set by readPacket()
* as the port is out-of-band in the I2CP header.
* @since 0.8.9
*/
public void setLocalPort(int port) {
_localPort = port;
}
/**
* @return Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
* @since 0.8.9
*/
public int getRemotePort() {
return _remotePort;
}
/**
* Must be called to change the port, not set by readPacket()
* as the port is out-of-band in the I2CP header.
* @since 0.8.9
*/
public void setRemotePort(int port) {
_remotePort = port;
}
/**
* Write the packet to the buffer (starting at the offset) and return
* the number of bytes written.
*
* @param buffer bytes to write to a destination
* @param offset starting point in the buffer to send
* @return Count actually written
* @throws IllegalStateException if there is data missing or otherwise b0rked
*/
public int writePacket(byte buffer[], int offset) throws IllegalStateException {
return writePacket(buffer, offset, 0);
}
/**
* @param fakeSigLen if 0, include the real signature in _optionSignature;
* if nonzero, leave space for that many bytes
*/
protected int writePacket(byte buffer[], int offset, int fakeSigLen) throws IllegalStateException {
int cur = offset;
DataHelper.toLong(buffer, cur, 4, (_sendStreamId >= 0 ? _sendStreamId : STREAM_ID_UNKNOWN));
cur += 4;
DataHelper.toLong(buffer, cur, 4, (_receiveStreamId >= 0 ? _receiveStreamId : STREAM_ID_UNKNOWN));
cur += 4;
DataHelper.toLong(buffer, cur, 4, _sequenceNum > 0 ? _sequenceNum : 0);
cur += 4;
DataHelper.toLong(buffer, cur, 4, _ackThrough > 0 ? _ackThrough : 0);
cur += 4;
if (_nacks != null) {
// if max win is ever > 255, limit to 255
DataHelper.toLong(buffer, cur, 1, _nacks.length);
cur++;
for (int i = 0; i < _nacks.length; i++) {
DataHelper.toLong(buffer, cur, 4, _nacks[i]);
cur += 4;
}
} else {
DataHelper.toLong(buffer, cur, 1, 0);
cur++;
}
DataHelper.toLong(buffer, cur, 1, _resendDelay > 0 ? _resendDelay : 0);
cur++;
DataHelper.toLong(buffer, cur, 2, _flags);
cur += 2;
int optionSize = 0;
if (isFlagSet(FLAG_DELAY_REQUESTED))
optionSize += 2;
if (isFlagSet(FLAG_FROM_INCLUDED))
optionSize += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
optionSize += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (fakeSigLen > 0)
optionSize += fakeSigLen;
else if (_optionSignature != null)
optionSize += _optionSignature.length();
else
throw new IllegalStateException();
}
DataHelper.toLong(buffer, cur, 2, optionSize);
cur += 2;
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
DataHelper.toLong(buffer, cur, 2, _optionDelay > 0 ? _optionDelay : 0);
cur += 2;
}
if (isFlagSet(FLAG_FROM_INCLUDED)) {
cur += _optionFrom.writeBytes(buffer, cur);
}
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE);
cur += 2;
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (fakeSigLen == 0) {
// we're signing (or validating)
System.arraycopy(_optionSignature.getData(), 0, buffer, cur, _optionSignature.length());
cur += _optionSignature.length();
} else {
Arrays.fill(buffer, cur, cur + fakeSigLen, (byte)0x0);
cur += fakeSigLen;
}
}
if (_payload != null) {
try {
System.arraycopy(_payload.getData(), _payload.getOffset(), buffer, cur, _payload.getValid());
} catch (ArrayIndexOutOfBoundsException aioobe) {
String error = "payload.length: " + _payload.getValid() + " buffer.length: " + buffer.length + " cur: " + cur;
I2PAppContext context = I2PAppContext.getCurrentContext();
if (context != null) {
Log l = context.logManager().getLog(Packet.class);
l.log(Log.ERROR,error,aioobe);
} else {
System.err.println(error);
aioobe.printStackTrace(System.out);
}
throw aioobe;
}
cur += _payload.getValid();
}
return cur - offset;
}
/**
* how large would this packet be if we wrote it
* @return How large the current packet would be
*
* @throws IllegalStateException
*/
private int writtenSize() {
//int size = 0;
//size += 4; // _sendStreamId.length;
//size += 4; // _receiveStreamId.length;
//size += 4; // sequenceNum
//size += 4; // ackThrough
// size++; // nacks length
//size++; // resendDelay
//size += 2; // flags
//size += 2; // option size
int size = 22;
if (_nacks != null) {
// if max win is ever > 255, limit to 255
size += 4 * _nacks.length;
}
if (isFlagSet(FLAG_DELAY_REQUESTED))
size += 2;
if (isFlagSet(FLAG_FROM_INCLUDED))
size += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
size += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
size += _optionSignature.length();
if (_payload != null) {
size += _payload.getValid();
}
return size;
}
/**
* Read the packet from the buffer (starting at the offset) and return
* the number of bytes read.
*
* @param buffer packet buffer containing the data
* @param offset index into the buffer to start readign
* @param length how many bytes within the buffer past the offset are
* part of the packet?
*
* @throws IllegalArgumentException if the data is b0rked
*/
public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException {
if (buffer.length - offset < length)
throw new IllegalArgumentException("len=" + buffer.length + " off=" + offset + " length=" + length);
if (length < 22) // min header size
throw new IllegalArgumentException("Too small: len=" + buffer.length);
int cur = offset;
setSendStreamId(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
setReceiveStreamId(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
setSequenceNum(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
setAckThrough(DataHelper.fromLong(buffer, cur, 4));
cur += 4;
int numNacks = buffer[cur] & 0xff;
cur++;
if (length < 22 + numNacks*4)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
if (numNacks > 0) {
long nacks[] = new long[numNacks];
for (int i = 0; i < numNacks; i++) {
nacks[i] = DataHelper.fromLong(buffer, cur, 4);
cur += 4;
}
setNacks(nacks);
} else {
setNacks(null);
}
setResendDelay(buffer[cur] & 0xff);
cur++;
setFlags((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
cur += 2;
if (length < 22 + numNacks*4 + optionSize)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks and "
+ optionSize + " options: " + length);
int payloadBegin = cur + optionSize;
int payloadSize = length - payloadBegin;
if ( (payloadSize < 0) || (payloadSize > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
// skip ahead to the payload
//_payload = new ByteArray(new byte[payloadSize]);
_payload = new ByteArray(buffer, payloadBegin, payloadSize);
//System.arraycopy(buffer, payloadBegin, _payload.getData(), 0, payloadSize);
//_payload.setValid(payloadSize);
//_payload.setOffset(0);
// ok now lets go back and deal with the options
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
setOptionalDelay((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
}
if (isFlagSet(FLAG_FROM_INCLUDED)) {
ByteArrayInputStream bais = new ByteArrayInputStream(buffer, cur, length - cur);
try {
Destination optionFrom = Destination.create(bais);
cur += optionFrom.size();
_optionFrom = optionFrom;
} catch (IOException ioe) {
throw new IllegalArgumentException("Bad from field", ioe);
} catch (DataFormatException dfe) {
throw new IllegalArgumentException("Bad from field", dfe);
}
}
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
setOptionalMaxSize((int)DataHelper.fromLong(buffer, cur, 2));
cur += 2;
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
Signature optionSignature;
Destination from = getOptionalFrom();
if (from != null) {
optionSignature = new Signature(from.getSigningPublicKey().getType());
} else {
// super cheat for now, look for correct type,
// assume no more options. If we add to the options
// we will have to ask the manager.
// We will get this wrong for Ed25519, same length as P256...
// See verifySignature() below where we will recast the signature to
// the correct type if necessary
int siglen = payloadBegin - cur;
SigType type = null;
for (SigType t : SigType.values()) {
if (t.getSigLen() == siglen) {
type = t;
break;
}
}
if (type == null) {
if (siglen < Signature.SIGNATURE_BYTES)
throw new IllegalArgumentException("unknown sig type len=" + siglen);
// Hope it's the default type with some unknown options following;
// if not the sig will fail later
type = SigType.DSA_SHA1;
siglen = Signature.SIGNATURE_BYTES;
}
optionSignature = new Signature(type);
}
byte buf[] = new byte[optionSignature.length()];
System.arraycopy(buffer, cur, buf, 0, buf.length);
optionSignature.setData(buf);
setOptionalSignature(optionSignature);
cur += buf.length;
}
}
/**
* Determine whether the signature on the data is valid.
*
* @param ctx Application context
* @param from the Destination the data came from
* @param buffer data to validate with signature
* @return true if the signature exists and validates against the data,
* false otherwise.
*/
public boolean verifySignature(I2PAppContext ctx, Destination from, byte buffer[]) {
if (!isFlagSet(FLAG_SIGNATURE_INCLUDED)) return false;
if (_optionSignature == null) return false;
// prevent receiveNewSyn() ... !active ... sendReset() ... verifySignature ... NPE
if (from == null) return false;
int size = writtenSize();
if (buffer == null)
buffer = new byte[size];
SigningPublicKey spk = from.getSigningPublicKey();
SigType type = spk.getType();
if (type == null) {
Log l = ctx.logManager().getLog(Packet.class);
if (l.shouldLog(Log.WARN))
l.warn("Unknown sig type in " + from + " cannot verify " + toString());
return false;
}
int written = writePacket(buffer, 0, type.getSigLen());
if (written != size) {
ctx.logManager().getLog(Packet.class).error("Written " + written + " size " + size + " for " + toString(), new Exception("moo"));
return false;
}
// Fixup of signature if we guessed wrong on the type in readPacket(), which could happen
// on a close or reset packet where we have a signature without a FROM
if (type != _optionSignature.getType() &&
type.getSigLen() == _optionSignature.length())
_optionSignature = new Signature(type, _optionSignature.getData());
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, spk);
if (!ok) {
Log l = ctx.logManager().getLog(Packet.class);
if (l.shouldLog(Log.WARN))
l.warn("Signature failed on " + toString(), new Exception("moo"));
//if (false) {
// l.error(Base64.encode(buffer, 0, size));
// l.error("Signature: " + Base64.encode(_optionSignature.getData()));
//}
}
return ok;
}
@Override
public String toString() {
StringBuilder str = formatAsString();
return str.toString();
}
protected StringBuilder formatAsString() {
StringBuilder buf = new StringBuilder(64);
buf.append(toId(_sendStreamId));
buf.append('/');
buf.append(toId(_receiveStreamId)).append(':');
if (_sequenceNum != 0 || isFlagSet(FLAG_SYNCHRONIZE))
buf.append(" #").append(_sequenceNum);
// else an ack-only packet
//if (_sequenceNum < 10)
// buf.append(" \t"); // so the tab lines up right
//else
// buf.append('\t');
toFlagString(buf);
if ( (_payload != null) && (_payload.getValid() > 0) )
buf.append(" data: ").append(_payload.getValid());
return buf;
}
static final String toId(long id) {
return Base64.encode(DataHelper.toLong(4, id)).replace("==", "");
}
private final void toFlagString(StringBuilder buf) {
if (isFlagSet(FLAG_NO_ACK))
buf.append(" NO_ACK");
else
buf.append(" ACK ").append(getAckThrough());
if (_nacks != null) {
buf.append(" NACK");
for (int i = 0; i < _nacks.length; i++) {
buf.append(' ').append(_nacks[i]);
}
}
if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE");
if (isFlagSet(FLAG_DELAY_REQUESTED)) buf.append(" DELAY ").append(_optionDelay);
if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO");
if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM ").append(_optionFrom.size());
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS ").append(_optionMaxSize);
if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE");
if (isFlagSet(FLAG_RESET)) buf.append(" RESET");
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (_optionSignature != null)
buf.append(" SIG ").append(_optionSignature.length());
else
buf.append(" (to be signed)");
}
if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ");
if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN");
}
/** Generate a pcap/tcpdump-compatible format,
* so we can use standard debugging tools.
*/
public void logTCPDump(Connection con) {
try {
I2PSocketManagerFull.pcapWriter.write(this, con);
} catch (IOException ioe) {
}
}
}