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

length_metadata only works for 32-bit values #22

Open
Alexthemediocre opened this issue Nov 29, 2024 · 0 comments
Open

length_metadata only works for 32-bit values #22

Alexthemediocre opened this issue Nov 29, 2024 · 0 comments

Comments

@Alexthemediocre
Copy link

Alexthemediocre commented Nov 29, 2024

length_metadata only returns the correct result when len is between -1 << 31 and ~(-1 << 31) (equivalent to Number((1n << 31n) - 1n)), inclusive - i.e., when len fits into a signed 32-bit integer (i32).

As far as I can tell, when applying bitwise operations to an integer Number outside of the range of a signed 32-bit integer, JavaScript first coerces it to a 32-bit signed integer by applying operations equivalent to the following:

  1. Represent the Number as a 64-bit signed integer. (BigInt(n))
  2. Take only the 32 least significant bits (the 4 least significant bytes), discarding the 32 most significant bits. (BigInt(n) & 0xff_ff_ff_ffn)
  3. Intepret these 32 bits as a signed 32-bit integer (i32). (~~Number(BigInt(n) & 0xff_ff_ff_ffn))
    (So for a Number n, ~~n should be equal to ~~Number(BigInt(n) & 0xff_ff_ff_ffn).)

Thus, when passing a len that is not an i32 to length_metadata, some of the bits may be discarded, giving the wrong result.
No matter what value of len is passed, length_metadata(len)[4], length_metadata(len)[5], length_metadata(len)[6], and length_metadata(len)[7] will always be equal to 0.

However, the function seems to be broken in another way for inputs outside of the 32-bit range - I am not sure of the exact source this code was taken from, but I would think lines 17 through 26 of index.ts would be the following:

  return [
    (len & 0x00000000000000ff) >> 0,
    (len & 0x000000000000ff00) >> 8,
    (len & 0x0000000000ff0000) >> 16,
    (len & 0x00000000ff000000) >> 24,
    (len & 0x000000ff00000000) >> 32,
    (len & 0x0000ff0000000000) >> 40,
    (len & 0x00ff000000000000) >> 48,
    (len & 0xff00000000000000) >> 56,
  ];

Or adapted to use BigInts to properly handle integers outside the 32-bit range:

  if (!Number.isSafeInteger(len)) throw RangeError("len is not a safe integer - it may be too large");
  let big = BigInt(len);
  return [
    (big & 0x00000000000000ffn) >> 0n,
    (big & 0x000000000000ff00n) >> 8n,
    (big & 0x0000000000ff0000n) >> 16n,
    (big & 0x00000000ff000000n) >> 24n,
    (big & 0x000000ff00000000n) >> 32n,
    (big & 0x0000ff0000000000n) >> 40n,
    (big & 0x00ff000000000000n) >> 48n,
    (big & 0xff00000000000000n) >> 56n,
  ].map(n => Number(n));

The above should be equivalent to the following:

if (!Number.isSafeInteger(len)) throw RangeError("len is not a safe integer - it may be too large");
let buf = new ArrayBuffer(8);
let view = new DataView(buf);
view.setBigUint64(0, BigInt(len), true); // write using little-endian (default)
return [...new Uint8Array(buf)];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant