[gui-dev] Re: A better QueryRouteTable
"Philippe Verdy" <[email protected]> Thu, 25 Nov 2004 18:00:46 +0100
| Newsgroups | gmane.network.gnutella.limewire.gui.devel |
|---|---|
| Message-ID | <[email protected]> |
The previous message contains the complete version of this file. This is a CVS patch version (roughly the same size)... A few things I'd like to see is to allow this class to reuse its working byte[] data buffer, instead of allocating a new one each time patches are updated (notably for the generation of UP-to-UP patches). Note that another optimization it performs also include a faster check of bad patch sizes (it is checked early only once instead of for each patch value within a patch fragment). This class is still the one that uses the most the CPU when running as an UltraPeer in the background. I wonder if using a BitSet is a good idea (we could use the working byte[] buffer directly, if we decide later to activate the generation of 1-bit patches; however we'll need to support for some time the generation of 4-bit patches with all legacy limewires and other servents.) For a next I wonder which solution will be good: activating the 2-bit format, or directly the 1-bit format? (This code supports both on reception). _______________________________________________ gui-dev mailing list [email protected] http://www.limewire.org/mailman/listinfo/gui-dev
QueryRouteTable.java.patch
(application/octet-stream, 32.2 KB)
Index: com/limegroup/gnutella/routing/QueryRouteTable.java
===================================================================
RCS file: /cvs/core/com/limegroup/gnutella/routing/QueryRouteTable.java,v
retrieving revision 1.47
diff -u -r1.47 QueryRouteTable.java
--- com/limegroup/gnutella/routing/QueryRouteTable.java 4 Nov 2004 19:14:58 -0000 1.47
+++ com/limegroup/gnutella/routing/QueryRouteTable.java 25 Nov 2004 16:10:11 -0000
@@ -160,8 +160,29 @@
this.sequenceNumber = -1;
this.sequenceSize = -1;
this.nextPatch = 0;
- this.keywordPresent = (byte)(1 - infinity);
- this.keywordAbsent = (byte)(infinity - 1);
+ if (infinity < 2) {
+ // infinity=1 generates a 1-bit format:
+ this.keywordPresent = (byte)-1;
+ this.keywordAbsent = (byte)0;
+ // However patches are XOR-encoded rather than additive, so that
+ // all changes to present or to absent use the same patch value.
+ } else {
+ // infinity=2 generates a 2-bit format: patch present=-1, absent=1
+ //
+ // infinity=3 generates a 4-bit format: patch present=-2, absent=2
+ // ...
+ // infinity=7 generates a 4-bit format: patch present=-6, absent=6
+ // infinity=8 generates a 4-bit format: patch present=-7, absent=7
+ //
+ // infinity=9 generates a 8-bit format: patch present=-8, absent=8
+ // ...
+ // infinity=15 generates a 8-bit format:patch present=-14,absent=14
+ // ...
+ this.keywordPresent = (byte)(1 - infinity);
+ this.keywordAbsent = (byte)(infinity - 1);
+ // Sent patches are additive, but we only consider their sign
+ // when updating bitTable.
+ }
this.infinity = infinity;
}
@@ -441,73 +462,63 @@
//RESET, ensure that m's sequence size matches last message
if (sequenceSize!=-1 && sequenceSize!=m.getSequenceSize())
throw new BadPacketException("Inconsistent seq size: "
- +m.getSequenceSize()
- +" vs. "+sequenceSize);
+ + m.getSequenceSize()
+ + " vs. "+sequenceSize);
//If we were just reset, ensure that m's sequence number is one.
//Otherwise it should be one greater than the last message received.
if (sequenceNumber==-1 ? m.getSequenceNumber()!=1 //reset
: sequenceNumber+1!=m.getSequenceNumber())
throw new BadPacketException("Inconsistent seq number: "
- +m.getSequenceNumber()
- +" vs. "+sequenceNumber);
+ + m.getSequenceNumber()
+ + " vs. "+sequenceNumber);
- byte[] data=m.getData();
+ byte[] data = m.getData();
//1. Start pipelined uncompression.
//TODO: check that compression is same as last message.
- if (m.getCompressor()==PatchTableMessage.COMPRESSOR_DEFLATE) {
+ if (m.getCompressor() == PatchTableMessage.COMPRESSOR_DEFLATE) {
try {
//a) If first message, create uncompressor (if needed).
- if (m.getSequenceNumber()==1) {
+ if (m.getSequenceNumber() == 1) {
uncompressor = new Inflater();
}
Assert.that(uncompressor!=null,
- "Null uncompressor. Sequence: "+m.getSequenceNumber());
- data=uncompress(data);
+ "Null uncompressor. Sequence: " + m.getSequenceNumber());
+ data = uncompress(data);
} catch (IOException e) {
- throw new BadPacketException("Couldn't uncompress data: "+e);
+ throw new BadPacketException("Couldn't uncompress data: " + e);
}
} else if (m.getCompressor()!=PatchTableMessage.COMPRESSOR_NONE) {
throw new BadPacketException("Unknown compressor");
}
- //2. Expand nibbles if necessary.
- if (m.getEntryBits()==4)
- data=unhalve(data);
- else if (m.getEntryBits()!=8)
- throw new BadPacketException("Unknown value for entry bits");
-
- //3. Add data[0...] to table[nextPatch...]
- for (int i=0; i<data.length; i++) {
- if(nextPatch >= bitTableLength)
- throw new BadPacketException("Tried to patch "+nextPatch
- +" on a bitTable of size "
- + bitTableLength);
- // All negative values indicate presence
- if (data[i] < 0) {
- bitTable.set(nextPatch);
- resizedQRT = null;
- }
- // All positive values indicate absence
- else if (data[i] > 0) {
- bitTable.clear(nextPatch);
- resizedQRT = null;
- }
- nextPatch++;
- }
- bitTable.compact();
-
- //4. Update sequence numbers.
- this.sequenceSize=m.getSequenceSize();
- if (m.getSequenceNumber()!=m.getSequenceSize()) {
- this.sequenceNumber=m.getSequenceNumber();
+ //2. Expand nibbles if necessary and apply data[0...] patches
+ // to table[nextPatch...]
+ switch (m.getEntryBits()) {
+ case 8: decode8bits(data); break;
+ case 4: decode4bits(data); break;
+ // /* 2-BIT FORMAT PARSING
+ case 2: decode2bits(data); break;
+ // */
+ // /* 1-BIT FORMAT PARSING
+ case 1: decode1bit(data); break;
+ // */
+ default:
+ throw new BadPacketException("Unknown value " + m.getEntryBits() +
+ " for entry bits");
+ }
+ this.bitTable.compact();
+ //3. Update sequence numbers.
+ this.sequenceSize = m.getSequenceSize();
+ if (m.getSequenceNumber() != m.getSequenceSize()) {
+ this.sequenceNumber = m.getSequenceNumber();
} else {
//Sequence complete.
- this.sequenceNumber=-1;
- this.sequenceSize=-1;
- this.nextPatch=0; //TODO: is this right?
+ this.sequenceNumber = -1;
+ this.sequenceSize = -1;
+ this.nextPatch = 0; //TODO: is this right?
// if this last message was compressed, release the uncompressor.
- if( this.uncompressor != null ) {
+ if (this.uncompressor != null) {
this.uncompressor.end();
this.uncompressor = null;
}
@@ -536,125 +547,406 @@
*/
public List /* of RouteTableMessage */ encode(
QueryRouteTable prev, boolean allowCompression) {
- List /* of RouteTableMessage */ buf=new LinkedList();
- if (prev==null)
- buf.add(new ResetTableMessage(bitTableLength, infinity));
- else
- Assert.that(prev.bitTableLength==this.bitTableLength,
- "TODO: can't deal with tables of different lengths");
-
- //1. Calculate patch array
- byte[] data=new byte[bitTableLength];
- // Fill up data with KEYWORD_NO_CHANGE, since the majority
- // of elements will be that.
- // Because it is already filled, we do not need to iterate and
- // set it anywhere.
- Utilities.fill(data, 0, bitTableLength, KEYWORD_NO_CHANGE);
- boolean needsPatch=false;
+ List /* of RouteTableMessage */ buf = new LinkedList();
+ Assert.that(keywordPresent < 0);
+ Assert.that(keywordAbsent > 0 || keywordAbsent == 0 && keywordPresent == -1);
- //1a. If there was a previous table, determine if it was the same one.
- // If so, we can prevent BitTableLength calls to BitSet.get(int).
- if( prev != null ) {
- //1a-I. If they are not equal, xOr the tables and loop
- // through the different bits. This avoids
- // bitTableLength*2 calls to BitSet.get
- // at the cost of the xOr'd table's cardinality
- // calls to both BitSet.nextSetBit and BitSet.get.
- // Generally it is worth it, as our BitTables don't
- // change very rapidly.
- // With the xOr'd table, we know that all 'clear'
- // values have not changed. Thus, we can use
- // nextSetBit on the xOr'd table & this.bitTable.get
- // to determine whether or not we should set
- // data[x] to keywordPresent or keywordAbsent.
- // Because this is an xOr, we know that if
- // this.bitTable.get is true, prev.bitTable.get
- // is false, and vice versa.
- if(!this.bitTable.equals(prev.bitTable) ) {
- BitSet xOr = (BitSet)this.bitTable.clone();
- xOr.xor(prev.bitTable);
- for (int i=xOr.nextSetBit(0); i >= 0; i=xOr.nextSetBit(i+1)) {
- data[i] = this.bitTable.get(i) ?
- keywordPresent : keywordAbsent;
- needsPatch = true;
- }
+ //1. Calculate patch array
+ byte[] data;
+ byte bits;
+ if (prev == null) { // No previous table, needs at least a RESET.
+ buf.add(new ResetTableMessage(bitTableLength, infinity));
+ // Check when the current one has no bit set.
+ // If so, just return a single RESET, without any PATCH.
+ if (this.bitTable.nextSetBit(0) < 0)
+ return buf;
+ // /* 1-BIT FORMAT GENERATION
+ if (keywordPresent == -1 && keywordAbsent == 0) {
+ data = encode1bit();
+ bits = 1;
+ } else
+ // */
+ // /* 2-BIT FORMAT GENERATION
+ if (keywordPresent >= -2 && keywordAbsent == 1) {
+ data = encode2bits();
+ bits = 2;
+ } else
+ // */
+ if (keywordPresent >= -8 && keywordAbsent <= 7) {
+ data = encode4bits();
+ bits = 4;
+ } else {
+ data = encode8bits();
+ bits = 8;
}
- // Else the two tables are equal, and we don't need to do anything
- // because all elements already contain KEYWORD_NO_CHANGE.
- }
- //1b. If there was no previous table, scan through the table using
- // nextSetBit, avoiding bitTableLength calls to BitSet.get(int).
- else {
- for (int i=bitTable.nextSetBit(0);i>=0;i=bitTable.nextSetBit(i+1)){
- data[i] = keywordPresent;
- needsPatch = true;
+ } else {
+ // Check when the current table equals the previous one.
+ // If so, there's no PATCH to send.
+ Assert.that(prev.bitTableLength == this.bitTableLength,
+ "TODO: can't deal with tables of different lengths");
+ if (this.bitTable.equals(prev.bitTable))
+ return buf;
+ // /* 1-BIT FORMAT GENERATION
+ if (keywordPresent >= -1 && keywordAbsent <= 0) {
+ data = encode1bit(prev);
+ bits = 1;
+ } else
+ // */
+ // /* 2-BIT FORMAT GENERATION
+ if (keywordPresent >= -2 && keywordAbsent <= 1) {
+ data = encode2bits(prev);
+ bits = 2;
+ } else
+ // */
+ if (keywordPresent >= -8 && keywordAbsent <= 7) {
+ data = encode4bits(prev);
+ bits = 4;
+ } else {
+ data = encode8bits(prev);
+ bits = 8;
}
}
- //Optimization: there's nothing to report. If prev=null, send a single
- //RESET. Otherwise send nothing.
- if (!needsPatch) {
- return buf;
- }
-
-
+
//2. Try compression.
- //TODO: Should this not be done if compression isn't allowed?
- byte bits=8;
- // Only halve if our values require 4 signed bytes at most.
- // keywordPresent will always be negative and
- // keywordAbsent will always be positive.
- if( keywordPresent >= -8 && keywordAbsent <= 7 ) {
- bits = 4;
- data = halve(data);
- }
-
- byte compression=PatchTableMessage.COMPRESSOR_NONE;
- //Optimization: If we are told it is safe to compress the message,
- //then attempt to compress it. Reasons it is not safe include
- //the outgoing stream already being compressed.
- if( allowCompression ) {
+ byte compression = PatchTableMessage.COMPRESSOR_NONE;
+ // Optimization: If we are told it is safe to compress the message,
+ // then attempt to compress it. Reasons it is not safe include the
+ // outgoing stream already being compressed.
+ if (allowCompression) {
byte[] patchCompressed = IOUtils.deflate(data);
- if (patchCompressed.length<data.length) {
- //...Hooray! Compression was efficient.
- data=patchCompressed;
- compression=PatchTableMessage.COMPRESSOR_DEFLATE;
+ if (patchCompressed.length < data.length) {
+ data = patchCompressed; // Compression was efficient!
+ compression = PatchTableMessage.COMPRESSOR_DEFLATE;
}
}
-
-
- //3. Break into 1KB chunks and send. TODO: break size limits if needed.
- final int chunks=(int)Math.ceil((float)data.length/(float)MAX_PATCH_SIZE);
- int chunk=1;
- for (int i=0; i<data.length; i+=MAX_PATCH_SIZE) {
- //Just past the last position of data to copy.
- //Note special case for last chunk.
- int stop=Math.min(i+MAX_PATCH_SIZE, data.length);
+
+ //3. Break into 1KB chunks and send. TODO: break size limits if needed.
+ //final int chunks = (int)Math.ceil((float)data.length/(float)MAX_PATCH_SIZE);
+ final int chunks = (data.length - 1) / MAX_PATCH_SIZE + 1;
+ int chunk = 1, start = 0, stop = MAX_PATCH_SIZE;
+ while (stop <= data.length) {
+ buf.add(new PatchTableMessage((short)chunk++, (short)chunks,
+ compression, bits,
+ data, start, stop));
+ start = stop;
+ stop += MAX_PATCH_SIZE;
+ }
+ if (start < data.length)
buf.add(new PatchTableMessage((short)chunk, (short)chunks,
compression, bits,
- data, i, stop));
- chunk++;
- }
+ data, start, data.length));
return buf;
}
-
///////////////// Helper Functions for Codec ////////////////////////
- /** Returns the uncompressed version of the given defalted bytes, using
+ private byte[] encode8bits() {
+ final BitSet bs = this.bitTable;
+ final byte[] data = new byte[this.bitTableLength];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ //TODO: do we still need parametered infinity?
+ // shouldn't we fix to present=-7, absent=7?
+ final byte present = this.keywordPresent;
+ for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
+ data[i] = present;
+ return data;
+ }
+
+ private byte[] encode8bits(final QueryRouteTable prev) {
+ final BitSet bs = this.bitTable;
+ final byte[] data = new byte[this.bitTableLength];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ final BitSet xOr = (BitSet)bs.clone();
+ xOr.xor(prev.bitTable);
+ //TODO: do we still need parametered infinity?
+ // shouldn't we fix to present=-7, absent=7?
+ final byte present = this.keywordPresent;
+ final byte absent = this.keywordAbsent;
+ for (int i = xOr.nextSetBit(0); i >= 0; i = xOr.nextSetBit(i + 1))
+ data[i] = bs.get(i) ? present : absent;
+ return data;
+ }
+
+ private byte[] encode4bits() {
+ final BitSet bs = this.bitTable;
+ final byte[] data = new byte[(this.bitTableLength + 1) >> 1];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ //TODO: do we still need parametered infinity?
+ // shouldn't we fix to present=-7, absent=7?
+ final byte present1 = (byte)(this.keywordPresent & 0x0F);
+ final byte present0 = (byte)(present1 << 4);
+ for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
+ if ((i & 1) == 0) // even position, set first high nibble
+ data[i>>1] = present0;
+ else // odd position, set low nibble
+ data[i>>1] |= present1;
+ }
+ return data;
+ }
+
+ private byte[] encode4bits(final QueryRouteTable prev) {
+ final BitSet bs = this.bitTable;
+ final BitSet xOr = (BitSet)bs.clone();
+ xOr.xor(prev.bitTable);
+ final byte[] data = new byte[(this.bitTableLength + 1) >> 1];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ //TODO: do we still need parametered infinity?
+ // shouldn't we fix to present=-7, absent=7?
+ final byte present1 = (byte)(this.keywordPresent & 0x0F);
+ final byte present0 = (byte)(present1 << 4);
+ final byte absent1 = (byte)(this.keywordAbsent & 0x0F);
+ final byte absent0 = (byte)(absent1 << 4);
+ for (int i = xOr.nextSetBit(0); i >= 0; i = xOr.nextSetBit(i + 1)) {
+ if ((i & 1) == 0) // even position, set first high nibble
+ data[i>>1] = bs.get(i) ? present0 : absent0;
+ else // odd position, set low nibble
+ data[i>>1] |= bs.get(i) ? present1 : absent1;
+ }
+ return data;
+ }
+
+ private byte[] encode2bits() {
+ BitSet bs = this.bitTable;
+ byte[] data = new byte[(this.bitTableLength + 3) >> 2];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ // Note: on 2 bits, keywordPresent could only be -2 or -1, and
+ // keywordAbsent = 1. We can only use infinite=1, so let's use:
+ // keywordPresent = -1(mod 3) = 3, keywordAbsent = 1(mod 3) = 1
+ //byte present3 = this.keywordPresent & 0x03; //-1(mod3)=0x03
+ //byte present2 = (byte)(present3 << 2);//0x0c
+ //byte present1 = (byte)(present3 << 4);//0x30
+ //byte present0 = (byte)(present3 << 6);//0xc0
+ for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
+ switch (i & 3) {
+ case 0: data[i>>2] = (byte)0xc0; break;//present0
+ case 1: data[i>>2] |= (byte)0x30; break;//present1
+ case 2: data[i>>2] |= (byte)0x0c; break;//present2
+ case 3: data[i>>2] |= (byte)0x03; break;//present3
+ }
+ }
+ return data;
+ }
+
+ private byte[] encode2bits(final QueryRouteTable prev) {
+ final BitSet bs = this.bitTable;
+ final BitSet xOr = (BitSet)bs.clone();
+ xOr.xor(prev.bitTable);
+ final byte[] data = new byte[(this.bitTableLength + 3) >> 2];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ // Note: on 2 bits, keywordPresent could only be -2 or -1, and
+ // keywordAbsent = 1. We can only use infinite = 1, so let's use:
+ // keywordPresent = -1(mod 3), keywordAbsent = 1(mod 3)
+ //byte present3 = this.keywordPresent & 0x03; //-1(mod3)=0x03
+ //byte present2 = (byte)(present3 << 2);//0x0c
+ //byte present1 = (byte)(present3 << 4);//0x30
+ //byte present0 = (byte)(present3 << 6);//0xc0
+ //byte absent3 = this.keywordAbsent & 0x03; // 0x01
+ //byte absent2 = (byte)(absent3 << 2);// 0x04
+ //byte absent1 = (byte)(absent3 << 4);// 0x10
+ //byte absent0 = (byte)(absent3 << 6);// 0x40
+ for (int i = xOr.nextSetBit(0); i >= 0; i = xOr.nextSetBit(i + 1)) {
+ switch (i & 3) {
+ case 0: data[i>>2] = bs.get(i) ? (byte)0xc0 : (byte)0x40; break;//?present0:absent0
+ case 1: data[i>>2] |= bs.get(i) ? (byte)0x30 : (byte)0x10; break;//?present1:absent1
+ case 2: data[i>>2] |= bs.get(i) ? (byte)0x0c : (byte)0x04; break;//?present2:absent2
+ case 3: data[i>>2] |= bs.get(i) ? (byte)0x03 : (byte)0x01; break;//?present3:absent3
+ }
+ }
+ return data;
+ }
+
+ private byte[] encode1bit() {
+ final BitSet bs = this.bitTable;
+ final byte[] data = new byte[(this.bitTableLength + 7) >> 3];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ // We don't encode present or absent but only which bits changed.
+ // infinity can only be 1, present=-1, absent=0
+ for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
+ data[i>>3] |= (byte)(0x80 >> (i & 7));
+ return data;
+ }
+
+ private byte[] encode1bit(final QueryRouteTable prev) {
+ final BitSet bs = this.bitTable;
+ final BitSet xOr = (BitSet)bs.clone();
+ xOr.xor(prev.bitTable);
+ final byte[] data = new byte[(this.bitTableLength + 7) >> 3];
+ Utilities.fill(data, 0, data.length, KEYWORD_NO_CHANGE);
+ // We don't encode present or absent but only which bits changed.
+ // infinity can only be 1, change(present/absent)=-1, unchanged=0
+ for (int i = xOr.nextSetBit(0); i >= 0; i = xOr.nextSetBit(i + 1))
+ data[i>>3] |= (byte)(0x80 >> (i & 7));
+ return data;
+ }
+
+ private void decode8bits(byte[] data) throws BadPacketException {
+ final BitSet bs = this.bitTable;
+ final int bsLen = this.bitTableLength;
+ int j = this.nextPatch;
+ if (j + data.length > bsLen)
+ throw new BadPacketException("Tried to patch "
+ + (j + data.length - 1)
+ + " on a bitTable of size "
+ + bsLen);
+ for (int i = 0; i < data.length; i++) {
+ // all zeroes indicate no change
+ final byte b;
+ if ((b = data[i]) != 0) {
+ // the patch is non-zero (changed)
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if (b < 0)
+ bs.set(j);
+ else
+ bs.clear(j);
+ resizedQRT = null;
+ }
+ j++;
+ }
+ this.nextPatch = j;
+ }
+
+ private void decode4bits(byte[] data) throws BadPacketException {
+ final BitSet bs = this.bitTable;
+ final int bsLen = this.bitTableLength;
+ int j = this.nextPatch;
+ if (j + data.length * 2 > bsLen)
+ throw new BadPacketException("Tried to patch "
+ + (j + data.length * 2 - 1)
+ + " on a bitTable of size "
+ + bsLen);
+ for (int i = 0; i < data.length; i++) {
+ // all zeroes indicate no change
+ final byte b;
+ if ((b = data[i]) != 0) {
+ // at least one of the two patches is non-zero (changed)
+ // test if first (high) patch is non-zero (changed)
+ if ((b & 0xf0) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if (b < 0)
+ bs.set(j);
+ else
+ bs.clear(j);
+ }
+ // test if second (low) patch is non-zero (changed)
+ if ((b & 0x0f) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if ((b & 0x08) != 0)//just check the nibble sign bit
+ bs.set(j + 1);
+ else
+ bs.clear(j + 1);
+ }
+ resizedQRT = null;
+ }
+ j += 2;
+ }
+ this.nextPatch = j;
+ }
+
+ private void decode2bits(byte[] data) throws BadPacketException {
+ final BitSet bs = this.bitTable;
+ final int bsLen = this.bitTableLength;
+ int j = this.nextPatch;
+ if (j + data.length * 4 > bsLen)
+ throw new BadPacketException("Tried to patch "
+ + (j + data.length * 4 - 1)
+ + " on a bitTable of size "
+ + bsLen);
+ for (int i = 0; i < data.length; i++) {
+ // all zeroes indicate no change
+ final byte b;
+ if ((b = data[i]) != 0) {
+ // at least one of the four patches is non-zero (changed)
+ // test if first (high) patch is non-zero (changed)
+ if ((b & 0xc0) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if (b < 0)
+ bs.set(j);
+ else
+ bs.clear(j);
+ }
+ // test if second patch is non-zero (changed)
+ if ((b & 0x30) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if ((b & 0x20) != 0)//just check the high sign bit
+ bs.set(j + 1);
+ else
+ bs.clear(j + 1);
+ }
+ // test if third patch is non-zero (changed)
+ if ((b & 0x0c) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if ((b & 0x08) != 0)//just check the high sign bit
+ bs.set(j + 2);
+ else
+ bs.clear(j + 2);
+ }
+ // test if fourth (low) patch is non-zero (changed)
+ if ((b & 0x03) != 0) {
+ // All negative values indicate change to presence
+ // All positive values indicate change to absence
+ if ((b & 0x02) != 0)//just check the high sign bit
+ bs.set(j + 3);
+ else
+ bs.clear(j + 3);
+ }
+ resizedQRT = null;
+ }
+ j += 4;
+ }
+ this.nextPatch = j;
+ }
+
+ private void decode1bit(byte[] data) throws BadPacketException {
+ final BitSet bs = this.bitTable;
+ final int bsLen = this.bitTableLength;
+ int j = this.nextPatch;
+ if (j + data.length * 8 > bsLen)
+ throw new BadPacketException("Tried to patch "
+ + (j + data.length * 8 - 1)
+ + " on a bitTable of size "
+ + bsLen);
+ for (int i = 0; i < data.length; i++) {
+ final byte b;
+ // all zeroes indicate no change
+ if ((b = data[i]) != 0) {
+ // at least one of the eight patches is non-zero (changed)
+ // test each non-zero bit to flip corresponding bits in bs.
+ if ((b & 0x80) != 0) bs.flip(j);
+ if ((b & 0x40) != 0) bs.flip(j + 1);
+ if ((b & 0x20) != 0) bs.flip(j + 2);
+ if ((b & 0x10) != 0) bs.flip(j + 3);
+ if ((b & 0x08) != 0) bs.flip(j + 4);
+ if ((b & 0x04) != 0) bs.flip(j + 5);
+ if ((b & 0x02) != 0) bs.flip(j + 6);
+ if ((b & 0x01) != 0) bs.flip(j + 7);
+ resizedQRT = null;
+ }
+ j += 8;
+ }
+ this.nextPatch = j;
+ }
+
+ /** Returns the uncompressed version of the given defaulted bytes, using
* any dictionaries in uncompressor. Throws IOException if the data is
* corrupt.
* @requires inflater initialized
* @modifies inflater */
private byte[] uncompress(byte[] data) throws IOException {
- ByteArrayOutputStream baos=new ByteArrayOutputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
uncompressor.setInput(data);
try {
- byte[] buf=new byte[1024];
+ byte[] buf = new byte[1024];
while (true) {
- int read=uncompressor.inflate(buf);
+ int read = uncompressor.inflate(buf);
//Needs input?
- if (read==0)
+ if (read == 0)
break;
baos.write(buf, 0, read);
}
@@ -664,38 +956,45 @@
throw new IOException("Bad deflate format");
}
}
-
- /** Returns an array R of length array.length/2, where R[i] consists of the
- * low nibble of array[2i] concatentated with the low nibble of array[2i+1].
- * Note that unhalve(halve(array))=array if all elements of array fit can
- * fit in four signed bytes.
- * @requires array.length is a multiple of two */
+ /** NO MORE NEEDED, EXCEPT FOR THE TESTS MODULE
+ * Returns an array R of length array.length/2, where R[i] consists of the
+ * low nibble of array[2i] concatentated with the low nibble of array[2i+1].
+ * Note that unhalve(halve(array))=array if all elements of array fit can
+ * fit in two signed bytes.
+ * @requires array.length is a multiple of two */
static byte[] halve(byte[] array) {
- byte[] ret=new byte[array.length/2];
- for (int i=0; i<ret.length; i++)
- ret[i]=(byte)((array[2*i]<<4) | (array[2*i+1]&0xF));
+ final byte[] ret = new byte[array.length >> 1];
+ for (int i = 0, j = 0; i < ret.length; ++i, j += 2)
+ ret[i] = (byte)((array[j] << 4) | (array[j + 1] & 15));
return ret;
}
- /** Returns an array of R of length array.length*2, where R[i] is the the
- * sign-extended high nibble of floor(i/2) if i even, or the sign-extended
- * low nibble of floor(i/2) if i odd. */
+ /** NO MORE NEEDED, EXCEPT FOR THE TESTS MODULE
+ * Returns an array of R of length array.length * 2, where R[i] is the
+ * sign-extended high nibble of floor(i/2) if i even, or the sign-extended
+ * low nibble of floor(i/2) if i odd.
+ * Note that only the sign or zeroness of unhalved elements is considered
+ * when decoding patches, so there's only 2 significant bits per element.
+ */
static byte[] unhalve(byte[] array) {
- byte[] ret=new byte[array.length*2];
- for (int i=0; i<array.length; i++) {
- ret[2*i]=(byte)(array[i]>>4); //sign extension
- ret[2*i+1]=extendNibble((byte)(array[i]&0xF));
+ final byte[] ret = new byte[array.length << 1];
+ for (int i = 0, j = 0; i < array.length; ++i, j += 2) {
+ byte b;
+ ret[j] = (byte)((b = array[i]) >> 4); //sign extension
+ ret[j + 1] = (byte)(((b & 8) == 0) ? (b & 7) : (b | -8));
}
return ret;
}
- /** Sign-extends the low nibble of b, i.e.,
- * returns (from MSB to LSB) b[3]b[3]b[3]b[3]b[3]b[2]b[1]b[0]. */
+ /** NO MORE NEEDED, EXCEPT FOR THE TESTS MODULE
+ * Sign-extends the low nibble of b, i.e.,
+ * returns (from MSB to LSB) b[3]b[3]b[3]b[3]b[3]b[2]b[1]b[0].
+ */
static byte extendNibble(byte b) {
- if ((b&0x8)!=0) //negative nibble; sign-extend.
- return (byte)(0xF0 | b);
+ if ((b & 8) == 0)
+ return (byte)(b & 7);
else
- return b;
+ return (byte)(b | -8);
}
}