From 8282f66159abece1e31ce8e6753e8baf8a8663a0 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 15 Oct 2026 08:42:34 -0400 Subject: [PATCH] fix: store LongBits words unsigned LongBits stores lo as lo | 0. For values with hi == 0 and lo in [2^31, 2^32) that is negative, so writeVarint64 writes one byte into the 5 bytes length() reserved and leaves 4 bytes of the allocUnsafe slab unwritten. uint64 and int64 are affected. sint64 and fixed64 are not. protobufjs stores both words with >>> 0. The port in d101804 changed them to | 0. Restore >>> 0 and add a regression test at the window edges. --- .../protons-runtime/src/utils/longbits.ts | 4 ++-- packages/protons/test/longbits.spec.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 packages/protons/test/longbits.spec.ts diff --git a/packages/protons-runtime/src/utils/longbits.ts b/packages/protons-runtime/src/utils/longbits.ts index 12cc5d43..31b120f2 100644 --- a/packages/protons-runtime/src/utils/longbits.ts +++ b/packages/protons-runtime/src/utils/longbits.ts @@ -22,12 +22,12 @@ export class LongBits { /** * Low bits */ - this.lo = lo | 0 + this.lo = lo >>> 0 /** * High bits */ - this.hi = hi | 0 + this.hi = hi >>> 0 } /** diff --git a/packages/protons/test/longbits.spec.ts b/packages/protons/test/longbits.spec.ts new file mode 100644 index 00000000..26b78f86 --- /dev/null +++ b/packages/protons/test/longbits.spec.ts @@ -0,0 +1,24 @@ +import { expect } from 'aegir/chai' +import { writer } from 'protons-runtime' + +// low word in [2^31, 2^32) used to be stored signed and written as one byte +describe('64-bit varints', () => { + const cases: Array<[bigint, number[]]> = [ + [2147483647n, [0xff, 0xff, 0xff, 0xff, 0x07]], + [2147483648n, [0x80, 0x80, 0x80, 0x80, 0x08]], + [3221225472n, [0x80, 0x80, 0x80, 0x80, 0x0c]], + [4294967295n, [0xff, 0xff, 0xff, 0xff, 0x0f]], + [4294967296n, [0x80, 0x80, 0x80, 0x80, 0x10]] + ] + + for (const method of ['uint64', 'int64'] as const) { + for (const [value, bytes] of cases) { + it(`should encode ${method} ${value}`, () => { + const w = writer() + w[method](value) + + expect(w.finish()).to.equalBytes(bytes) + }) + } + } +})