package com.limegroup.gnutella.routing;
import java.io.IOException;
import java.io.OutputStream;
import com.limegroup.gnutella.Assert;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.statistics.DroppedSentMessageStatHandler;
import com.limegroup.gnutella.statistics.SentMessageStatHandler;
/**
* The PATCH route table update message. This class is as simple as possible.
* For example, the getData() method returns the raw bytes of the message,
* requiring the caller to call the getEntryBits() method to calculate the i'th
* patch value. (Note that this is trivial if getEntryBits() returns 8.) This
* is by intention, as patches are normally split into several
* PatchTableMessages.
*/
public class PatchTableMessage extends RouteTableMessage {
/** For sequenceNumber and size, we really do need values of 0-255.
* Java bytes are signed, of course, so we store shorts internally
* and convert to bytes when writing. */
private short sequenceNumber;
private short sequenceSize;
private byte compressor;
private byte entryBits;
//TODO: I think storing payload here would be more efficient
private byte[] data;
public static final byte COMPRESSOR_NONE=0x0;
public static final byte COMPRESSOR_DEFLATE=0x1;
/////////////////////////////// Encoding //////////////////////////////
/**
* Creates a new PATCH variant from scratch, with TTL 1. The patch data is
* copied from dataSrc[datSrcStart...dataSrcStop-1], inclusive.
*
* @requires sequenceNumber and sequenceSize can fit in one unsigned byte,
* sequenceNumber and sequenceSize >= 1,
* sequenceNumber<=sequenceSize
* compressor one of COMPRESSOR_NONE or COMPRESSOR_DEFLATE
* entryBits less than 1
* dataSrcStart>dataSrcStop
* dataSrcStart or dataSrcStop not valid indices fof dataSrc
* @see RouteTableMessage
*/
public PatchTableMessage(short sequenceNumber,
short sequenceSize,
byte compressor,
byte entryBits,
byte[] dataSrc,
int dataSrcStart,
int dataSrcStop) {
//Payload length INCLUDES variant
super((byte)1,
5+(dataSrcStop-dataSrcStart),
RouteTableMessage.PATCH_VARIANT);
this.sequenceNumber=sequenceNumber;
this.sequenceSize=sequenceSize;
this.compressor=compressor;
this.entryBits=entryBits;
//Copy dataSrc[dataSrcStart...dataSrcStop-1] to data
data=new byte[dataSrcStop-dataSrcStart]; //TODO3: avoid
System.arraycopy(dataSrc, dataSrcStart, data, 0, data.length);
}
protected void writePayloadData(OutputStream out) throws IOException {
//Does NOT include variant
byte[] buf=new byte[4+data.length];
buf[0]=(byte)sequenceNumber;
buf[1]=(byte)sequenceSize;
buf[2]=(byte)compressor;
buf[3]=(byte)entryBits;
System.arraycopy(data, 0, buf, 4, data.length); //TODO3: avoid
out.write(buf);
SentMessageStatHandler.TCP_PATCH_ROUTE_TABLE_MESSAGES.addMessage(this);
}
/////////////////////////////// Decoding ///////////////////////////////
/**
* Creates a new PATCH variant with data read from the network.
* The first byte is guaranteed to be PATCH_VARIANT.
*
* @exception BadPacketException the remaining values in payload are not
* well-formed, e.g., because it's the wrong length, the sequence size
* is less than the sequence number, etc.
*/
protected PatchTableMessage(byte[] guid,
byte ttl,
byte hops,
byte[] payload) throws BadPacketException {
super(guid, ttl, hops, payload.length,
RouteTableMessage.PATCH_VARIANT);
//TODO: maybe we shouldn't enforce this
//if (payload.length<5)
// throw new BadPacketException("Extra arguments in reset message.");
Assert.that(payload[0]==PATCH_VARIANT);
this.sequenceNumber=(short)ByteOrder.ubyte2int(payload[1]);
this.sequenceSize=(short)ByteOrder.ubyte2int(payload[2]);
if (sequenceNumber<1 || sequenceSize<1 || sequenceNumber>sequenceSize)
throw new BadPacketException(
"Bad sequence/size: "+sequenceNumber+"/"+sequenceSize);
this.compressor=payload[3];
if (! (compressor==COMPRESSOR_NONE || compressor==COMPRESSOR_DEFLATE))
throw new BadPacketException("Bad compressor: "+compressor);
this.entryBits=payload[4];
if (entryBits<0)
throw new BadPacketException("Negative entryBits: "+entryBits);
this.data=new byte[payload.length-5];
System.arraycopy(payload, 5, data, 0, data.length); //TODO3: avoid
}
/////////////////////////////// Accessors ///////////////////////////////
public short getSequenceNumber() {
return sequenceNumber;
}
public short getSequenceSize() {
return sequenceSize;
}
public byte getCompressor() {
return compressor;
}
public byte getEntryBits() {
return entryBits;
}
public byte[] getData() {
return data;
}
// inherit doc comment
public void recordDrop() {
DroppedSentMessageStatHandler.TCP_PATCH_ROUTE_TABLE_MESSAGES.addMessage(this);
}
public String toString() {
StringBuffer buf=new StringBuffer();
buf.append("{PATCH, Sequence: "+getSequenceNumber()+"/"+getSequenceSize()
+", Bits: "+entryBits+", Compr: "+getCompressor()+", [");
// for (int i=0; i<data.length; i++) {
// if (data[i]!=0)
// buf.append(i+"/"+data[i]+", ");
// }
buf.append("<"+data.length+" bytes>");
buf.append("]");
return buf.toString();
}
}