Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React-native bigint64 deserialization problem - refactoring/bigint64le #16

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
57 changes: 33 additions & 24 deletions src/readBig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Buffer } from 'buffer'

// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L758
const ERR_BUFFER_OUT_OF_BOUNDS = () => new Error('Attempt to access memory outside buffer bounds')

Expand Down Expand Up @@ -28,30 +26,41 @@ function boundsError(value: number, length: number) {
throw ERR_OUT_OF_RANGE('offset', `>= 0 and <= ${length}`, value)
}

// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L129-L145
export function readBigInt64LE(buffer: Buffer, offset = 0): bigint {
validateNumber(offset, 'offset')
const first = buffer[offset]
const last = buffer[offset + 7]
if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8)
// tslint:disable-next-line:no-bitwise
const val = buffer[offset + 4] + buffer[offset + 5] * 2 ** 8 + buffer[offset + 6] * 2 ** 16 + (last << 24) // Overflow
return (
(BigInt(val) << BigInt(32)) + // tslint:disable-line:no-bitwise
BigInt(first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24)
)
}

// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L89-L107
// This function works with react-native >= 0.66.1
export function readBigUInt64LE(buffer: Buffer, offset = 0): bigint {
validateNumber(offset, 'offset')
const first = buffer[offset]
const last = buffer[offset + 7]
if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8)
buffer = buffer.slice(offset)

const bufferLenght = Math.min(buffer.length, 8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in the name. also, i believe this code expects buffer to have 8 bytes storing the number, so you can get rid of the min here and also the Number.MAX_VALUE / maxIndex check below.


let tot: number = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code loses precision -- this is a floating point number, which has fewer bits of precision than a 64 bit integer (since some of its 64 bits are allocated to the exponent). I think if you make this a BigInt right off the bat, it should be fine.

const maxIndex = Math.log2(Number.MAX_VALUE)
for(let index = 0; index < bufferLenght; index++) {
const value = buffer[index]
const exponent = 8*index

if(exponent > maxIndex)
throw new Error("out of range")

const lo = first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24
const addend = value * Math.pow(2, exponent)

tot += addend

const hi = buffer[++offset] + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + last * 2 ** 24
// console.log("index: " + index + ", buffer value: " + value + ", decimal value: " + addend + ", tot: " + tot)
}

return BigInt(lo) + (BigInt(hi) << BigInt(32)) // tslint:disable-line:no-bitwise
return BigInt(tot)
}

// This function works with react-native >= 0.66.1
export function readBigInt64LE(buffer: Buffer, offset = 0): bigint {
const resultUnsigned = readBigUInt64LE(buffer, offset)

const FFFFFFFFFFFFFFFF = BigInt(2**64 - 1);

if(buffer.length >= 8) {
if(buffer[7] >= 128)
return resultUnsigned - FFFFFFFFFFFFFFFF;
}

return resultUnsigned
}