From 8aaa14f70c4e95b34371ca992a61e397f760780a Mon Sep 17 00:00:00 2001 From: eliassjogreen Date: Wed, 1 Mar 2023 21:44:11 +0100 Subject: [PATCH] feat: null terminated strings --- types/string/fixed_length.ts | 25 +++++++++++++++ types/string/mod.ts | 27 ++-------------- types/string/null_terminated.ts | 37 ++++++++++++++++++++++ types/string/null_terminated_test.ts | 47 ++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 types/string/fixed_length.ts create mode 100644 types/string/null_terminated.ts create mode 100644 types/string/null_terminated_test.ts diff --git a/types/string/fixed_length.ts b/types/string/fixed_length.ts new file mode 100644 index 0000000..1c2e4cf --- /dev/null +++ b/types/string/fixed_length.ts @@ -0,0 +1,25 @@ +import { SizedType } from "../types.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +export class FixedLengthString implements SizedType { + byteLength: number; + + constructor(byteLength: number) { + this.byteLength = byteLength; + } + + read(dataView: DataView, byteOffset = 0): string { + return decoder.decode( + new Uint8Array(dataView.buffer, byteOffset, byteOffset + this.byteLength), + ); + } + + write(value: string, dataView: DataView, byteOffset = 0) { + encoder.encodeInto( + value, + new Uint8Array(dataView.buffer, byteOffset, this.byteLength), + ); + } +} diff --git a/types/string/mod.ts b/types/string/mod.ts index 576a9c8..d229a08 100644 --- a/types/string/mod.ts +++ b/types/string/mod.ts @@ -1,25 +1,2 @@ -import { SizedType } from "../types.ts"; - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - -export class FixedUTF8String implements SizedType { - byteLength: number; - - constructor(byteLength: number) { - this.byteLength = byteLength; - } - - read(dataView: DataView, byteOffset = 0): string { - return decoder.decode( - new Uint8Array(dataView.buffer, byteOffset, byteOffset + this.byteLength), - ); - } - - write(value: string, dataView: DataView, byteOffset = 0) { - encoder.encodeInto( - value, - new Uint8Array(dataView.buffer, byteOffset, this.byteLength), - ); - } -} +export * from "./fixed_length.ts"; +export * from "./null_terminated.ts"; diff --git a/types/string/null_terminated.ts b/types/string/null_terminated.ts new file mode 100644 index 0000000..953951f --- /dev/null +++ b/types/string/null_terminated.ts @@ -0,0 +1,37 @@ +import { Type } from "../types.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +export class NullTerminatedString implements Type { + read(dataView: DataView, byteOffset = 0): string { + let endByteLength; + + for (let i = byteOffset; i < dataView.byteLength; i++) { + if (dataView.getUint8(i) === 0) { + endByteLength = i - byteOffset; + break; + } + } + + if (endByteLength == null) { + throw new TypeError( + "Did not encounter null terminator when attempting to read null terminated string", + ); + } + + return decoder.decode( + new Uint8Array(dataView.buffer, byteOffset, endByteLength), + ); + } + + write(value: string, dataView: DataView, byteOffset = 0) { + value += "\0"; + encoder.encodeInto( + value, + new Uint8Array(dataView.buffer, byteOffset, value.length), + ); + } +} + +export const nullTerminatedString = new NullTerminatedString(); diff --git a/types/string/null_terminated_test.ts b/types/string/null_terminated_test.ts new file mode 100644 index 0000000..88024df --- /dev/null +++ b/types/string/null_terminated_test.ts @@ -0,0 +1,47 @@ +import { + assertEquals, + assertThrows, +} from "https://deno.land/std@0.178.0/testing/asserts.ts"; +import { nullTerminatedString } from "./null_terminated.ts"; + +const encoder = new TextEncoder(); + +Deno.test("NullTerminatedString", async ({ step }) => { + await step("read", () => { + assertEquals( + nullTerminatedString.read( + new DataView(encoder.encode("Hello world!\0").buffer), + ), + "Hello world!", + ); + + assertEquals( + nullTerminatedString.read( + new DataView(encoder.encode("Hello\0world!\0").buffer), + ), + "Hello", + ); + + assertEquals( + nullTerminatedString.read( + new DataView(encoder.encode("Hello\0world!\0").buffer), + 6, + ), + "world!", + ); + + assertThrows( + () => + nullTerminatedString.read( + new DataView(encoder.encode("Hello world!").buffer), + ), + "Did not encounter null terminator when attempting to read null terminated string", + ); + }); + + await step("write", () => { + const dataView = new DataView(new Uint8Array(13).buffer); + nullTerminatedString.write("Hello world!", dataView); + assertEquals(dataView.buffer, encoder.encode("Hello world!\0").buffer); + }); +});