diff --git a/src/cjs/index.cjs b/src/cjs/index.cjs index ba01ad8..eee1b28 100644 --- a/src/cjs/index.cjs +++ b/src/cjs/index.cjs @@ -33,6 +33,17 @@ function compare(v1, v2) { return Buffer.from(v1).compare(Buffer.from(v2)); } exports.compare = compare; +function checkReadOffset(buffer, offset, byteLength) { + // Direct Uint8Array indexing does not validate these values, while Node.js + // Buffer throws. + if (!Number.isInteger(offset) || + !Number.isInteger(byteLength) || + offset < 0 || + byteLength < 0 || + offset + byteLength > buffer.length) { + throw new Error("Offset is outside the bounds of Uint8Array"); + } +} function writeUInt8(buffer, offset, value) { if (offset + 1 > buffer.length) { throw new Error("Offset is outside the bounds of Uint8Array"); @@ -95,53 +106,69 @@ function writeUInt64(buffer, offset, value, littleEndian) { } exports.writeUInt64 = writeUInt64; function readUInt8(buffer, offset) { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } - const buf = Buffer.from(buffer); - return buf.readUInt8(offset); + checkReadOffset(buffer, offset, 1); + return buffer[offset]; } exports.readUInt8 = readUInt8; function readUInt16(buffer, offset, littleEndian) { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); - if (littleEndian === "LE") { - return buf.readUInt16LE(offset); - } - else { - return buf.readUInt16BE(offset); - } + // Match Node.js Buffer's implementation by expressing each byte's + // positional value. + return littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; } exports.readUInt16 = readUInt16; function readUInt32(buffer, offset, littleEndian) { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); - if (littleEndian === "LE") { - return buf.readUInt32LE(offset); - } - else { - return buf.readUInt32BE(offset); - } + // Multiplication preserves the unsigned range; bitwise operators coerce + // values to signed int32. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24 + : buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } exports.readUInt32 = readUInt32; function readUInt64(buffer, offset, littleEndian) { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); + let lo; + let hi; + // As in Node.js Buffer's implementation, compose exact 32-bit halves before + // converting to BigInt. This needs only two BigInt conversions instead of one + // conversion and shift per byte. if (littleEndian === "LE") { - return buf.readBigUInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + buffer[offset + 7] * 2 ** 24; } else { - return buf.readBigUInt64BE(offset); - } + hi = + buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; + } + return (BigInt(hi) << 32n) + BigInt(lo); } exports.readUInt64 = readUInt64; function writeInt8(buffer, offset, value) { @@ -206,49 +233,72 @@ function writeInt64(buffer, offset, value, littleEndian) { } exports.writeInt64 = writeInt64; function readInt8(buffer, offset) { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } - const buf = Buffer.from(buffer); - return buf.readInt8(offset); + checkReadOffset(buffer, offset, 1); + const val = buffer[offset]; + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x80 ? val : val - 0x100; } exports.readInt8 = readInt8; function readInt16(buffer, offset, littleEndian) { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase(); - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt16LE(offset); - } - else { - return Buffer.from(buffer).readInt16BE(offset); - } + const val = littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x8000 ? val : val - 0x10000; } exports.readInt16 = readInt16; function readInt32(buffer, offset, littleEndian) { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase(); - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt32LE(offset); - } - else { - return Buffer.from(buffer).readInt32BE(offset); - } + // Node.js Buffer's implementation shifts only the most-significant byte so + // JavaScript sign-extends it. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + (buffer[offset + 3] << 24) + : (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } exports.readInt32 = readInt32; function readInt64(buffer, offset, littleEndian) { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase(); + let lo; + let hi; + // Node.js Buffer's implementation makes hi a signed int32. Combining signed + // hi with unsigned lo produces the 64-bit two's-complement value without an + // extra BigInt correction. if (littleEndian === "LE") { - return Buffer.from(buffer).readBigInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + (buffer[offset + 7] << 24); } else { - return Buffer.from(buffer).readBigInt64BE(offset); - } + hi = + (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; + } + return (BigInt(hi) << 32n) + BigInt(lo); } exports.readInt64 = readInt64; diff --git a/src/mjs/index.js b/src/mjs/index.js index 3e3a85b..c417540 100644 --- a/src/mjs/index.js +++ b/src/mjs/index.js @@ -22,6 +22,17 @@ export function fromBase64(base64) { export function compare(v1, v2) { return Buffer.from(v1).compare(Buffer.from(v2)); } +function checkReadOffset(buffer, offset, byteLength) { + // Direct Uint8Array indexing does not validate these values, while Node.js + // Buffer throws. + if (!Number.isInteger(offset) || + !Number.isInteger(byteLength) || + offset < 0 || + byteLength < 0 || + offset + byteLength > buffer.length) { + throw new Error("Offset is outside the bounds of Uint8Array"); + } +} export function writeUInt8(buffer, offset, value) { if (offset + 1 > buffer.length) { throw new Error("Offset is outside the bounds of Uint8Array"); @@ -80,50 +91,66 @@ export function writeUInt64(buffer, offset, value, littleEndian) { return offset + 8; } export function readUInt8(buffer, offset) { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } - const buf = Buffer.from(buffer); - return buf.readUInt8(offset); + checkReadOffset(buffer, offset, 1); + return buffer[offset]; } export function readUInt16(buffer, offset, littleEndian) { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); - if (littleEndian === "LE") { - return buf.readUInt16LE(offset); - } - else { - return buf.readUInt16BE(offset); - } + // Match Node.js Buffer's implementation by expressing each byte's + // positional value. + return littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; } export function readUInt32(buffer, offset, littleEndian) { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); - if (littleEndian === "LE") { - return buf.readUInt32LE(offset); - } - else { - return buf.readUInt32BE(offset); - } + // Multiplication preserves the unsigned range; bitwise operators coerce + // values to signed int32. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24 + : buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } export function readUInt64(buffer, offset, littleEndian) { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase(); - const buf = Buffer.from(buffer); + let lo; + let hi; + // As in Node.js Buffer's implementation, compose exact 32-bit halves before + // converting to BigInt. This needs only two BigInt conversions instead of one + // conversion and shift per byte. if (littleEndian === "LE") { - return buf.readBigUInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + buffer[offset + 7] * 2 ** 24; } else { - return buf.readBigUInt64BE(offset); - } + hi = + buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; + } + return (BigInt(hi) << 32n) + BigInt(lo); } export function writeInt8(buffer, offset, value) { if (offset + 1 > buffer.length) { @@ -183,45 +210,68 @@ export function writeInt64(buffer, offset, value, littleEndian) { return offset + 8; } export function readInt8(buffer, offset) { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } - const buf = Buffer.from(buffer); - return buf.readInt8(offset); + checkReadOffset(buffer, offset, 1); + const val = buffer[offset]; + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x80 ? val : val - 0x100; } export function readInt16(buffer, offset, littleEndian) { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase(); - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt16LE(offset); - } - else { - return Buffer.from(buffer).readInt16BE(offset); - } + const val = littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x8000 ? val : val - 0x10000; } export function readInt32(buffer, offset, littleEndian) { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase(); - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt32LE(offset); - } - else { - return Buffer.from(buffer).readInt32BE(offset); - } + // Node.js Buffer's implementation shifts only the most-significant byte so + // JavaScript sign-extends it. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + (buffer[offset + 3] << 24) + : (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } export function readInt64(buffer, offset, littleEndian) { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase(); + let lo; + let hi; + // Node.js Buffer's implementation makes hi a signed int32. Combining signed + // hi with unsigned lo produces the 64-bit two's-complement value without an + // extra BigInt correction. if (littleEndian === "LE") { - return Buffer.from(buffer).readBigInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + (buffer[offset + 7] << 24); } else { - return Buffer.from(buffer).readBigInt64BE(offset); - } + hi = + (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; + } + return (BigInt(hi) << 32n) + BigInt(lo); } diff --git a/ts_src/index.ts b/ts_src/index.ts index 50f6924..5a0c9ce 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -33,6 +33,24 @@ export function compare(v1: Uint8Array, v2: Uint8Array): CompareResult { export type endian = "LE" | "BE" | "le" | "be"; +function checkReadOffset( + buffer: Uint8Array, + offset: number, + byteLength: number +): void { + // Direct Uint8Array indexing does not validate these values, while Node.js + // Buffer throws. + if ( + !Number.isInteger(offset) || + !Number.isInteger(byteLength) || + offset < 0 || + byteLength < 0 || + offset + byteLength > buffer.length + ) { + throw new Error("Offset is outside the bounds of Uint8Array"); + } +} + export function writeUInt8( buffer: Uint8Array, offset: number, @@ -128,12 +146,9 @@ export function writeUInt64( } export function readUInt8(buffer: Uint8Array, offset: number): number { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 1); - const buf = Buffer.from(buffer); - return buf.readUInt8(offset); + return buffer[offset]; } export function readUInt16( @@ -141,19 +156,15 @@ export function readUInt16( offset: number, littleEndian: endian ): number { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase() as endian; - const buf = Buffer.from(buffer); - - if (littleEndian === "LE") { - return buf.readUInt16LE(offset); - } else { - return buf.readUInt16BE(offset); - } + // Match Node.js Buffer's implementation by expressing each byte's + // positional value. + return littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; } export function readUInt32( @@ -161,19 +172,21 @@ export function readUInt32( offset: number, littleEndian: endian ): number { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase() as endian; - const buf = Buffer.from(buffer); - - if (littleEndian === "LE") { - return buf.readUInt32LE(offset); - } else { - return buf.readUInt32BE(offset); - } + // Multiplication preserves the unsigned range; bitwise operators coerce + // values to signed int32. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24 + : buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } export function readUInt64( @@ -181,19 +194,41 @@ export function readUInt64( offset: number, littleEndian: endian ): bigint { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase() as endian; - const buf = Buffer.from(buffer); + let lo: number; + let hi: number; + // As in Node.js Buffer's implementation, compose exact 32-bit halves before + // converting to BigInt. This needs only two BigInt conversions instead of one + // conversion and shift per byte. if (littleEndian === "LE") { - return buf.readBigUInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + buffer[offset + 7] * 2 ** 24; } else { - return buf.readBigUInt64BE(offset); + hi = + buffer[offset] * 2 ** 24 + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; } + + return (BigInt(hi) << 32n) + BigInt(lo); } export function writeInt8( @@ -285,12 +320,13 @@ export function writeInt64( } export function readInt8(buffer: Uint8Array, offset: number): number { - if (offset + 1 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 1); + + const val = buffer[offset]; - const buf = Buffer.from(buffer); - return buf.readInt8(offset); + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x80 ? val : val - 0x100; } export function readInt16( @@ -298,17 +334,18 @@ export function readInt16( offset: number, littleEndian: endian ): number { - if (offset + 2 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 2); littleEndian = littleEndian.toUpperCase() as endian; - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt16LE(offset); - } else { - return Buffer.from(buffer).readInt16BE(offset); - } + const val = + littleEndian === "LE" + ? buffer[offset] + buffer[offset + 1] * 2 ** 8 + : buffer[offset] * 2 ** 8 + buffer[offset + 1]; + + // Convert from two's complement explicitly instead of using Node.js + // Buffer's branchless sign-extension expression. + return val < 0x8000 ? val : val - 0x10000; } export function readInt32( @@ -316,17 +353,21 @@ export function readInt32( offset: number, littleEndian: endian ): number { - if (offset + 4 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 4); littleEndian = littleEndian.toUpperCase() as endian; - if (littleEndian === "LE") { - return Buffer.from(buffer).readInt32LE(offset); - } else { - return Buffer.from(buffer).readInt32BE(offset); - } + // Node.js Buffer's implementation shifts only the most-significant byte so + // JavaScript sign-extends it. + return littleEndian === "LE" + ? buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + (buffer[offset + 3] << 24) + : (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; } export function readInt64( @@ -334,15 +375,39 @@ export function readInt64( offset: number, littleEndian: endian ): bigint { - if (offset + 8 > buffer.length) { - throw new Error("Offset is outside the bounds of Uint8Array"); - } + checkReadOffset(buffer, offset, 8); littleEndian = littleEndian.toUpperCase() as endian; + let lo: number; + let hi: number; + + // Node.js Buffer's implementation makes hi a signed int32. Combining signed + // hi with unsigned lo produces the 64-bit two's-complement value without an + // extra BigInt correction. if (littleEndian === "LE") { - return Buffer.from(buffer).readBigInt64LE(offset); + lo = + buffer[offset] + + buffer[offset + 1] * 2 ** 8 + + buffer[offset + 2] * 2 ** 16 + + buffer[offset + 3] * 2 ** 24; + hi = + buffer[offset + 4] + + buffer[offset + 5] * 2 ** 8 + + buffer[offset + 6] * 2 ** 16 + + (buffer[offset + 7] << 24); } else { - return Buffer.from(buffer).readBigInt64BE(offset); + hi = + (buffer[offset] << 24) + + buffer[offset + 1] * 2 ** 16 + + buffer[offset + 2] * 2 ** 8 + + buffer[offset + 3]; + lo = + buffer[offset + 4] * 2 ** 24 + + buffer[offset + 5] * 2 ** 16 + + buffer[offset + 6] * 2 ** 8 + + buffer[offset + 7]; } + + return (BigInt(hi) << 32n) + BigInt(lo); } diff --git a/ts_src/tests.spec.ts b/ts_src/tests.spec.ts index b5686a3..23a3c0e 100644 --- a/ts_src/tests.spec.ts +++ b/ts_src/tests.spec.ts @@ -392,6 +392,59 @@ describe(`Uint8Array tools`, () => { ); }); + it("should read integers from Buffer and Uint8Array views", () => { + const bytes = [0xaa, 0x80, 1, 2, 3, 4, 5, 6, 7, 0xbb]; + const expected = Buffer.from(bytes.slice(1, 9)); + const views: Uint8Array[] = [ + Uint8Array.from(bytes).subarray(1, 9), + Buffer.from(bytes).subarray(1, 9), + ]; + + for (const view of views) { + expect(tools.readUInt8(view, 0)).toEqual(expected.readUInt8(0)); + expect(tools.readInt8(view, 0)).toEqual(expected.readInt8(0)); + + expect(tools.readUInt16(view, 0, "LE")).toEqual( + expected.readUInt16LE(0) + ); + expect(tools.readUInt16(view, 0, "BE")).toEqual( + expected.readUInt16BE(0) + ); + expect(tools.readInt16(view, 0, "LE")).toEqual( + expected.readInt16LE(0) + ); + expect(tools.readInt16(view, 0, "BE")).toEqual( + expected.readInt16BE(0) + ); + + expect(tools.readUInt32(view, 0, "LE")).toEqual( + expected.readUInt32LE(0) + ); + expect(tools.readUInt32(view, 0, "BE")).toEqual( + expected.readUInt32BE(0) + ); + expect(tools.readInt32(view, 0, "LE")).toEqual( + expected.readInt32LE(0) + ); + expect(tools.readInt32(view, 0, "BE")).toEqual( + expected.readInt32BE(0) + ); + + expect(tools.readUInt64(view, 0, "LE")).toEqual( + expected.readBigUInt64LE(0) + ); + expect(tools.readUInt64(view, 0, "BE")).toEqual( + expected.readBigUInt64BE(0) + ); + expect(tools.readInt64(view, 0, "LE")).toEqual( + expected.readBigInt64LE(0) + ); + expect(tools.readInt64(view, 0, "BE")).toEqual( + expected.readBigInt64BE(0) + ); + } + }); + it("should throw an error if the offset is out of bounds", () => { const arr = new Uint8Array(10); @@ -626,4 +679,27 @@ describe(`Uint8Array tools`, () => { }); }); } + + it("node reads should reject invalid numeric offsets", () => { + const bytes = new Uint8Array(8); + + for (const offset of [-1, 0.5, NaN, Infinity]) { + const reads = [ + () => node.readUInt8(bytes, offset), + () => node.readUInt16(bytes, offset, "LE"), + () => node.readUInt32(bytes, offset, "LE"), + () => node.readUInt64(bytes, offset, "LE"), + () => node.readInt8(bytes, offset), + () => node.readInt16(bytes, offset, "LE"), + () => node.readInt32(bytes, offset, "LE"), + () => node.readInt64(bytes, offset, "LE"), + ]; + + for (const read of reads) { + expect(read).toThrowError( + new Error("Offset is outside the bounds of Uint8Array") + ); + } + } + }); }); diff --git a/tsconfig-base.json b/tsconfig-base.json index b5b9408..758c1f1 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -7,7 +7,8 @@ "noUnusedParameters": true, "noPropertyAccessFromIndexSignature": true, "moduleResolution": "node", - "esModuleInterop": true + "esModuleInterop": true, + "types": ["node", "jest"] }, "include": ["ts_src/*.ts"], "exclude": [