Skip to content

Commit a2c1dc5

Browse files
committed
Fixes for message length encoding
1 parent bb6f91b commit a2c1dc5

File tree

10 files changed

+59
-48
lines changed

10 files changed

+59
-48
lines changed

convex-core/src/main/java/convex/core/data/ABlob.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ public final int encode(byte[] bs, int pos) {
295295
public int hashCode() {
296296
// note: We use a salted hash of the last bytes for blobs.
297297
// SECURITY: This is decent for small blobs, DoS risk for user generated large blobs. Be careful putting large keys in Java HashMaps.....
298+
// TODO: consider salted psuedorandom selection of bytes to include in hash?
298299
return Bits.hash32(longValue());
299300
}
300301

convex-core/src/main/java/convex/core/data/Format.java

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ public class Format {
103103
* @return Length of VLQ encoding in bytes
104104
*/
105105
public static int getVLQLongLength(long x) {
106-
if ((x < 64) && (x >= -64)) {
107-
return 1;
108-
}
106+
if ((x<64)&&(x>=-64)) return 1;
109107
int bitLength = Utils.bitLength(x);
110108
int blen = (bitLength + 6) / 7;
111109
return blen;
@@ -114,41 +112,39 @@ public static int getVLQLongLength(long x) {
114112
/**
115113
* Gets the length in bytes of VLQ count encoding for the given long value
116114
* @param x Long value to encode
117-
* @return Length of VLQ encoding
115+
* @return Length of VLQ encoding, or 0 if value is negative (overflow case)
118116
*/
119117
public static int getVLQCountLength(long x) {
120-
if (x<0) throw new IllegalArgumentException("Negative Count");
121-
if (x < 128) {
122-
return 1;
123-
}
118+
if (x<0) return 0;
119+
if (x<128) return 1;
124120
int bitLength = Utils.bitLength(x)-1; // high zero not required
125121
int blen = (bitLength + 6) / 7;
126122
return blen;
127123
}
128124

129125
/**
130-
* Puts a VLQ encoded long into the specified bytebuffer (with no tag)
126+
* Puts a VLQ encoded count into the specified bytebuffer (with no tag)
131127
*
132128
* Format:
133129
* <ul>
134130
* <li>MSB of each byte 0=last octet, 1=more octets</li>
135131
* <li>Following MSB, 7 bits of integer representation for each octet</li>
136-
* <li>Second highest bit of first byte is interpreted as the sign</li>
137132
* </ul>
138133
* @param bb ByteBuffer to write to
139134
* @param x Value to VLQ encode
140135
* @return Updated ByteBuffer
141136
*/
142-
public static ByteBuffer writeVLQLong(ByteBuffer bb, long x) {
143-
if ((x < 64) && (x >= -64)) {
144-
// single byte, cleared high bit
145-
byte single = (byte) (x & 0x7F);
137+
public static ByteBuffer writeVLQCount(ByteBuffer bb, long x) {
138+
if (x<128) {
139+
if (x<0) throw new IllegalArgumentException("Negative count!");
140+
// single byte
141+
byte single = (byte) (x);
146142
return bb.put(single);
147143
}
148-
int bitLength = Utils.bitLength(x);
149-
int blen = (bitLength + 6) / 7;
144+
int bitLength = 64-Bits.leadingZeros(x);
145+
int blen = (bitLength + 6) / 7; // 8 bits overflows to 2 bytes etc.
150146
for (int i = blen - 1; i >= 1; i--) {
151-
byte single = (byte) (0x80 | (x >> (7 * i))); // 7 bits with high bit set
147+
byte single = (byte) (0x80 | (x >>> (7 * i))); // 7 bits
152148
bb = bb.put(single);
153149
}
154150
byte end = (byte) (x & 0x7F); // last 7 bits of long, high bit zero
@@ -310,7 +306,7 @@ public static long readVLQCount(AArrayBlob blob, int pos) throws BadFormatExcept
310306

311307
/**
312308
* Peeks for a VLQ encoded message length at the start of a ByteBuffer, which
313-
* must contain at least 1 byte, maximum 2.
309+
* must contain at least 1 byte
314310
*
315311
* Does not move the buffer position.
316312
*
@@ -323,7 +319,7 @@ public static int peekMessageLength(ByteBuffer bb) throws BadFormatException {
323319
int remaining=bb.limit();
324320
if (remaining==0) return -1;
325321

326-
int len = bb.get(0);
322+
long len = bb.get(0);
327323

328324
// Quick check for 1 byte message length
329325
if ((len & 0x80) == 0) {
@@ -333,25 +329,27 @@ public static int peekMessageLength(ByteBuffer bb) throws BadFormatException {
333329
"Format.peekMessageLength: Zero message length:" + Utils.readBufferData(bb));
334330
}
335331

336-
// 1 byte header (without high bit set)
337-
return len & 0x3F;
332+
// 1 byte length (without high bit set)
333+
return (int)(len & 0x7F);
338334
}
339335

340-
// Clear high bit
336+
// Clear high bits
341337
len &=0x7f;
342338

343339
if (len==0) throw new BadFormatException("Format.peekMessageLength: Excess leading zeros");
344340

345341
for (int i=1; i<Format.MAX_VLQ_COUNT_LENGTH; i++) {
346342
if (i>=remaining) return -1; // we are expecting more bytes, but none available yet....
347343
int lsb = bb.get(i);
348-
len = (len << 7) + (lsb&0x7f);
344+
len = (len << 7) | (lsb&0x7f);
349345
if ((lsb & 0x80) == 0) {
350-
return len;
346+
break;
351347
}
352348
}
353349

354-
throw new BadFormatException("Format.peekMessageLength: Too many bytes in length encoding");
350+
int result=(int)len;
351+
if (result!=len) throw new BadFormatException("Format.peekMessageLength: Too many bytes in length encoding");
352+
return result;
355353
}
356354

357355
/**
@@ -362,7 +360,7 @@ public static int peekMessageLength(ByteBuffer bb) throws BadFormatException {
362360
* @return The ByteBuffer after writing the message length
363361
*/
364362
public static ByteBuffer writeMessageLength(ByteBuffer bb, int len) {
365-
return writeVLQLong(bb, len);
363+
return writeVLQCount(bb, len);
366364
}
367365

368366
/**
@@ -448,11 +446,6 @@ public static AString readUTF8String(Blob blob, int pos, int len) throws BadForm
448446
AString s = Strings.create(blob.slice(pos,pos+len));
449447
return s;
450448
}
451-
452-
public static ByteBuffer writeLength(ByteBuffer bb, int i) {
453-
bb = writeVLQLong(bb, i);
454-
return bb;
455-
}
456449

457450
/**
458451
* Reads a Ref or embedded Cell value from a Blob

convex-core/src/main/java/convex/core/data/MapLeaf.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public int estimatedEncodingSize() {
340340
* @throws BadFormatException If encoding is invalid
341341
*/
342342
public static <K extends ACell, V extends ACell> MapLeaf<K, V> read(Blob b, int pos, long count) throws BadFormatException {
343-
int epos=pos+2; // Note: Tag byte plus VLC Count length which is always 1
343+
int epos=pos+2; // Note: Tag byte plus VLQ Count length which is always 1
344344

345345
@SuppressWarnings("unchecked")
346346
MapEntry<K, V>[] items = (MapEntry<K, V>[]) new MapEntry[(int) count];

convex-core/src/main/java/convex/core/data/Maps.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ public static <K extends ACell, V extends ACell, R extends AMap<K, V>> R coerce(
139139
* @throws BadFormatException If encoding is invalid
140140
*/
141141
public static <K extends ACell, V extends ACell> AHashMap<K, V> read(Blob b, int pos) throws BadFormatException {
142+
// A hashmap always starts with a VLQ count after the tag
143+
// We use this to distinguish the type of Map cell
142144
long count = Format.readVLQCount(b,pos+1);
145+
143146
if (count==0) return empty();
144147
if (count <= MapLeaf.MAX_ENTRIES) {
145148
if (count < 0) throw new BadFormatException("Overflowed count of map elements!");

convex-core/src/main/java/convex/core/data/SetLeaf.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public int estimatedEncodingSize() {
220220
*/
221221

222222
public static <V extends ACell> SetLeaf<V> read(Blob b, int pos, long count) throws BadFormatException {
223-
int headerLen=1+1; // tag plus VLC Count length which is always 1
223+
int headerLen=1+1; // tag plus VLQ Count length which is always 1
224224

225225
int epos=pos+headerLen;
226226
if (count == 0) return Sets.empty();

convex-core/src/main/java/convex/core/util/Bits.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static int positionForDigit(int digit, short mask) {
4242
* @return Number of leading zeros (0-32)
4343
*/
4444
public static int leadingZeros(int x) {
45-
if (x == 0) return 32;
45+
if (x == 0) return 32; // fast path if all zeros
4646
int result = 0;
4747
if ((x & 0xFFFF0000) == 0) {
4848
result += 16;
@@ -66,9 +66,7 @@ public static int leadingZeros(int x) {
6666
}
6767
if ((x & 0x2) == 0) {
6868
result += 1;
69-
} else {
70-
x >>>= 1;
71-
}
69+
}
7270
return result;
7371
}
7472

@@ -78,10 +76,9 @@ public static int leadingZeros(int x) {
7876
* @return Number of leading zeros (0-64)
7977
*/
8078
public static int leadingZeros(long x) {
81-
int highWord = (int) (x >>> 32); // high 4 bytes, unsigned
82-
if (highWord != 0) return leadingZeros(highWord);
83-
int lowWord = (int) (x);
84-
return 32 + leadingZeros(lowWord);
79+
int z=leadingZeros((int) (x >>> 32)); // get leading zeros from high word
80+
if (z<32) return z;
81+
return z + leadingZeros((int) x); // add leading zeros from low word
8582
}
8683

8784
/**
@@ -113,7 +110,7 @@ public static int leadingOnes(long value) {
113110
* A long salt value used for internal hashing.
114111
*
115112
* We use a local, secure random number to minimise chance of attacker engineering hash collisions
116-
* also minimise risk of multiple peers suffering such attacks at the same time
113+
* also minimise risk of multiple peers suffering hashing collisions at the same time
117114
*/
118115
private static final long SALT=new SecureRandom().nextLong();
119116

convex-core/src/test/java/convex/core/data/EncodingTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public class EncodingTest {
5656
assertEquals(2,Format.getVLQCountLength(0x80)); // roll over at 128
5757
assertEquals(Format.MAX_VLQ_COUNT_LENGTH,Format.getVLQCountLength(Long.MAX_VALUE));
5858

59-
assertThrows(IllegalArgumentException.class, ()->Format.getVLQCountLength(-10));
59+
// technically an overflow
60+
assertEquals(0,Format.getVLQCountLength(-10));
6061
}
6162

6263
@Test public void testVLCCount() throws BadFormatException {

convex-core/src/test/java/convex/core/data/FormatTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import static org.junit.jupiter.api.Assertions.assertThrows;
55
import static org.junit.jupiter.api.Assertions.fail;
66

7+
import java.nio.ByteBuffer;
8+
79
import org.junit.jupiter.api.Test;
810

911
import convex.core.exceptions.BadFormatException;
@@ -36,6 +38,18 @@ private void assertBadVLCEncoding(String hex) {
3638
if (Format.getVLQLongLength(val)!=b.count()) throw new BadFormatException("Wrong length");
3739
});
3840
}
41+
42+
@Test public void testBigCount() throws BadFormatException {
43+
int c = Integer.MAX_VALUE;
44+
int n=Format.getVLQCountLength(c);
45+
assertEquals(5,n);
46+
ByteBuffer bb=ByteBuffer.allocate(n);
47+
Format.writeVLQCount(bb, c);
48+
bb.flip();
49+
assertEquals(c,Format.peekMessageLength(bb));
50+
Blob b=Blob.fromByteBuffer(bb);
51+
assertEquals(n,b.count());
52+
}
3953

4054
private void checkVLCEncoding(String hex, long a) {
4155
byte[] bs=new byte[12];

convex-core/src/test/java/convex/core/data/GenTestMessages.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
public class GenTestMessages {
1616

1717
@Property
18-
public void messageLengthVLC(Long a) throws BadFormatException {
19-
ByteBuffer bb = ByteBuffer.allocate(Format.MAX_VLQ_LONG_LENGTH);
20-
Format.writeVLQLong(bb, a);
18+
public void messageLengthVLQ(Integer a) throws BadFormatException {
19+
if (a<1) return;
20+
ByteBuffer bb = ByteBuffer.allocate(5); // sufficient for 32 bits
21+
Format.writeVLQCount(bb, a);
2122
bb.flip();
22-
assertEquals(bb.remaining(), Format.getVLQLongLength(a));
23+
assertEquals(a,Format.peekMessageLength(bb));
24+
assertEquals(bb.remaining(), Format.getVLQCountLength(a));
2325
}
2426
}

convex-peer/src/main/java/convex/net/MessageReceiver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public synchronized int receiveFromChannel(ReadableByteChannel chan) throws BadF
9797
int len = Format.peekMessageLength(buffer);
9898
if (len<0) return numRead; // Not enough bytes for a message length yet
9999

100-
int lengthLength = Format.getVLQLongLength(len);
100+
int lengthLength = Format.getVLQCountLength(len);
101101
int totalFrameSize=lengthLength + len;
102102

103103
if (totalFrameSize>buffer.capacity()) {

0 commit comments

Comments
 (0)