From dc59df0b648b74794b5ca70478cb8cdeb02c7aea Mon Sep 17 00:00:00 2001 From: Skye <63878374+MierenManz@users.noreply.github.com> Date: Fri, 21 Apr 2023 10:42:50 +0200 Subject: [PATCH] feat: add `I32LEB128` (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Elias Sjögreen --- types/varint/leb128.ts | 40 ++++++++++++++++ types/varint/leb128_test.ts | 94 +++++++++++++++++++++++++++++++++++++ types/varint/mod.ts | 1 + 3 files changed, 135 insertions(+) create mode 100644 types/varint/leb128.ts create mode 100644 types/varint/leb128_test.ts create mode 100644 types/varint/mod.ts 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";