From 502c058c98ed1c1488f0a487517413432fe47080 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Wed, 4 Dec 2024 03:29:10 +0000 Subject: [PATCH 01/12] Implement more varint variations --- package.json | 1 + src/datatypes/utils.js | 45 +------- src/datatypes/varint.js | 223 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 43 deletions(-) create mode 100644 src/datatypes/varint.js diff --git a/package.json b/package.json index 25007bf..33258c9 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "types": "index.d.ts", "author": "roblabla ", "scripts": { + "prepare": "git submodule update --init", "lint": "standard", "fix": "standard --fix", "unit-test": "mocha --recursive --reporter spec", diff --git a/src/datatypes/utils.js b/src/datatypes/utils.js index 8cc1d52..253362d 100644 --- a/src/datatypes/utils.js +++ b/src/datatypes/utils.js @@ -1,14 +1,14 @@ const { getCount, sendCount, calcCount, PartialReadError } = require('../utils') module.exports = { - varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/utils.json').varint], bool: [readBool, writeBool, 1, require('../../ProtoDef/schemas/utils.json').bool], pstring: [readPString, writePString, sizeOfPString, require('../../ProtoDef/schemas/utils.json').pstring], buffer: [readBuffer, writeBuffer, sizeOfBuffer, require('../../ProtoDef/schemas/utils.json').buffer], void: [readVoid, writeVoid, 0, require('../../ProtoDef/schemas/utils.json').void], bitfield: [readBitField, writeBitField, sizeOfBitField, require('../../ProtoDef/schemas/utils.json').bitfield], cstring: [readCString, writeCString, sizeOfCString, require('../../ProtoDef/schemas/utils.json').cstring], - mapper: [readMapper, writeMapper, sizeOfMapper, require('../../ProtoDef/schemas/utils.json').mapper] + mapper: [readMapper, writeMapper, sizeOfMapper, require('../../ProtoDef/schemas/utils.json').mapper], + ...require('./varint') } function mapperEquality (a, b) { @@ -58,47 +58,6 @@ function sizeOfMapper (value, { type, mappings }, rootNode) { return this.sizeOf(mappedValue, type, rootNode) } -function readVarInt (buffer, offset) { - let result = 0 - let shift = 0 - let cursor = offset - - while (true) { - if (cursor + 1 > buffer.length) { throw new PartialReadError() } - const b = buffer.readUInt8(cursor) - result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB - cursor++ - if (!(b & 0x80)) { // If the MSB is not set, we return the number - return { - value: result, - size: cursor - offset - } - } - shift += 7 // we only have 7 bits, MSB being the return-trigger - if (shift > 64) throw new PartialReadError(`varint is too big: ${shift}`) // Make sure our shift don't overflow. - } -} - -function sizeOfVarInt (value) { - let cursor = 0 - while (value & ~0x7F) { - value >>>= 7 - cursor++ - } - return cursor + 1 -} - -function writeVarInt (value, buffer, offset) { - let cursor = 0 - while (value & ~0x7F) { - buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor) - cursor++ - value >>>= 7 - } - buffer.writeUInt8(value, offset + cursor) - return offset + cursor + 1 -} - function readPString (buffer, offset, typeArgs, rootNode) { const { size, count } = getCount.call(this, buffer, offset, typeArgs, rootNode) const cursor = offset + size diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js new file mode 100644 index 0000000..869b7eb --- /dev/null +++ b/src/datatypes/varint.js @@ -0,0 +1,223 @@ +const { PartialReadError } = require('../utils') + +module.exports = { + varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/utils.json').varint], + varint64: [readVarLong, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/utils.json').varint], + zigzag32: [readSignedVarInt, writeSignedVarInt, sizeOfSignedVarInt, require('../../ProtoDef/schemas/utils.json').varint], + zigzag64: [readSignedVarLong, writeSignedVarLong, sizeOfSignedVarLong, require('../../ProtoDef/schemas/utils.json').varint] +} + +// u32 + +function readVarInt (buffer, offset) { + let result = 0 + let shift = 0 + let cursor = offset + + while (true) { + if (cursor + 1 > buffer.length) { throw new PartialReadError() } + const b = buffer.readUInt8(cursor) + result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB + cursor++ + if (!(b & 0x80)) { // If the MSB is not set, we return the number + return { + value: result, + size: cursor - offset + } + } + shift += 7 // we only have 7 bits, MSB being the return-trigger + if (shift > 64) throw new PartialReadError(`varint is too big: ${shift}`) // Make sure our shift don't overflow. + } +} + +function sizeOfVarInt (value) { + let cursor = 0 + while (value & ~0x7F) { + value >>>= 7 + cursor++ + } + return cursor + 1 +} + +function writeVarInt (value, buffer, offset) { + let cursor = 0 + while (value & ~0x7F) { + buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor) + cursor++ + value >>>= 7 + } + buffer.writeUInt8(value, offset + cursor) + return offset + cursor + 1 +} + +// u64 + +function sizeOfVarLong (value) { + if (typeof value.valueOf() === 'object') { + value = (BigInt(value[0]) << 32n) | BigInt(value[1]) + } else if (typeof value !== 'bigint') value = BigInt(value) + + let cursor = 0 + while (value > 127n) { + value >>= 7n + cursor++ + } + return cursor + 1 +} + +function readVarLong (buffer, offset) { + let result = BigInt(0) + let shift = 0n + let cursor = offset + let size = 0 + + while (true) { + if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } + const b = buffer.readUInt8(cursor) + result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB + cursor++ + if (!(b & 0x80)) { // If the MSB is not set, we return the number + size = cursor - offset + break + } + shift += 7n // we only have 7 bits, MSB being the return-trigger + if (shift > 63n) throw new Error(`varint is too big: ${shift}`) + } + + return { value: result, size } +} + +function writeVarLong (value, buffer, offset) { + // if an array, turn it into a BigInt + if (typeof value.valueOf() === 'object') { + value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1]) + } else if (typeof value !== 'bigint') value = BigInt(value) + + let cursor = 0 + while (value > 127n) { // keep writing in 7 bit slices + const num = Number(value & 0xFFn) + buffer.writeUInt8(num | 0x80, offset + cursor) + cursor++ + value >>= 7n + } + buffer.writeUInt8(Number(value), offset + cursor) + return offset + cursor + 1 +} + +// zs32 +function sizeOfSignedVarInt (value) { + value = (value << 1) ^ (value >> 63) + let cursor = 0 + while (value & ~0x7F) { + value >>>= 7 + cursor++ + } + return cursor + 1 +} + +function sizeOfSignedVarLong (value) { + if (typeof value.valueOf() === 'object') { + value = (BigInt(value[0]) << 32n) | BigInt(value[1]) + } else if (typeof value !== 'bigint') value = BigInt(value) + + value = (value << 1n) ^ (value >> 63n) + let cursor = 0 + while (value > 127n) { + value >>= 7n + cursor++ + } + return cursor + 1 +} + +/** + * Reads a zigzag encoded 64-bit VarInt as a BigInt + */ +function readSignedVarLong (buffer, offset) { + let result = BigInt(0) + let shift = 0n + let cursor = offset + let size = 0 + + while (true) { + if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } + const b = buffer.readUInt8(cursor) + result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB + cursor++ + if (!(b & 0x80)) { // If the MSB is not set, we return the number + size = cursor - offset + break + } + shift += 7n // we only have 7 bits, MSB being the return-trigger + if (shift > 63n) throw new Error(`varint is too big: ${shift}`) + } + + // in zigzag encoding, the sign bit is the LSB of the value - remove the bit, + // if 1, then flip the rest of the bits (xor) and set to negative + // Note: bigint has no sign bit; instead if we XOR -0 we get no-op, XOR -1 flips and sets negative + const zigzag = (result >> 1n) ^ -(result & 1n) + return { value: zigzag, size } +} + +/** + * Writes a zigzag encoded 64-bit VarInt as a BigInt + */ +function writeSignedVarLong (value, buffer, offset) { + // if an array, turn it into a BigInt + if (typeof value.valueOf() === 'object') { + value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1]) + } else if (typeof value !== 'bigint') value = BigInt(value) + + // shift value left and flip if negative (no sign bit, but right shifting beyond value will always be -0b1) + value = (value << 1n) ^ (value >> 63n) + let cursor = 0 + while (value > 127n) { // keep writing in 7 bit slices + const num = Number(value & 0xFFn) + buffer.writeUInt8(num | 0x80, offset + cursor) + cursor++ + value >>= 7n + } + buffer.writeUInt8(Number(value), offset + cursor) + return offset + cursor + 1 +} + +/** + * Reads a 32-bit zigzag encoded varint as a Number + */ +function readSignedVarInt (buffer, offset) { + let result = 0 + let shift = 0 + let cursor = offset + let size = 0 + + while (true) { + if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } + const b = buffer.readUInt8(cursor) + result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB + cursor++ + if (!(b & 0x80)) { // If the MSB is not set, we return the number + size = cursor - offset + break + } + shift += 7 // we only have 7 bits, MSB being the return-trigger + if (shift > 63) throw new Error(`varint is too big: ${shift}`) + } + + const zigzag = ((((result << 63) >> 63) ^ result) >> 1) ^ (result & (1 << 63)) + return { value: zigzag, size } +} + +/** + * Writes a 32-bit zigzag encoded varint + */ +function writeSignedVarInt (value, buffer, offset) { + value = (value << 1) ^ (value >> 31) + let cursor = 0 + while (value & ~0x7F) { + const num = Number((value & 0xFF) | 0x80) + buffer.writeUInt8(num, offset + cursor) + cursor++ + value >>>= 7 + } + buffer.writeUInt8(value, offset + cursor) + return offset + cursor + 1 +} From 07ecaebdac2bd6ba05d73d8b553fcb5fa72e2461 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Wed, 4 Dec 2024 03:54:31 +0000 Subject: [PATCH 02/12] clean --- example.js | 21 ++++- src/datatypes/varint.js | 196 ++++++++++------------------------------ test/misc.js | 5 + 3 files changed, 71 insertions(+), 151 deletions(-) create mode 100644 test/misc.js diff --git a/example.js b/example.js index 3d3f940..4a3cede 100644 --- a/example.js +++ b/example.js @@ -2,6 +2,10 @@ const ProtoDef = require('protodef').ProtoDef const Serializer = require('protodef').Serializer const Parser = require('protodef').Parser +BigInt.prototype.toJSON = function () { // eslint-disable-line -- Allow serializing BigIntegers + return this.toString() +} + // the protocol can be in a separate json file const exampleProtocol = { container: 'native', @@ -27,6 +31,18 @@ const exampleProtocol = { { name: 'onGround', type: 'bool' + }, + { + name: 'longId', + type: 'varint64' + }, + { + name: 'zigzagId', + type: 'zigzag32' + }, + { + name: 'zigzagBig', + type: 'zigzag64' } ] ], @@ -72,7 +88,10 @@ serializer.write({ entityId: 1, yaw: 1, pitch: 1, - onGround: true + onGround: true, + longId: 22n, + zigzagId: 66, + zigzagBig: 4294967296n } }) serializer.pipe(parser) diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js index 869b7eb..330816c 100644 --- a/src/datatypes/varint.js +++ b/src/datatypes/varint.js @@ -15,17 +15,14 @@ function readVarInt (buffer, offset) { let cursor = offset while (true) { - if (cursor + 1 > buffer.length) { throw new PartialReadError() } - const b = buffer.readUInt8(cursor) - result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB + if (cursor >= buffer.length) throw new PartialReadError('Unexpected buffer end while reading VarInt') + const byte = buffer.readUInt8(cursor) + result |= (byte & 0x7F) << shift // Add the bits, excluding the MSB cursor++ - if (!(b & 0x80)) { // If the MSB is not set, we return the number - return { - value: result, - size: cursor - offset - } + if (!(byte & 0x80)) { // If MSB is not set, return result + return { value: result, size: cursor - offset } } - shift += 7 // we only have 7 bits, MSB being the return-trigger + shift += 7 if (shift > 64) throw new PartialReadError(`varint is too big: ${shift}`) // Make sure our shift don't overflow. } } @@ -52,172 +49,71 @@ function writeVarInt (value, buffer, offset) { // u64 -function sizeOfVarLong (value) { - if (typeof value.valueOf() === 'object') { - value = (BigInt(value[0]) << 32n) | BigInt(value[1]) - } else if (typeof value !== 'bigint') value = BigInt(value) - - let cursor = 0 - while (value > 127n) { - value >>= 7n - cursor++ - } - return cursor + 1 -} - function readVarLong (buffer, offset) { - let result = BigInt(0) + let result = 0n let shift = 0n let cursor = offset - let size = 0 while (true) { - if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } - const b = buffer.readUInt8(cursor) - result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB + if (cursor >= buffer.length) throw new Error('Unexpected buffer end while reading VarLong') + const byte = buffer.readUInt8(cursor) + result |= (BigInt(byte) & 0x7Fn) << shift // Add the bits, excluding the MSB cursor++ - if (!(b & 0x80)) { // If the MSB is not set, we return the number - size = cursor - offset - break + if (!(byte & 0x80)) { // If MSB is not set, return result + return { value: result, size: cursor - offset } } - shift += 7n // we only have 7 bits, MSB being the return-trigger + shift += 7n if (shift > 63n) throw new Error(`varint is too big: ${shift}`) } - - return { value: result, size } } -function writeVarLong (value, buffer, offset) { - // if an array, turn it into a BigInt - if (typeof value.valueOf() === 'object') { - value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1]) - } else if (typeof value !== 'bigint') value = BigInt(value) - - let cursor = 0 - while (value > 127n) { // keep writing in 7 bit slices - const num = Number(value & 0xFFn) - buffer.writeUInt8(num | 0x80, offset + cursor) - cursor++ +function sizeOfVarLong (value) { + value = BigInt(value) + let size = 0 + do { value >>= 7n - } - buffer.writeUInt8(Number(value), offset + cursor) - return offset + cursor + 1 + size++ + } while (value !== 0n) + return size } -// zs32 -function sizeOfSignedVarInt (value) { - value = (value << 1) ^ (value >> 63) - let cursor = 0 - while (value & ~0x7F) { - value >>>= 7 - cursor++ - } - return cursor + 1 -} - -function sizeOfSignedVarLong (value) { - if (typeof value.valueOf() === 'object') { - value = (BigInt(value[0]) << 32n) | BigInt(value[1]) - } else if (typeof value !== 'bigint') value = BigInt(value) - - value = (value << 1n) ^ (value >> 63n) - let cursor = 0 - while (value > 127n) { +function writeVarLong (value, buffer, offset) { + value = BigInt(value) + let cursor = offset + do { + const byte = value & 0x7Fn value >>= 7n - cursor++ - } - return cursor + 1 + buffer.writeUInt8(Number(byte) | (value ? 0x80 : 0), cursor++) + } while (value) + return cursor } -/** - * Reads a zigzag encoded 64-bit VarInt as a BigInt - */ -function readSignedVarLong (buffer, offset) { - let result = BigInt(0) - let shift = 0n - let cursor = offset - let size = 0 - - while (true) { - if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } - const b = buffer.readUInt8(cursor) - result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB - cursor++ - if (!(b & 0x80)) { // If the MSB is not set, we return the number - size = cursor - offset - break - } - shift += 7n // we only have 7 bits, MSB being the return-trigger - if (shift > 63n) throw new Error(`varint is too big: ${shift}`) - } +// Zigzag 32 - // in zigzag encoding, the sign bit is the LSB of the value - remove the bit, - // if 1, then flip the rest of the bits (xor) and set to negative - // Note: bigint has no sign bit; instead if we XOR -0 we get no-op, XOR -1 flips and sets negative - const zigzag = (result >> 1n) ^ -(result & 1n) - return { value: zigzag, size } +function readSignedVarInt (buffer, offset) { + const { value, size } = readVarInt(buffer, offset) + return { value: (value >>> 1) ^ -(value & 1), size } } -/** - * Writes a zigzag encoded 64-bit VarInt as a BigInt - */ -function writeSignedVarLong (value, buffer, offset) { - // if an array, turn it into a BigInt - if (typeof value.valueOf() === 'object') { - value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1]) - } else if (typeof value !== 'bigint') value = BigInt(value) +function sizeOfSignedVarInt (value) { + return sizeOfVarInt((value << 1) ^ (value >> 31)) +} - // shift value left and flip if negative (no sign bit, but right shifting beyond value will always be -0b1) - value = (value << 1n) ^ (value >> 63n) - let cursor = 0 - while (value > 127n) { // keep writing in 7 bit slices - const num = Number(value & 0xFFn) - buffer.writeUInt8(num | 0x80, offset + cursor) - cursor++ - value >>= 7n - } - buffer.writeUInt8(Number(value), offset + cursor) - return offset + cursor + 1 +function writeSignedVarInt (value, buffer, offset) { + return writeVarInt((value << 1) ^ (value >> 31), buffer, offset) } -/** - * Reads a 32-bit zigzag encoded varint as a Number - */ -function readSignedVarInt (buffer, offset) { - let result = 0 - let shift = 0 - let cursor = offset - let size = 0 +// Zigzag 64 - while (true) { - if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') } - const b = buffer.readUInt8(cursor) - result |= ((b & 0x7f) << shift) // Add the bits to our number, except MSB - cursor++ - if (!(b & 0x80)) { // If the MSB is not set, we return the number - size = cursor - offset - break - } - shift += 7 // we only have 7 bits, MSB being the return-trigger - if (shift > 63) throw new Error(`varint is too big: ${shift}`) - } +function readSignedVarLong (buffer, offset) { + const { value, size } = readVarLong(buffer, offset) + return { value: (value >> 1n) ^ -(value & 1n), size } +} - const zigzag = ((((result << 63) >> 63) ^ result) >> 1) ^ (result & (1 << 63)) - return { value: zigzag, size } +function sizeOfSignedVarLong (value) { + return sizeOfVarLong((BigInt(value) << 1n) ^ (BigInt(value) >> 63n)) } -/** - * Writes a 32-bit zigzag encoded varint - */ -function writeSignedVarInt (value, buffer, offset) { - value = (value << 1) ^ (value >> 31) - let cursor = 0 - while (value & ~0x7F) { - const num = Number((value & 0xFF) | 0x80) - buffer.writeUInt8(num, offset + cursor) - cursor++ - value >>>= 7 - } - buffer.writeUInt8(value, offset + cursor) - return offset + cursor + 1 +function writeSignedVarLong (value, buffer, offset) { + return writeVarLong((BigInt(value) << 1n) ^ (BigInt(value) >> 63n), buffer, offset) } diff --git a/test/misc.js b/test/misc.js new file mode 100644 index 0000000..83200bc --- /dev/null +++ b/test/misc.js @@ -0,0 +1,5 @@ +/* eslint-env mocha */ + +it('example works', () => { + require('../example') +}) From d611d1a36089d314acfbcf283b624563e232431c Mon Sep 17 00:00:00 2001 From: extremeheat Date: Wed, 4 Dec 2024 04:22:03 +0000 Subject: [PATCH 03/12] varint128 --- example.js | 21 +++++++-------------- src/datatypes/varint.js | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/example.js b/example.js index 4a3cede..8f68bdb 100644 --- a/example.js +++ b/example.js @@ -32,18 +32,10 @@ const exampleProtocol = { name: 'onGround', type: 'bool' }, - { - name: 'longId', - type: 'varint64' - }, - { - name: 'zigzagId', - type: 'zigzag32' - }, - { - name: 'zigzagBig', - type: 'zigzag64' - } + { name: 'longId', type: 'varint64' }, + { name: 'longerId', type: 'varint128' }, + { name: 'zigzagId', type: 'zigzag32' }, + { name: 'zigzagBig', type: 'zigzag64' } ] ], packet: [ @@ -89,8 +81,9 @@ serializer.write({ yaw: 1, pitch: 1, onGround: true, - longId: 22n, - zigzagId: 66, + longId: 13n, + longerId: 2n ** 68n, // 9 bytes integer, 10 over wire + zigzagId: -3, zigzagBig: 4294967296n } }) diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js index 330816c..c5ea896 100644 --- a/src/datatypes/varint.js +++ b/src/datatypes/varint.js @@ -3,6 +3,7 @@ const { PartialReadError } = require('../utils') module.exports = { varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/utils.json').varint], varint64: [readVarLong, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/utils.json').varint], + varint128: [readVarLong128, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/utils.json').varint], zigzag32: [readSignedVarInt, writeSignedVarInt, sizeOfSignedVarInt, require('../../ProtoDef/schemas/utils.json').varint], zigzag64: [readSignedVarLong, writeSignedVarLong, sizeOfSignedVarLong, require('../../ProtoDef/schemas/utils.json').varint] } @@ -67,6 +68,24 @@ function readVarLong (buffer, offset) { } } +function readVarLong128 (buffer, offset) { + let result = 0n + let shift = 0n + let cursor = offset + + while (true) { + if (cursor >= buffer.length) throw new Error('Unexpected buffer end while reading VarLong') + const byte = buffer.readUInt8(cursor) + result |= (BigInt(byte) & 0x7Fn) << shift // Add the bits, excluding the MSB + cursor++ + if (!(byte & 0x80)) { // If MSB is not set, return result + return { value: result, size: cursor - offset } + } + shift += 7n + if (shift > 128n) throw new Error(`varint is too big: ${shift}`) + } +} + function sizeOfVarLong (value) { value = BigInt(value) let size = 0 From 068697913f23b09163b9d5b315c93ab81095ffb8 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Wed, 4 Dec 2024 02:34:54 -0500 Subject: [PATCH 04/12] Update varint.js --- src/datatypes/varint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js index c5ea896..4400b48 100644 --- a/src/datatypes/varint.js +++ b/src/datatypes/varint.js @@ -82,7 +82,7 @@ function readVarLong128 (buffer, offset) { return { value: result, size: cursor - offset } } shift += 7n - if (shift > 128n) throw new Error(`varint is too big: ${shift}`) + if (shift > 127n) throw new Error(`varint is too big: ${shift}`) } } From 4ffa8312c14648ed0b296fa6f5c6aa7790a3097c Mon Sep 17 00:00:00 2001 From: extremeheat Date: Thu, 5 Dec 2024 07:51:50 +0000 Subject: [PATCH 05/12] Add bitflags --- example.js | 16 +++++---- src/datatypes/compiler-utils.js | 61 +++++++++++++++++++++++++++++++ src/datatypes/utils.js | 63 +++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 7 deletions(-) diff --git a/example.js b/example.js index 8f68bdb..9730859 100644 --- a/example.js +++ b/example.js @@ -1,3 +1,4 @@ +const assert = require('assert') const ProtoDef = require('protodef').ProtoDef const Serializer = require('protodef').Serializer const Parser = require('protodef').Parser @@ -13,6 +14,7 @@ const exampleProtocol = { byte: 'native', bool: 'native', switch: 'native', + bitflags: 'native', entity_look: [ 'container', [ @@ -28,10 +30,7 @@ const exampleProtocol = { name: 'pitch', type: 'i8' }, - { - name: 'onGround', - type: 'bool' - }, + { name: 'flags', type: ['bitflags', { type: 'u8', flags: ['onGround'] }] }, { name: 'longId', type: 'varint64' }, { name: 'longerId', type: 'varint128' }, { name: 'zigzagId', type: 'zigzag32' }, @@ -79,8 +78,10 @@ serializer.write({ params: { entityId: 1, yaw: 1, - pitch: 1, - onGround: true, + pitch: 6, + flags: { + onGround: true + }, longId: 13n, longerId: 2n ** 68n, // 9 bytes integer, 10 over wire zigzagId: -3, @@ -90,5 +91,6 @@ serializer.write({ serializer.pipe(parser) parser.on('data', function (chunk) { - console.log(JSON.stringify(chunk, null, 2)) + console.log(chunk) + 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]) }) diff --git a/src/datatypes/compiler-utils.js b/src/datatypes/compiler-utils.js index 824cae3..d46cb89 100644 --- a/src/datatypes/compiler-utils.js +++ b/src/datatypes/compiler-utils.js @@ -58,6 +58,27 @@ module.exports = { code += 'return { value: { ' + names.join(', ') + ` }, size: ${totalBytes} }` return compiler.wrapCode(code) }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` +const { value: _value, size } = ${compiler.callType(type, 'offset')} +const value = { _value } +const flags = ${fstr} +for (const key in flags) { + value[key] = (_value & flags[key]) == flags[key] +} +return { value, size } + `.trim()) + }], mapper: ['parametrizable', (compiler, mapper) => { let code = 'const { value, size } = ' + compiler.callType(mapper.type) + '\n' code += 'return { value: ' + JSON.stringify(sanitizeMappings(mapper.mappings)) + '[value] || value, size }' @@ -116,6 +137,26 @@ module.exports = { code += 'return offset' return compiler.wrapCode(code) }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` +const flags = ${fstr} +let val = value._value ${big ? '|| 0n' : ''} +for (const key in flags) { + if (value[key]) val |= flags[key] +} +return (ctx.${type})(val, buffer, offset) + `.trim()) + }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) @@ -148,6 +189,26 @@ module.exports = { const totalBytes = Math.ceil(values.reduce((acc, { size }) => acc + size, 0) / 8) return `${totalBytes}` }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` +const flags = ${fstr} +let val = value._value ${big ? '|| 0n' : ''} +for (const key in flags) { + if (value[key]) val |= flags[key] +} +return (ctx.${type})(val) + `.trim()) + }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) diff --git a/src/datatypes/utils.js b/src/datatypes/utils.js index 253362d..ec82930 100644 --- a/src/datatypes/utils.js +++ b/src/datatypes/utils.js @@ -6,6 +6,7 @@ module.exports = { buffer: [readBuffer, writeBuffer, sizeOfBuffer, require('../../ProtoDef/schemas/utils.json').buffer], void: [readVoid, writeVoid, 0, require('../../ProtoDef/schemas/utils.json').void], bitfield: [readBitField, writeBitField, sizeOfBitField, require('../../ProtoDef/schemas/utils.json').bitfield], + bitflags: [readBitflags, writeBitflags, sizeOfBitflags, require('../../ProtoDef/schemas/utils.json').bitflags], cstring: [readCString, writeCString, sizeOfCString, require('../../ProtoDef/schemas/utils.json').cstring], mapper: [readMapper, writeMapper, sizeOfMapper, require('../../ProtoDef/schemas/utils.json').mapper], ...require('./varint') @@ -217,3 +218,65 @@ function sizeOfCString (value) { const length = Buffer.byteLength(value, 'utf8') return length + 1 } + +function readBitflags (buffer, offset, { type, flags, shift, big }, rootNode) { + const { size, value } = this.read(buffer, offset, type, rootNode) + let f = {} + if (Array.isArray(flags)) { + for (const [k, v] of Object.entries(flags)) { + f[v] = big ? (1n << BigInt(k)) : (1 << k) + } + } else if (shift) { + for (const k in flags) { + f[k] = (big ? 1n : 1) << flags[k] + } + } else { + f = flags + } + const result = { _value: value } + for (const key in f) { + result[key] = (value & f[key]) === f[key] + } + return { value: result, size } +} + +function writeBitflags (value, buffer, offset, { type, flags, shift, big }, rootNode) { + let f = {} + if (Array.isArray(flags)) { + for (const [k, v] of Object.entries(flags)) { + f[v] = big ? (1n << BigInt(k)) : (1 << k) + } + } else if (shift) { + for (const k in flags) { + f[k] = (big ? 1n : 1) << flags[k] + } + } else { + f = flags + } + let val = value._value || (big ? 0n : 0) + for (const key in f) { + if (value[key]) val |= f[key] + } + return this.write(val, buffer, offset, type, rootNode) +} + +function sizeOfBitflags (value, { type, flags, shift, big }, rootNode) { + if (!value) throw new Error('Missing field') + let f = {} + if (Array.isArray(flags)) { + for (const [k, v] of Object.entries(flags)) { + f[v] = big ? (1n << BigInt(k)) : (1 << k) + } + } else if (shift) { + for (const k in flags) { + f[k] = (big ? 1n : 1) << flags[k] + } + } else { + f = flags + } + let mappedValue = value._value || (big ? 0n : 0) + for (const key in f) { + if (value[key]) mappedValue |= f[key] + } + return this.sizeOf(mappedValue, type, rootNode) +} From eb6cbb594bdd7f9f6b9d936ca29f5bbb8b428dc0 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Thu, 5 Dec 2024 07:54:16 +0000 Subject: [PATCH 06/12] logging fix --- example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.js b/example.js index 9730859..e9dc715 100644 --- a/example.js +++ b/example.js @@ -91,6 +91,6 @@ serializer.write({ serializer.pipe(parser) parser.on('data', function (chunk) { - console.log(chunk) + console.dir(chunk, { depth: null }) 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]) }) From ae2958cfcd83ac129c8c3052464cc8e7aaca769e Mon Sep 17 00:00:00 2001 From: extremeheat Date: Fri, 6 Dec 2024 01:36:07 -0500 Subject: [PATCH 07/12] fix BigInt handling for bitflags.shift --- src/datatypes/compiler-utils.js | 6 +++--- src/datatypes/utils.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/datatypes/compiler-utils.js b/src/datatypes/compiler-utils.js index d46cb89..2c3d5a8 100644 --- a/src/datatypes/compiler-utils.js +++ b/src/datatypes/compiler-utils.js @@ -66,7 +66,7 @@ module.exports = { fstr += '}' } else if (shift) { fstr = '{' - for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` @@ -145,7 +145,7 @@ return { value, size } fstr += '}' } else if (shift) { fstr = '{' - for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` @@ -197,7 +197,7 @@ return (ctx.${type})(val, buffer, offset) fstr += '}' } else if (shift) { fstr = '{' - for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` diff --git a/src/datatypes/utils.js b/src/datatypes/utils.js index ec82930..2f1722b 100644 --- a/src/datatypes/utils.js +++ b/src/datatypes/utils.js @@ -228,7 +228,7 @@ function readBitflags (buffer, offset, { type, flags, shift, big }, rootNode) { } } else if (shift) { for (const k in flags) { - f[k] = (big ? 1n : 1) << flags[k] + f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k]) } } else { f = flags @@ -248,7 +248,7 @@ function writeBitflags (value, buffer, offset, { type, flags, shift, big }, root } } else if (shift) { for (const k in flags) { - f[k] = (big ? 1n : 1) << flags[k] + f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k]) } } else { f = flags @@ -269,7 +269,7 @@ function sizeOfBitflags (value, { type, flags, shift, big }, rootNode) { } } else if (shift) { for (const k in flags) { - f[k] = (big ? 1n : 1) << flags[k] + f[k] = big ? (1n << BigInt(flags[k])) : (1 << flags[k]) } } else { f = flags From a2c683108ed176025236490ae95b959308b5f437 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Fri, 6 Dec 2024 01:42:49 -0500 Subject: [PATCH 08/12] run CI against protodef fork --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aef04dd..37990c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install + - run: mv ProtoDef ProtoDef-old && git clone -b patch-2 https://github.com/extremeheat/ProtoDef - run: npm test - run: npm run benchmark \ No newline at end of file From e17c3021223624865d1a8f1c9bc1a08802c3a90c Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sat, 7 Dec 2024 17:05:09 +0000 Subject: [PATCH 09/12] update test bigint handling --- src/datatypes/varint.js | 4 ++-- test/dataTypes/datatypes.js | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js index 4400b48..324a361 100644 --- a/src/datatypes/varint.js +++ b/src/datatypes/varint.js @@ -56,7 +56,7 @@ function readVarLong (buffer, offset) { let cursor = offset while (true) { - if (cursor >= buffer.length) throw new Error('Unexpected buffer end while reading VarLong') + if (cursor >= buffer.length) throw new PartialReadError('Unexpected buffer end while reading VarLong') const byte = buffer.readUInt8(cursor) result |= (BigInt(byte) & 0x7Fn) << shift // Add the bits, excluding the MSB cursor++ @@ -74,7 +74,7 @@ function readVarLong128 (buffer, offset) { let cursor = offset while (true) { - if (cursor >= buffer.length) throw new Error('Unexpected buffer end while reading VarLong') + if (cursor >= buffer.length) throw new PartialReadError('Unexpected buffer end while reading VarLong') const byte = buffer.readUInt8(cursor) result |= (BigInt(byte) & 0x7Fn) << shift // Add the bits, excluding the MSB cursor++ diff --git a/test/dataTypes/datatypes.js b/test/dataTypes/datatypes.js index 51bef45..35a50b9 100644 --- a/test/dataTypes/datatypes.js +++ b/test/dataTypes/datatypes.js @@ -13,7 +13,12 @@ function testValue (type, value, buffer) { }) it('reads', function () { const actualResult = proto.parsePacketBuffer(type, buffer) - if (value === null) { assert.ok(actualResult.data === undefined) } else { expect(actualResult.data).to.deep.equal(value) } + if (typeof actualResult.data === 'bigint') value = BigInt(value) + if (value === null) { + assert.ok(actualResult.data === undefined) + } else { + expect(actualResult.data).to.deep.equal(value) + } expect(actualResult.metadata.size).to.deep.equal(buffer.length) }) it('writes (compiled)', function () { From 9962f3ae5f6b6b9eed8c31370e07f8d256ba29d1 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 8 Dec 2024 06:48:44 +0000 Subject: [PATCH 10/12] update ProtoDef submodule --- ProtoDef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtoDef b/ProtoDef index 2c023c4..8e07785 160000 --- a/ProtoDef +++ b/ProtoDef @@ -1 +1 @@ -Subproject commit 2c023c4f5ee141a5ff88aa002f7743f78a9b99aa +Subproject commit 8e07785e94626882fa3333184bb366e3f6625356 From 3e43598d544b9f52270b694f8a4d863c124d0967 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 8 Dec 2024 06:56:51 +0000 Subject: [PATCH 11/12] update submod --- .github/workflows/ci.yml | 1 - src/datatypes/varint.js | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37990c7..aef04dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,5 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install - - run: mv ProtoDef ProtoDef-old && git clone -b patch-2 https://github.com/extremeheat/ProtoDef - run: npm test - run: npm run benchmark \ No newline at end of file diff --git a/src/datatypes/varint.js b/src/datatypes/varint.js index 324a361..4abf8b6 100644 --- a/src/datatypes/varint.js +++ b/src/datatypes/varint.js @@ -1,11 +1,11 @@ const { PartialReadError } = require('../utils') module.exports = { - varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/utils.json').varint], - varint64: [readVarLong, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/utils.json').varint], - varint128: [readVarLong128, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/utils.json').varint], - zigzag32: [readSignedVarInt, writeSignedVarInt, sizeOfSignedVarInt, require('../../ProtoDef/schemas/utils.json').varint], - zigzag64: [readSignedVarLong, writeSignedVarLong, sizeOfSignedVarLong, require('../../ProtoDef/schemas/utils.json').varint] + varint: [readVarInt, writeVarInt, sizeOfVarInt, require('../../ProtoDef/schemas/numeric.json').varint], + varint64: [readVarLong, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/numeric.json').varint64], + varint128: [readVarLong128, writeVarLong, sizeOfVarLong, require('../../ProtoDef/schemas/numeric.json').varint128], + zigzag32: [readSignedVarInt, writeSignedVarInt, sizeOfSignedVarInt, require('../../ProtoDef/schemas/numeric.json').zigzag32], + zigzag64: [readSignedVarLong, writeSignedVarLong, sizeOfSignedVarLong, require('../../ProtoDef/schemas/numeric.json').zigzag64] } // u32 From 587134daeaa94959e5d889924163b2ccbdb86486 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 8 Dec 2024 06:49:57 -0500 Subject: [PATCH 12/12] Update package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 33258c9..25007bf 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "types": "index.d.ts", "author": "roblabla ", "scripts": { - "prepare": "git submodule update --init", "lint": "standard", "fix": "standard --fix", "unit-test": "mocha --recursive --reporter spec",