summaryrefslogtreecommitdiff
path: root/std/encoding
diff options
context:
space:
mode:
authorOliver Lenehan <sunsetkookaburra+github@outlook.com.au>2020-03-11 06:16:08 +1100
committerGitHub <noreply@github.com>2020-03-10 15:16:08 -0400
commita309dcdd0f5bc93849d720328b887931a4810e2f (patch)
treee6f684df4607ada000635a2acdfee85576e3d774 /std/encoding
parent55119aaee2e5fec8074373ef51b56d5095da1faf (diff)
feat (std/encoding): add binary module (#4274)
Diffstat (limited to 'std/encoding')
-rw-r--r--std/encoding/README.md29
-rw-r--r--std/encoding/binary.ts250
-rw-r--r--std/encoding/binary_test.ts165
-rw-r--r--std/encoding/mod.ts1
4 files changed, 444 insertions, 1 deletions
diff --git a/std/encoding/README.md b/std/encoding/README.md
index d96075746..2b2d416b1 100644
--- a/std/encoding/README.md
+++ b/std/encoding/README.md
@@ -1,4 +1,31 @@
-# Encoding
+# encoding
+
+Helper module for dealing with external data structures.
+
+- [`base32`](#base32)
+- [`binary`](#binary)
+- [`csv`](#csv)
+- [`toml`](#toml)
+- [`yaml`](#yaml)
+
+## Binary
+
+Implements equivalent methods to Go's `encoding/binary` package.
+
+Available Functions:
+
+```typescript
+sizeof(dataType: RawTypes): number
+getNBytes(r: Deno.Reader, n: number): Promise<Uint8Array>
+varnum(b: Uint8Array, o: VarnumOptions = {}): number | Deno.EOF
+varbig(b: Uint8Array, o: VarbigOptions = {}): bigint | Deno.EOF
+putVarnum(b: Uint8Array, x: number, o: VarnumOptions = {}): number
+putVarbig(b: Uint8Array, x: bigint, o: VarbigOptions = {}): number
+readVarnum(r: Deno.Reader, o: VarnumOptions = {}): Promise<number>
+readVarbig(r: Deno.Reader, o: VarbigOptions = {}): Promise<bigint>
+writeVarnum(w: Deno.Writer, x: number, o: VarnumOptions = {}): Promise<number>
+writeVarbig(w: Deno.Writer, x: bigint, o: VarbigOptions = {}): Promise<number>
+```
## CSV
diff --git a/std/encoding/binary.ts b/std/encoding/binary.ts
new file mode 100644
index 000000000..e666dec26
--- /dev/null
+++ b/std/encoding/binary.ts
@@ -0,0 +1,250 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+import { UnexpectedEOFError } from "../io/bufio.ts";
+
+type RawBaseTypes = "int8" | "int16" | "int32" | "uint8" | "uint16" | "uint32";
+type RawNumberTypes = RawBaseTypes | "float32" | "float64";
+type RawBigTypes = RawBaseTypes | "int64" | "uint64";
+type RawTypes = RawNumberTypes | RawBigTypes;
+
+/** How encoded binary data is ordered. */
+export type Endianness = "little" | "big";
+
+/** Options for working with the `number` type. */
+export interface VarnumOptions {
+ /** The binary format used. */
+ dataType?: RawNumberTypes;
+ /** The binary encoding order used. */
+ endian?: Endianness;
+}
+
+/** Options for working with the `bigint` type. */
+export interface VarbigOptions {
+ /** The binary format used. */
+ dataType?: RawBigTypes;
+ /** The binary encoding order used. */
+ endian?: Endianness;
+}
+
+const rawTypeSizes = {
+ int8: 1,
+ uint8: 1,
+ int16: 2,
+ uint16: 2,
+ int32: 4,
+ uint32: 4,
+ int64: 8,
+ uint64: 8,
+ float32: 4,
+ float64: 8
+};
+
+/** Returns the number of bytes required to store the given data-type. */
+export function sizeof(dataType: RawTypes): number {
+ return rawTypeSizes[dataType];
+}
+
+/** Reads `n` bytes from `r`.
+ *
+ * Returns it in a `Uint8Array`, or throws `UnexpectedEOFError` if `n` bytes cannot be read. */
+export async function getNBytes(
+ r: Deno.Reader,
+ n: number
+): Promise<Uint8Array> {
+ const scratch = new Uint8Array(n);
+ const nRead = await r.read(scratch);
+ if (nRead === Deno.EOF || nRead < n) throw new UnexpectedEOFError();
+ return scratch;
+}
+
+/** Decode a number from `b`, and return it as a `number`. Data-type defaults to `int32`.
+ * Returns `EOF` if `b` is too short for the data-type given in `o`. */
+export function varnum(
+ b: Uint8Array,
+ o: VarnumOptions = {}
+): number | Deno.EOF {
+ o.dataType = o.dataType ?? "int32";
+ const littleEndian = (o.endian ?? "big") === "little" ? true : false;
+ if (b.length < sizeof(o.dataType)) return Deno.EOF;
+ const view = new DataView(b.buffer);
+ switch (o.dataType) {
+ case "int8":
+ return view.getInt8(0);
+ case "uint8":
+ return view.getUint8(0);
+ case "int16":
+ return view.getInt16(0, littleEndian);
+ case "uint16":
+ return view.getUint16(0, littleEndian);
+ case "int32":
+ return view.getInt32(0, littleEndian);
+ case "uint32":
+ return view.getUint32(0, littleEndian);
+ case "float32":
+ return view.getFloat32(0, littleEndian);
+ case "float64":
+ return view.getFloat64(0, littleEndian);
+ }
+}
+
+/** Decode an integer from `b`, and return it as a `bigint`. Data-type defaults to `int64`.
+ * Returns `EOF` if `b` is too short for the data-type given in `o`. */
+export function varbig(
+ b: Uint8Array,
+ o: VarbigOptions = {}
+): bigint | Deno.EOF {
+ o.dataType = o.dataType ?? "int64";
+ const littleEndian = (o.endian ?? "big") === "little" ? true : false;
+ if (b.length < sizeof(o.dataType)) return Deno.EOF;
+ const view = new DataView(b.buffer);
+ switch (o.dataType) {
+ case "int8":
+ return BigInt(view.getInt8(0));
+ case "uint8":
+ return BigInt(view.getUint8(0));
+ case "int16":
+ return BigInt(view.getInt16(0, littleEndian));
+ case "uint16":
+ return BigInt(view.getUint16(0, littleEndian));
+ case "int32":
+ return BigInt(view.getInt32(0, littleEndian));
+ case "uint32":
+ return BigInt(view.getUint32(0, littleEndian));
+ case "int64":
+ return view.getBigInt64(0, littleEndian);
+ case "uint64":
+ return view.getBigUint64(0, littleEndian);
+ }
+}
+
+/** Encode a number `x` into `b`, and return the number of bytes used. Data-type defaults to `int32`.
+ * Returns 0 if `b` is too short for the data-type given in `o`. */
+export function putVarnum(
+ b: Uint8Array,
+ x: number,
+ o: VarnumOptions = {}
+): number {
+ o.dataType = o.dataType ?? "int32";
+ const littleEndian = (o.endian ?? "big") === "little" ? true : false;
+ if (b.length < sizeof(o.dataType)) return 0;
+ const view = new DataView(b.buffer);
+ switch (o.dataType) {
+ case "int8":
+ view.setInt8(0, x);
+ break;
+ case "uint8":
+ view.setUint8(0, x);
+ break;
+ case "int16":
+ view.setInt16(0, x, littleEndian);
+ break;
+ case "uint16":
+ view.setUint16(0, x, littleEndian);
+ break;
+ case "int32":
+ view.setInt32(0, x, littleEndian);
+ break;
+ case "uint32":
+ view.setUint32(0, x, littleEndian);
+ break;
+ case "float32":
+ view.setFloat32(0, x, littleEndian);
+ break;
+ case "float64":
+ view.setFloat64(0, x, littleEndian);
+ break;
+ }
+ return sizeof(o.dataType);
+}
+
+/** Encode an integer `x` into `b`, and return the number of bytes used. Data-type defaults to `int64`.
+ * Returns 0 if `b` is too short for the data-type given in `o`. */
+export function putVarbig(
+ b: Uint8Array,
+ x: bigint,
+ o: VarbigOptions = {}
+): number {
+ o.dataType = o.dataType ?? "int64";
+ const littleEndian = (o.endian ?? "big") === "little" ? true : false;
+ if (b.length < sizeof(o.dataType)) return 0;
+ const view = new DataView(b.buffer);
+ switch (o.dataType) {
+ case "int8":
+ view.setInt8(0, Number(x));
+ break;
+ case "uint8":
+ view.setUint8(0, Number(x));
+ break;
+ case "int16":
+ view.setInt16(0, Number(x), littleEndian);
+ break;
+ case "uint16":
+ view.setUint16(0, Number(x), littleEndian);
+ break;
+ case "int32":
+ view.setInt32(0, Number(x), littleEndian);
+ break;
+ case "uint32":
+ view.setUint32(0, Number(x), littleEndian);
+ break;
+ case "int64":
+ view.setBigInt64(0, x, littleEndian);
+ break;
+ case "uint64":
+ view.setBigUint64(0, x, littleEndian);
+ break;
+ }
+ return sizeof(o.dataType);
+}
+
+/** Reads a number from `r`, comsuming `sizeof(o.dataType)` bytes. Data-type defaults to `int32`.
+ *
+ * Returns it as `number`, or throws `UnexpectedEOFError` if not enough bytes can be read. */
+export async function readVarnum(
+ r: Deno.Reader,
+ o: VarnumOptions = {}
+): Promise<number> {
+ o.dataType = o.dataType ?? "int32";
+ const scratch = await getNBytes(r, sizeof(o.dataType));
+ return varnum(scratch, o) as number;
+}
+
+/** Reads an integer from `r`, comsuming `sizeof(o.dataType)` bytes. Data-type defaults to `int64`.
+ *
+ * Returns it as `bigint`, or throws `UnexpectedEOFError` if not enough bytes can be read. */
+export async function readVarbig(
+ r: Deno.Reader,
+ o: VarbigOptions = {}
+): Promise<bigint> {
+ o.dataType = o.dataType ?? "int64";
+ const scratch = await getNBytes(r, sizeof(o.dataType));
+ return varbig(scratch, o) as bigint;
+}
+
+/** Writes a number `x` to `w`. Data-type defaults to `int32`.
+ *
+ * Returns the number of bytes written. */
+export function writeVarnum(
+ w: Deno.Writer,
+ x: number,
+ o: VarnumOptions = {}
+): Promise<number> {
+ o.dataType = o.dataType ?? "int32";
+ const scratch = new Uint8Array(sizeof(o.dataType));
+ putVarnum(scratch, x, o);
+ return w.write(scratch);
+}
+
+/** Writes an integer `x` to `w`. Data-type defaults to `int64`.
+ *
+ * Returns the number of bytes written. */
+export function writeVarbig(
+ w: Deno.Writer,
+ x: bigint,
+ o: VarbigOptions = {}
+): Promise<number> {
+ o.dataType = o.dataType ?? "int64";
+ const scratch = new Uint8Array(sizeof(o.dataType));
+ putVarbig(scratch, x, o);
+ return w.write(scratch);
+}
diff --git a/std/encoding/binary_test.ts b/std/encoding/binary_test.ts
new file mode 100644
index 000000000..9b541746b
--- /dev/null
+++ b/std/encoding/binary_test.ts
@@ -0,0 +1,165 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, assertThrowsAsync } from "../testing/asserts.ts";
+import { UnexpectedEOFError } from "../io/bufio.ts";
+import {
+ getNBytes,
+ putVarbig,
+ putVarnum,
+ readVarbig,
+ readVarnum,
+ sizeof,
+ varbig,
+ varnum,
+ writeVarbig,
+ writeVarnum
+} from "./binary.ts";
+
+Deno.test(async function testGetNBytes(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const buff = new Deno.Buffer(data.buffer);
+ const rslt = await getNBytes(buff, 8);
+ assertEquals(rslt, data);
+});
+
+Deno.test(async function testGetNBytesThrows(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4]);
+ const buff = new Deno.Buffer(data.buffer);
+ assertThrowsAsync(async () => {
+ await getNBytes(buff, 8);
+ }, UnexpectedEOFError);
+});
+
+Deno.test(async function testPutVarbig(): Promise<void> {
+ const buff = new Uint8Array(8);
+ putVarbig(buff, 0xffeeddccbbaa9988n);
+ assertEquals(
+ buff,
+ new Uint8Array([0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88])
+ );
+});
+
+Deno.test(async function testPutVarbigLittleEndian(): Promise<void> {
+ const buff = new Uint8Array(8);
+ putVarbig(buff, 0x8899aabbccddeeffn, { endian: "little" });
+ assertEquals(
+ buff,
+ new Uint8Array([0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88])
+ );
+});
+
+Deno.test(async function testPutVarnum(): Promise<void> {
+ const buff = new Uint8Array(4);
+ putVarnum(buff, 0xffeeddcc);
+ assertEquals(buff, new Uint8Array([0xff, 0xee, 0xdd, 0xcc]));
+});
+
+Deno.test(async function testPutVarnumLittleEndian(): Promise<void> {
+ const buff = new Uint8Array(4);
+ putVarnum(buff, 0xccddeeff, { endian: "little" });
+ assertEquals(buff, new Uint8Array([0xff, 0xee, 0xdd, 0xcc]));
+});
+
+Deno.test(async function testReadVarbig(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const buff = new Deno.Buffer(data.buffer);
+ const rslt = await readVarbig(buff);
+ assertEquals(rslt, 0x0102030405060708n);
+});
+
+Deno.test(async function testReadVarbigLittleEndian(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const buff = new Deno.Buffer(data.buffer);
+ const rslt = await readVarbig(buff, { endian: "little" });
+ assertEquals(rslt, 0x0807060504030201n);
+});
+
+Deno.test(async function testReadVarnum(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4]);
+ const buff = new Deno.Buffer(data.buffer);
+ const rslt = await readVarnum(buff);
+ assertEquals(rslt, 0x01020304);
+});
+
+Deno.test(async function testReadVarnumLittleEndian(): Promise<void> {
+ const data = new Uint8Array([1, 2, 3, 4]);
+ const buff = new Deno.Buffer(data.buffer);
+ const rslt = await readVarnum(buff, { endian: "little" });
+ assertEquals(rslt, 0x04030201);
+});
+
+Deno.test(function testSizeof(): void {
+ assertEquals(1, sizeof("int8"));
+ assertEquals(1, sizeof("uint8"));
+ assertEquals(2, sizeof("int16"));
+ assertEquals(2, sizeof("uint16"));
+ assertEquals(4, sizeof("int32"));
+ assertEquals(4, sizeof("uint32"));
+ assertEquals(8, sizeof("int64"));
+ assertEquals(8, sizeof("uint64"));
+ assertEquals(4, sizeof("float32"));
+ assertEquals(8, sizeof("float64"));
+});
+
+Deno.test(function testVarbig(): void {
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const rslt = varbig(data);
+ assertEquals(rslt, 0x0102030405060708n);
+});
+
+Deno.test(function testVarbigLittleEndian(): void {
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const rslt = varbig(data, { endian: "little" });
+ assertEquals(rslt, 0x0807060504030201n);
+});
+
+Deno.test(function testVarnum(): void {
+ const data = new Uint8Array([1, 2, 3, 4]);
+ const rslt = varnum(data);
+ assertEquals(rslt, 0x01020304);
+});
+Deno.test(function testVarnumLittleEndian(): void {
+ const data = new Uint8Array([1, 2, 3, 4]);
+ const rslt = varnum(data, { endian: "little" });
+ assertEquals(rslt, 0x04030201);
+});
+
+Deno.test(async function testWriteVarbig(): Promise<void> {
+ const data = new Uint8Array(8);
+ const buff = new Deno.Buffer();
+ await writeVarbig(buff, 0x0102030405060708n);
+ await buff.read(data);
+ assertEquals(
+ data,
+ new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
+ );
+});
+
+Deno.test(async function testWriteVarbigLittleEndian(): Promise<void> {
+ const data = new Uint8Array(8);
+ const buff = new Deno.Buffer();
+ await writeVarbig(buff, 0x0807060504030201n, { endian: "little" });
+ await buff.read(data);
+ assertEquals(
+ data,
+ new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
+ );
+});
+
+Deno.test(async function testWriteVarnum(): Promise<void> {
+ const data = new Uint8Array(4);
+ const buff = new Deno.Buffer();
+ await writeVarnum(buff, 0x01020304);
+ await buff.read(data);
+ assertEquals(data, new Uint8Array([0x01, 0x02, 0x03, 0x04]));
+});
+
+Deno.test(async function testWriteVarnumLittleEndian(): Promise<void> {
+ const data = new Uint8Array(4);
+ const buff = new Deno.Buffer();
+ await writeVarnum(buff, 0x04030201, { endian: "little" });
+ await buff.read(data);
+ assertEquals(data, new Uint8Array([0x01, 0x02, 0x03, 0x04]));
+});
+
+Deno.runTests();
diff --git a/std/encoding/mod.ts b/std/encoding/mod.ts
index 03bde0294..d63cf47f3 100644
--- a/std/encoding/mod.ts
+++ b/std/encoding/mod.ts
@@ -12,3 +12,4 @@ export {
} from "./hex.ts";
export { parse as parseToml, stringify as tomlStringify } from "./toml.ts";
export { parse as parseYaml, stringify as yamlStringify } from "./yaml.ts";
+export * from "./binary.ts";