Skip to content

Commit e3f4366

Browse files
committed
Added flex int type
1 parent 1a41cb4 commit e3f4366

File tree

6 files changed

+191
-7
lines changed

6 files changed

+191
-7
lines changed

lib/assert.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ module.exports = class assert {
1616
break
1717
}
1818
}
19-
if (!constructorMatched) throw new TypeError(util.inspect(instance) + ' is not an instance of ' + constructors.map(constructor => constructor.name).join(' or '))
19+
if (!constructorMatched) {
20+
throw new TypeError(
21+
util.inspect(instance) +
22+
' is not an instance of ' +
23+
constructors
24+
.map(constructor => constructor.name)
25+
.join(' or ')
26+
)
27+
}
2028
}
2129
//Assert that a number is an integer (within the +/-2^53 that can be represented precisely in a double)
2230
static integer(instance) {

lib/flex-int.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const assert = require(__dirname + '/assert.js')
2+
3+
const possibleValueCount = bytes => {
4+
const usableBits = 7 * bytes
5+
return Math.pow(2, usableBits)
6+
}
7+
const UPPER_BOUNDS = new Map //mapping of numbers of bytes to the exclusive upper bound on numbers in the range
8+
{
9+
let cumulativeValues = 0
10+
let bytes = 0
11+
while (cumulativeValues <= Number.MAX_SAFE_INTEGER) {
12+
UPPER_BOUNDS.set(bytes, cumulativeValues)
13+
bytes++
14+
cumulativeValues += possibleValueCount(bytes)
15+
}
16+
}
17+
//Mapping of the number of leading ones in the first byte's mask to the number of bytes
18+
const NUMBER_OF_BYTES = new Map
19+
//Mapping of numbers of bytes to the mask for the first byte
20+
//Goes 0b00000000, 0b10000000, 0b11000000, etc.
21+
const BYTE_MASKS = new Map
22+
{
23+
let mask = 0
24+
let bitToMaskNext = 7 //0 is least significant bit, 7 is most significant bit
25+
for (const [bytes, _] of UPPER_BOUNDS) {
26+
if (!bytes) continue //should never be writing with 0 bytes
27+
28+
BYTE_MASKS.set(bytes, mask)
29+
NUMBER_OF_BYTES.set(bytes - 1, bytes)
30+
mask |= 1 << bitToMaskNext
31+
bitToMaskNext--
32+
}
33+
}
34+
35+
module.exports = {
36+
makeValueBuffer(value) {
37+
assert.integer(value)
38+
assert.assert(value >= 0, String(value) + ' is negative')
39+
const bytes = (() => {
40+
for (const [bytes, maxValue] of UPPER_BOUNDS) {
41+
if (maxValue > value) return bytes
42+
}
43+
})()
44+
const writeValue = value - UPPER_BOUNDS.get(bytes - 1)
45+
const buffer = new ArrayBuffer(bytes)
46+
const castBuffer = new Uint8Array(buffer)
47+
{
48+
let shiftedValue = writeValue
49+
for (let writeByte = bytes - 1; writeByte >= 0; writeByte--) {
50+
castBuffer[writeByte] = shiftedValue & 0xFF //write least significant byte
51+
shiftedValue >>= 8 //move next least significant byte to least significant byte
52+
}
53+
}
54+
castBuffer[0] |= BYTE_MASKS.get(bytes)
55+
return buffer
56+
},
57+
getByteCount(firstByte) {
58+
assert.byteUnsignedInteger(firstByte)
59+
const leadingOnes = (() => {
60+
let leadingOnes
61+
for (leadingOnes = 0; firstByte & 0b10000000; leadingOnes++) {
62+
firstByte <<= 1
63+
}
64+
return leadingOnes
65+
})()
66+
const bytes = NUMBER_OF_BYTES.get(leadingOnes)
67+
assert.assert(bytes !== undefined, 'Invalid number of bytes')
68+
return bytes
69+
},
70+
readValueBuffer(valueBuffer) {
71+
assert.instanceOf(valueBuffer, ArrayBuffer)
72+
const bytes = valueBuffer.byteLength
73+
const castBuffer = new Uint8Array(valueBuffer)
74+
const valueOfPossible = (() => {
75+
let value = 0
76+
for (let byteIndex = 0; byteIndex < bytes; byteIndex++) {
77+
value <<= 8
78+
value |= castBuffer[byteIndex]
79+
}
80+
return value ^ (BYTE_MASKS.get(bytes) << ((bytes - 1) * 8))
81+
})()
82+
return UPPER_BOUNDS.get(bytes - 1) + valueOfPossible
83+
}
84+
}

read.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const assert = require(__dirname + '/lib/assert.js')
77
const bitMath = require(__dirname + '/lib/bit-math.js')
88
const bufferString = require(__dirname + '/lib/buffer-string.js')
99
const constructorRegistry = require(__dirname + '/constructor-registry.js')
10+
const flexInt = require(__dirname + '/lib/flex-int.js')
1011
const recursiveRegistry = require(__dirname + '/recursive-registry.js')
1112
const strint = require(__dirname + '/lib/strint.js')
1213
const t = require(__dirname + '/structure-types.js')
@@ -24,7 +25,7 @@ function readLengthBuffer(buffer, offset) {
2425
catch (e) { assert.fail(NOT_LONG_ENOUGH) }
2526
}
2627
//Types whose type bytes are only 1 byte long (the type byte)
27-
const SINGLE_BYTE_TYPES = [
28+
const SINGLE_BYTE_TYPES = new Set([
2829
t.ByteType,
2930
t.ShortType,
3031
t.IntType,
@@ -35,6 +36,7 @@ const SINGLE_BYTE_TYPES = [
3536
t.UnsignedIntType,
3637
t.UnsignedLongType,
3738
t.BigUnsignedIntType,
39+
t.FlexUnsignedIntType,
3840
t.DateType,
3941
t.DayType,
4042
t.TimeType,
@@ -45,7 +47,12 @@ const SINGLE_BYTE_TYPES = [
4547
t.CharType,
4648
t.StringType,
4749
t.OctetsType
48-
]
50+
])
51+
//Mapping of type bytes to the corresponding types
52+
const SINGLE_BYTE_TYPE_BYTES = new Map
53+
for (const singleByteType of SINGLE_BYTE_TYPES) {
54+
SINGLE_BYTE_TYPE_BYTES.set(singleByteType._value, singleByteType)
55+
}
4956
//Pads a string with preceding 0s so that it has the desired length (for error messages)
5057
function pad(str, digits) {
5158
if (str.length < digits) return '0'.repeat(digits - str.length) + str
@@ -154,7 +161,7 @@ function consumeValue({buffer, pointerStart, offset, type, baseValue}) {
154161
}
155162
case t.UnsignedByteType: {
156163
length = 1
157-
assert.assert(buffer.byteLength >= offset + length, NOT_LONG_ENOUGH)
164+
assert.assert(buffer.byteLength > offset, NOT_LONG_ENOUGH)
158165
value = new Uint8Array(buffer)[offset] //endianness doesn't matter because there is only 1 byte
159166
break
160167
}
@@ -193,6 +200,15 @@ function consumeValue({buffer, pointerStart, offset, type, baseValue}) {
193200
length += bytes
194201
break
195202
}
203+
case t.FlexUnsignedIntType: {
204+
length = 1
205+
assert.assert(buffer.byteLength > offset, NOT_LONG_ENOUGH)
206+
const bytes = flexInt.getByteCount(new Uint8Array(buffer)[offset])
207+
assert.assert(buffer.byteLength >= offset + bytes, NOT_LONG_ENOUGH)
208+
value = flexInt.readValueBuffer(buffer.slice(offset, offset + bytes))
209+
length += bytes
210+
break
211+
}
196212
case t.DayType: {
197213
length = 3
198214
assert.assert(buffer.byteLength >= offset + length, NOT_LONG_ENOUGH)
@@ -412,9 +428,8 @@ function consumeType(typeBuffer, offset) {
412428
const typeByte = castBuffer[offset]
413429
let value,
414430
length = 1
415-
for (const testType of SINGLE_BYTE_TYPES) {
416-
if (typeByte === testType._value) return {value: new testType, length} //eslint-disable-line new-cap
417-
}
431+
const singleByteType = SINGLE_BYTE_TYPE_BYTES.get(typeByte)
432+
if (singleByteType !== undefined) return {value: new singleByteType, length} //eslint-disable-line new-cap
418433
switch (typeByte) {
419434
case t.BooleanTupleType._value: {
420435
assert.assert(typeBuffer.byteLength > offset + length, NOT_LONG_ENOUGH)

structure-types.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const assert = require(__dirname + '/lib/assert.js')
1010
const base64 = require('base64-js')
1111
const bufferString = require(__dirname + '/lib/buffer-string.js')
1212
const config = require(__dirname + '/config.js')
13+
const flexInt = require(__dirname + '/lib/flex-int.js')
1314
const sha256 = require('sha256')
1415
const GrowableBuffer = require(__dirname + '/lib/growable-buffer.js')
1516
let recursiveRegistry
@@ -533,6 +534,18 @@ class BigUnsignedIntType extends UnsignedType {
533534
buffer.addAll(byteBuffer)
534535
}
535536
}
537+
class FlexUnsignedIntType extends UnsignedType {
538+
static get _value() {
539+
return 0x17
540+
}
541+
writeValue(buffer, value) {
542+
assert.instanceOf(buffer, GrowableBuffer)
543+
const convertedValue = strToNum(value)
544+
if (convertedValue !== undefined) value = convertedValue
545+
assert.integer(value)
546+
buffer.addAll(flexInt.makeValueBuffer(value))
547+
}
548+
}
536549

537550
/**
538551
* A type storing some sort of time.
@@ -1665,6 +1678,7 @@ module.exports = {
16651678
UnsignedIntType,
16661679
UnsignedLongType,
16671680
BigUnsignedIntType,
1681+
FlexUnsignedIntType,
16681682
DateType,
16691683
DayType,
16701684
TimeType,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*eslint-disable no-undef*/
2+
const type = new t.FlexUnsignedIntType
3+
const buffer = type.toBuffer()
4+
assert.equal(buffer, bufferFrom([0x17]))
5+
assert.equal(r.type(buffer), new t.FlexUnsignedIntType)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*eslint-disable no-undef*/
2+
const type = new t.FlexUnsignedIntType
3+
const TWO_7 = Math.pow(2, 7),
4+
TWO_14 = Math.pow(2, 14)
5+
for (let value = 0; value < TWO_7; value++) {
6+
const valueBuffer = type.valueBuffer(value)
7+
assert.equal(valueBuffer, bufferFrom([value]))
8+
assert.equal(r.value({type, buffer: valueBuffer}), value)
9+
}
10+
for (let value = TWO_7; value < TWO_7 + TWO_14; value++) {
11+
const relativeValue = value - TWO_7
12+
const valueBuffer = type.valueBuffer(value)
13+
assert.equal(valueBuffer, bufferFrom([
14+
0b10000000 | (relativeValue >> 8),
15+
relativeValue & 0xFF
16+
]))
17+
assert.equal(r.value({type, buffer: valueBuffer}), value)
18+
}
19+
for (let value = TWO_7 + TWO_14; value < 50000; value++) {
20+
const relativeValue = value - (TWO_7 + TWO_14)
21+
const valueBuffer = type.valueBuffer(value)
22+
assert.equal(valueBuffer, bufferFrom([
23+
0b11000000 | (relativeValue >> 16),
24+
(relativeValue >> 8) & 0xFF,
25+
relativeValue & 0xFF
26+
]))
27+
assert.equal(r.value({type, buffer: valueBuffer}), value)
28+
}
29+
assert.equal(type.valueBuffer('123'), bufferFrom([123]))
30+
31+
assert.throws(
32+
() => type.valueBuffer(true),
33+
'true is not an instance of Number'
34+
)
35+
assert.throws(
36+
() => type.valueBuffer(1.2),
37+
'1.2 is not an integer'
38+
)
39+
assert.throws(
40+
() => type.valueBuffer(Number.MAX_SAFE_INTEGER * 2),
41+
'18014398509481982 is not an integer'
42+
)
43+
assert.throws(
44+
() => type.valueBuffer(-1),
45+
'-1 is negative'
46+
)
47+
assert.throws(
48+
() => r.value({type, buffer: bufferFrom([])}),
49+
'Buffer is not long enough'
50+
)
51+
assert.throws(
52+
() => r.value({type, buffer: bufferFrom([0b11111111])}),
53+
'Invalid number of bytes'
54+
)
55+
assert.throws(
56+
() => r.value({type, buffer: bufferFrom([0b10000001])}),
57+
'Buffer is not long enough'
58+
)

0 commit comments

Comments
 (0)