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