diff --git a/types/array/array.ts b/types/array/array.ts index a5889c7..7fb03b3 100644 --- a/types/array/array.ts +++ b/types/array/array.ts @@ -57,6 +57,6 @@ export class ArrayType implements SizedType, ViewableType { }); } - return array as T[]; + return array; } } diff --git a/types/string/fixed_length.ts b/types/string/fixed_length.ts index 1c2e4cf..035e743 100644 --- a/types/string/fixed_length.ts +++ b/types/string/fixed_length.ts @@ -12,14 +12,22 @@ export class FixedLengthString implements SizedType { read(dataView: DataView, byteOffset = 0): string { return decoder.decode( - new Uint8Array(dataView.buffer, byteOffset, byteOffset + this.byteLength), + new Uint8Array( + dataView.buffer, + dataView.byteOffset + byteOffset, + this.byteLength - byteOffset, + ), ); } write(value: string, dataView: DataView, byteOffset = 0) { encoder.encodeInto( value, - new Uint8Array(dataView.buffer, byteOffset, this.byteLength), + new Uint8Array( + dataView.buffer, + dataView.byteOffset + byteOffset, + this.byteLength - byteOffset, + ), ); } } diff --git a/types/varint/leb128.ts b/types/varint/leb128.ts new file mode 100644 index 0000000..3a93921 --- /dev/null +++ b/types/varint/leb128.ts @@ -0,0 +1,40 @@ +import { Type } from "../types.ts"; + +const SEGMENT_BITS = 0x7F; +const CONTINUE_BIT = 0x80; + +export class I32LEB128 implements Type { + read(dataView: DataView, byteOffset = 0): number { + let value = 0, position = 0; + while (true) { + const currentByte = dataView.getInt8(byteOffset); + value |= (currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) === 0) break; + + position += 7; + byteOffset++; + + if (position >= 32) { + throw new TypeError("I32LEB128 cannot exceed 32 bits in length"); + } + } + + return value; + } + + write(value: number, dataView: DataView, byteOffset = 0): void { + while (true) { + if ((value & ~SEGMENT_BITS) === 0) { + dataView.setInt8(byteOffset, value); + return; + } + + dataView.setInt8(byteOffset, value & SEGMENT_BITS | CONTINUE_BIT); + byteOffset++; + value >>>= 7; + } + } +} + +export const i32leb128 = new I32LEB128(); diff --git a/types/varint/leb128_test.ts b/types/varint/leb128_test.ts new file mode 100644 index 0000000..35b455a --- /dev/null +++ b/types/varint/leb128_test.ts @@ -0,0 +1,94 @@ +import { I32LEB128 } from "./mod.ts"; +import { + assertEquals, + assertThrows, +} from "https://deno.land/std@0.183.0/testing/asserts.ts"; + +Deno.test({ + name: "Read Positive varint", + fn: () => { + let data = Uint8Array.of(127); + let result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, 127); + + data = Uint8Array.of(128, 1); + result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, 128); + + data = Uint8Array.of(221, 199, 1); + result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, 25565); + + data = Uint8Array.of(255, 255, 255, 255, 7); + result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, 2147483647); + }, +}); + +Deno.test({ + name: "Read Negative varint", + fn: () => { + let data = Uint8Array.of(255, 255, 255, 255, 15); + let result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, -1); + + data = Uint8Array.of(128, 128, 128, 128, 8); + result = new I32LEB128().read(new DataView(data.buffer)); + assertEquals(result, -2147483648); + }, +}); + +Deno.test({ + name: "Read Bad varint", + fn: () => { + const data = Uint8Array.of(255, 255, 255, 255, 255, 15); + assertThrows(() => new I32LEB128().read(new DataView(data.buffer))); + }, +}); + +Deno.test({ + name: "Write Positive varint", + fn: () => { + let data = new Uint8Array(1); + new I32LEB128().write(127, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(127)); + + data = new Uint8Array(2); + new I32LEB128().write(128, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(128, 1)); + + data = new Uint8Array(3); + new I32LEB128().write(25565, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(221, 199, 1)); + + data = new Uint8Array(5); + new I32LEB128().write(2147483647, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(255, 255, 255, 255, 7)); + }, +}); + +Deno.test({ + name: "Write Negative varint", + fn: () => { + let data = new Uint8Array(5); + new I32LEB128().write(-1, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(255, 255, 255, 255, 15)); + + data = new Uint8Array(5); + new I32LEB128().write(-2147483648, new DataView(data.buffer)); + assertEquals(data, Uint8Array.of(128, 128, 128, 128, 8)); + }, +}); + +Deno.test({ + name: "Write & read i32 MAX", + fn: () => { + const value = 2_147_483_647; + const decoder = new I32LEB128(); + const bytes = new Uint8Array(5); + const dt = new DataView(bytes.buffer); + decoder.write(value, dt, 0); + const decodedValue = decoder.read(dt); + assertEquals(decodedValue, value); + }, +}); diff --git a/types/varint/mod.ts b/types/varint/mod.ts new file mode 100644 index 0000000..53fea10 --- /dev/null +++ b/types/varint/mod.ts @@ -0,0 +1 @@ +export * from "./leb128.ts";