Skip to content

Commit 89c2588

Browse files
authored
Add additional varint types and bitflags (#163)
* Implement more varint variations * clean * varint128 * Update varint.js * Add bitflags * logging fix * fix BigInt handling for bitflags.shift * run CI against protodef fork * update test bigint handling * update ProtoDef submodule * update submod * Update package.json
1 parent 67b411a commit 89c2588

File tree

7 files changed

+297
-52
lines changed

7 files changed

+297
-52
lines changed

example.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
const assert = require('assert')
12
const ProtoDef = require('protodef').ProtoDef
23
const Serializer = require('protodef').Serializer
34
const Parser = require('protodef').Parser
45

6+
BigInt.prototype.toJSON = function () { // eslint-disable-line -- Allow serializing BigIntegers
7+
return this.toString()
8+
}
9+
510
// the protocol can be in a separate json file
611
const exampleProtocol = {
712
container: 'native',
813
varint: 'native',
914
byte: 'native',
1015
bool: 'native',
1116
switch: 'native',
17+
bitflags: 'native',
1218
entity_look: [
1319
'container',
1420
[
@@ -24,10 +30,11 @@ const exampleProtocol = {
2430
name: 'pitch',
2531
type: 'i8'
2632
},
27-
{
28-
name: 'onGround',
29-
type: 'bool'
30-
}
33+
{ name: 'flags', type: ['bitflags', { type: 'u8', flags: ['onGround'] }] },
34+
{ name: 'longId', type: 'varint64' },
35+
{ name: 'longerId', type: 'varint128' },
36+
{ name: 'zigzagId', type: 'zigzag32' },
37+
{ name: 'zigzagBig', type: 'zigzag64' }
3138
]
3239
],
3340
packet: [
@@ -71,12 +78,19 @@ serializer.write({
7178
params: {
7279
entityId: 1,
7380
yaw: 1,
74-
pitch: 1,
75-
onGround: true
81+
pitch: 6,
82+
flags: {
83+
onGround: true
84+
},
85+
longId: 13n,
86+
longerId: 2n ** 68n, // 9 bytes integer, 10 over wire
87+
zigzagId: -3,
88+
zigzagBig: 4294967296n
7689
}
7790
})
7891
serializer.pipe(parser)
7992

8093
parser.on('data', function (chunk) {
81-
console.log(JSON.stringify(chunk, null, 2))
94+
console.dir(chunk, { depth: null })
95+
assert.deepEqual([...chunk.buffer], [22, 1, 1, 6, 1, 13, 128, 128, 128, 128, 128, 128, 128, 128, 128, 32, 5, 128, 128, 128, 128, 32])
8296
})

src/datatypes/compiler-utils.js

+61
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ module.exports = {
5858
code += 'return { value: { ' + names.join(', ') + ` }, size: ${totalBytes} }`
5959
return compiler.wrapCode(code)
6060
}],
61+
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
62+
let fstr = JSON.stringify(flags)
63+
if (Array.isArray(flags)) {
64+
fstr = '{'
65+
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
66+
fstr += '}'
67+
} else if (shift) {
68+
fstr = '{'
69+
for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}`
70+
fstr += '}'
71+
}
72+
return compiler.wrapCode(`
73+
const { value: _value, size } = ${compiler.callType(type, 'offset')}
74+
const value = { _value }
75+
const flags = ${fstr}
76+
for (const key in flags) {
77+
value[key] = (_value & flags[key]) == flags[key]
78+
}
79+
return { value, size }
80+
`.trim())
81+
}],
6182
mapper: ['parametrizable', (compiler, mapper) => {
6283
let code = 'const { value, size } = ' + compiler.callType(mapper.type) + '\n'
6384
code += 'return { value: ' + JSON.stringify(sanitizeMappings(mapper.mappings)) + '[value] || value, size }'
@@ -116,6 +137,26 @@ module.exports = {
116137
code += 'return offset'
117138
return compiler.wrapCode(code)
118139
}],
140+
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
141+
let fstr = JSON.stringify(flags)
142+
if (Array.isArray(flags)) {
143+
fstr = '{'
144+
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
145+
fstr += '}'
146+
} else if (shift) {
147+
fstr = '{'
148+
for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}`
149+
fstr += '}'
150+
}
151+
return compiler.wrapCode(`
152+
const flags = ${fstr}
153+
let val = value._value ${big ? '|| 0n' : ''}
154+
for (const key in flags) {
155+
if (value[key]) val |= flags[key]
156+
}
157+
return (ctx.${type})(val, buffer, offset)
158+
`.trim())
159+
}],
119160
mapper: ['parametrizable', (compiler, mapper) => {
120161
const mappings = JSON.stringify(swapMappings(mapper.mappings))
121162
const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type)
@@ -148,6 +189,26 @@ module.exports = {
148189
const totalBytes = Math.ceil(values.reduce((acc, { size }) => acc + size, 0) / 8)
149190
return `${totalBytes}`
150191
}],
192+
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
193+
let fstr = JSON.stringify(flags)
194+
if (Array.isArray(flags)) {
195+
fstr = '{'
196+
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
197+
fstr += '}'
198+
} else if (shift) {
199+
fstr = '{'
200+
for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}`
201+
fstr += '}'
202+
}
203+
return compiler.wrapCode(`
204+
const flags = ${fstr}
205+
let val = value._value ${big ? '|| 0n' : ''}
206+
for (const key in flags) {
207+
if (value[key]) val |= flags[key]
208+
}
209+
return (ctx.${type})(val)
210+
`.trim())
211+
}],
151212
mapper: ['parametrizable', (compiler, mapper) => {
152213
const mappings = JSON.stringify(swapMappings(mapper.mappings))
153214
const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type)

src/datatypes/utils.js

+65-43
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
const { getCount, sendCount, calcCount, PartialReadError } = require('../utils')
22

33
module.exports = {
4-
varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/utils.json').varint],
54
bool: [readBool, writeBool, 1, require('../../ProtoDef/schemas/utils.json').bool],
65
pstring: [readPString, writePString, sizeOfPString, require('../../ProtoDef/schemas/utils.json').pstring],
76
buffer: [readBuffer, writeBuffer, sizeOfBuffer, require('../../ProtoDef/schemas/utils.json').buffer],
87
void: [readVoid, writeVoid, 0, require('../../ProtoDef/schemas/utils.json').void],
98
bitfield: [readBitField, writeBitField, sizeOfBitField, require('../../ProtoDef/schemas/utils.json').bitfield],
9+
bitflags: [readBitflags, writeBitflags, sizeOfBitflags, require('../../ProtoDef/schemas/utils.json').bitflags],
1010
cstring: [readCString, writeCString, sizeOfCString, require('../../ProtoDef/schemas/utils.json').cstring],
11-
mapper: [readMapper, writeMapper, sizeOfMapper, require('../../ProtoDef/schemas/utils.json').mapper]
11+
mapper: [readMapper, writeMapper, sizeOfMapper, require('../../ProtoDef/schemas/utils.json').mapper],
12+
...require('./varint')
1213
}
1314

1415
function mapperEquality (a, b) {
@@ -58,47 +59,6 @@ function sizeOfMapper (value, { type, mappings }, rootNode) {
5859
return this.sizeOf(mappedValue, type, rootNode)
5960
}
6061

61-
function readVarInt (buffer, offset) {
62-
let result = 0
63-
let shift = 0
64-
let cursor = offset
65-
66-
while (true) {
67-
if (cursor + 1 > buffer.length) { throw new PartialReadError() }
68-
const b = buffer.readUInt8(cursor)
69-
result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB
70-
cursor++
71-
if (!(b & 0x80)) { // If the MSB is not set, we return the number
72-
return {
73-
value: result,
74-
size: cursor - offset
75-
}
76-
}
77-
shift += 7 // we only have 7 bits, MSB being the return-trigger
78-
if (shift > 64) throw new PartialReadError(`varint is too big: ${shift}`) // Make sure our shift don't overflow.
79-
}
80-
}
81-
82-
function sizeOfVarInt (value) {
83-
let cursor = 0
84-
while (value & ~0x7F) {
85-
value >>>= 7
86-
cursor++
87-
}
88-
return cursor + 1
89-
}
90-
91-
function writeVarInt (value, buffer, offset) {
92-
let cursor = 0
93-
while (value & ~0x7F) {
94-
buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor)
95-
cursor++
96-
value >>>= 7
97-
}
98-
buffer.writeUInt8(value, offset + cursor)
99-
return offset + cursor + 1
100-
}
101-
10262
function readPString (buffer, offset, typeArgs, rootNode) {
10363
const { size, count } = getCount.call(this, buffer, offset, typeArgs, rootNode)
10464
const cursor = offset + size
@@ -258,3 +218,65 @@ function sizeOfCString (value) {
258218
const length = Buffer.byteLength(value, 'utf8')
259219
return length + 1
260220
}
221+
222+
function readBitflags (buffer, offset, { type, flags, shift, big }, rootNode) {
223+
const { size, value } = this.read(buffer, offset, type, rootNode)
224+
let f = {}
225+
if (Array.isArray(flags)) {
226+
for (const [k, v] of Object.entries(flags)) {
227+
f[v] = big ? (1n << BigInt(k)) : (1 << k)
228+
}
229+
} else if (shift) {
230+
for (const k in flags) {
231+
f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k])
232+
}
233+
} else {
234+
f = flags
235+
}
236+
const result = { _value: value }
237+
for (const key in f) {
238+
result[key] = (value & f[key]) === f[key]
239+
}
240+
return { value: result, size }
241+
}
242+
243+
function writeBitflags (value, buffer, offset, { type, flags, shift, big }, rootNode) {
244+
let f = {}
245+
if (Array.isArray(flags)) {
246+
for (const [k, v] of Object.entries(flags)) {
247+
f[v] = big ? (1n << BigInt(k)) : (1 << k)
248+
}
249+
} else if (shift) {
250+
for (const k in flags) {
251+
f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k])
252+
}
253+
} else {
254+
f = flags
255+
}
256+
let val = value._value || (big ? 0n : 0)
257+
for (const key in f) {
258+
if (value[key]) val |= f[key]
259+
}
260+
return this.write(val, buffer, offset, type, rootNode)
261+
}
262+
263+
function sizeOfBitflags (value, { type, flags, shift, big }, rootNode) {
264+
if (!value) throw new Error('Missing field')
265+
let f = {}
266+
if (Array.isArray(flags)) {
267+
for (const [k, v] of Object.entries(flags)) {
268+
f[v] = big ? (1n << BigInt(k)) : (1 << k)
269+
}
270+
} else if (shift) {
271+
for (const k in flags) {
272+
f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k])
273+
}
274+
} else {
275+
f = flags
276+
}
277+
let mappedValue = value._value || (big ? 0n : 0)
278+
for (const key in f) {
279+
if (value[key]) mappedValue |= f[key]
280+
}
281+
return this.sizeOf(mappedValue, type, rootNode)
282+
}

0 commit comments

Comments
 (0)