summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAxetroy <troy450409405@gmail.com>2019-06-18 01:19:57 +0800
committerRyan Dahl <ry@tinyclouds.org>2019-06-17 10:19:56 -0700
commitf430df56195cdb4a1bcc92062e788eec06067111 (patch)
tree95fa8f63ea83821311b3358b6996b02d9ec83a8c
parentba180f346030f82ae439abc68902310a8ecf4ca4 (diff)
add encoding/hex module (denoland/deno_std#434)
Original: https://github.com/denoland/deno_std/commit/418cdff25d022e8abfd4423f1535173c2e7c2210
-rw-r--r--encoding/hex.ts137
-rw-r--r--encoding/hex_test.ts182
-rw-r--r--encoding/test.ts1
-rwxr-xr-xtest.ts1
4 files changed, 321 insertions, 0 deletions
diff --git a/encoding/hex.ts b/encoding/hex.ts
new file mode 100644
index 000000000..83c88ac78
--- /dev/null
+++ b/encoding/hex.ts
@@ -0,0 +1,137 @@
+// Ported from Go
+// https://github.com/golang/go/blob/go1.12.5/src/encoding/hex/hex.go
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+const hextable = new TextEncoder().encode("0123456789abcdef");
+
+export function errInvalidByte(byte: number): Error {
+ return new Error(
+ "encoding/hex: invalid byte: " +
+ new TextDecoder().decode(new Uint8Array([byte]))
+ );
+}
+
+export function errLength(): Error {
+ return new Error("encoding/hex: odd length hex string");
+}
+
+// fromHexChar converts a hex character into its value and a success flag.
+function fromHexChar(byte: number): [number, boolean] {
+ switch (true) {
+ case 48 <= byte && byte <= 57: // '0' <= byte && byte <= '9'
+ return [byte - 48, true];
+ case 97 <= byte && byte <= 102: // 'a' <= byte && byte <= 'f'
+ return [byte - 97 + 10, true];
+ case 65 <= byte && byte <= 70: // 'A' <= byte && byte <= 'F'
+ return [byte - 65 + 10, true];
+ }
+ return [0, false];
+}
+
+/**
+ * EncodedLen returns the length of an encoding of n source bytes. Specifically, it returns n * 2.
+ * @param n
+ */
+export function encodedLen(n: number): number {
+ return n * 2;
+}
+
+/**
+ * Encode encodes `src` into `encodedLen(src.length)` bytes of `dst`.
+ * As a convenience, it returns the number of bytes written to `dst`
+ * but this value is always `encodedLen(src.length)`.
+ * Encode implements hexadecimal encoding.
+ * @param dst
+ * @param src
+ */
+export function encode(dst: Uint8Array, src: Uint8Array): number {
+ const srcLength = encodedLen(src.length);
+ if (dst.length !== srcLength) {
+ throw new Error("Out of index.");
+ }
+ for (let i = 0; i < src.length; i++) {
+ const v = src[i];
+ dst[i * 2] = hextable[v >> 4];
+ dst[i * 2 + 1] = hextable[v & 0x0f];
+ }
+ return srcLength;
+}
+
+/**
+ * EncodeToString returns the hexadecimal encoding of `src`.
+ * @param src
+ */
+export function encodeToString(src: Uint8Array): string {
+ const dest = new Uint8Array(encodedLen(src.length));
+ encode(dest, src);
+ return new TextDecoder().decode(dest);
+}
+
+/**
+ * Decode decodes `src` into `decodedLen(src.length)` bytes
+ * returning the actual number of bytes written to `dst`.
+ * Decode expects that `src` contains only hexadecimal characters and that `src` has even length.
+ * If the input is malformed, Decode returns the number of bytes decoded before the error.
+ * @param dst
+ * @param src
+ */
+export function decode(
+ dst: Uint8Array,
+ src: Uint8Array
+): [number, Error | void] {
+ var i = 0;
+ for (; i < Math.floor(src.length / 2); i++) {
+ const [a, aOK] = fromHexChar(src[i * 2]);
+ if (!aOK) {
+ return [i, errInvalidByte(src[i * 2])];
+ }
+ const [b, bOK] = fromHexChar(src[i * 2 + 1]);
+ if (!bOK) {
+ return [i, errInvalidByte(src[i * 2 + 1])];
+ }
+
+ dst[i] = (a << 4) | b;
+ }
+
+ if (src.length % 2 == 1) {
+ // Check for invalid char before reporting bad length,
+ // since the invalid char (if present) is an earlier problem.
+ const [, ok] = fromHexChar(src[i * 2]);
+ if (!ok) {
+ return [i, errInvalidByte(src[i * 2])];
+ }
+ return [i, errLength()];
+ }
+
+ return [i, undefined];
+}
+
+/**
+ * DecodedLen returns the length of a decoding of `x` source bytes. Specifically, it returns `x / 2`.
+ * @param x
+ */
+export function decodedLen(x: number): number {
+ return Math.floor(x / 2);
+}
+
+/**
+ * DecodeString returns the bytes represented by the hexadecimal string `s`.
+ * DecodeString expects that src contains only hexadecimal characters and that src has even length.
+ * If the input is malformed, DecodeString will throws an error.
+ * @param s the `string` need to decode to `Uint8Array`
+ */
+export function decodeString(s: string): Uint8Array {
+ const src = new TextEncoder().encode(s);
+ // We can use the source slice itself as the destination
+ // because the decode loop increments by one and then the 'seen' byte is not used anymore.
+ const [n, err] = decode(src, src);
+
+ if (err) {
+ throw err;
+ }
+
+ return src.slice(0, n);
+}
diff --git a/encoding/hex_test.ts b/encoding/hex_test.ts
new file mode 100644
index 000000000..5ea81ebdb
--- /dev/null
+++ b/encoding/hex_test.ts
@@ -0,0 +1,182 @@
+// Ported from Go
+// https://github.com/golang/go/blob/go1.12.5/src/encoding/hex/hex.go
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import { test, runIfMain } from "../testing/mod.ts";
+import { assertEquals, assertThrows } from "../testing/asserts.ts";
+
+import {
+ encodedLen,
+ encode,
+ encodeToString,
+ decodedLen,
+ decode,
+ decodeString,
+ errLength,
+ errInvalidByte
+} from "./hex.ts";
+
+function toByte(s: string): number {
+ return new TextEncoder().encode(s)[0];
+}
+
+const testCases = [
+ // encoded(hex) / decoded(Uint8Array)
+ ["", []],
+ ["0001020304050607", [0, 1, 2, 3, 4, 5, 6, 7]],
+ ["08090a0b0c0d0e0f", [8, 9, 10, 11, 12, 13, 14, 15]],
+ ["f0f1f2f3f4f5f6f7", [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7]],
+ ["f8f9fafbfcfdfeff", [0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]],
+ ["67", Array.from(new TextEncoder().encode("g"))],
+ ["e3a1", [0xe3, 0xa1]]
+];
+
+const errCases = [
+ // encoded(hex) / error
+ ["", "", undefined],
+ ["0", "", errLength()],
+ ["zd4aa", "", errInvalidByte(toByte("z"))],
+ ["d4aaz", "\xd4\xaa", errInvalidByte(toByte("z"))],
+ ["30313", "01", errLength()],
+ ["0g", "", errInvalidByte(new TextEncoder().encode("g")[0])],
+ ["00gg", "\x00", errInvalidByte(new TextEncoder().encode("g")[0])],
+ ["0\x01", "", errInvalidByte(new TextEncoder().encode("\x01")[0])],
+ ["ffeed", "\xff\xee", errLength()]
+];
+
+test({
+ name: "[encoding.hex] encodedLen",
+ fn(): void {
+ assertEquals(encodedLen(0), 0);
+ assertEquals(encodedLen(1), 2);
+ assertEquals(encodedLen(2), 4);
+ assertEquals(encodedLen(3), 6);
+ assertEquals(encodedLen(4), 8);
+ }
+});
+
+test({
+ name: "[encoding.hex] encode",
+ fn(): void {
+ {
+ const srcStr = "abc";
+ const src = new TextEncoder().encode(srcStr);
+ const dest = new Uint8Array(encodedLen(src.length));
+ const int = encode(dest, src);
+ assertEquals(src, new Uint8Array([97, 98, 99]));
+ assertEquals(int, 6);
+ }
+
+ {
+ const srcStr = "abc";
+ const src = new TextEncoder().encode(srcStr);
+ const dest = new Uint8Array(2); // out of index
+ assertThrows(
+ (): void => {
+ encode(dest, src);
+ },
+ Error,
+ "Out of index."
+ );
+ }
+
+ for (const [enc, dec] of testCases) {
+ const dest = new Uint8Array(encodedLen(dec.length));
+ const src = new Uint8Array(dec as number[]);
+ const n = encode(dest, src);
+ assertEquals(dest.length, n);
+ assertEquals(new TextDecoder().decode(dest), enc);
+ }
+ }
+});
+
+test({
+ name: "[encoding.hex] encodeToString",
+ fn(): void {
+ for (const [enc, dec] of testCases) {
+ assertEquals(encodeToString(new Uint8Array(dec as number[])), enc);
+ }
+ }
+});
+
+test({
+ name: "[encoding.hex] decodedLen",
+ fn(): void {
+ assertEquals(decodedLen(0), 0);
+ assertEquals(decodedLen(2), 1);
+ assertEquals(decodedLen(4), 2);
+ assertEquals(decodedLen(6), 3);
+ assertEquals(decodedLen(8), 4);
+ }
+});
+
+test({
+ name: "[encoding.hex] decode",
+ fn(): void {
+ // Case for decoding uppercase hex characters, since
+ // Encode always uses lowercase.
+ const extraTestcase = [
+ ["F8F9FAFBFCFDFEFF", [0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]]
+ ];
+
+ const cases = testCases.concat(extraTestcase);
+
+ for (const [enc, dec] of cases) {
+ const dest = new Uint8Array(decodedLen(enc.length));
+ const src = new TextEncoder().encode(enc as string);
+ const [, err] = decode(dest, src);
+ assertEquals(err, undefined);
+ assertEquals(Array.from(dest), Array.from(dec as number[]));
+ }
+ }
+});
+
+test({
+ name: "[encoding.hex] decodeString",
+ fn(): void {
+ for (const [enc, dec] of testCases) {
+ const dst = decodeString(enc as string);
+
+ assertEquals(dec, Array.from(dst));
+ }
+ }
+});
+
+test({
+ name: "[encoding.hex] decode error",
+ fn(): void {
+ for (const [input, output, expectedErr] of errCases) {
+ const out = new Uint8Array((input as string).length + 10);
+ const [n, err] = decode(out, new TextEncoder().encode(input as string));
+ assertEquals(
+ new TextDecoder("ascii").decode(out.slice(0, n)),
+ output as string
+ );
+ assertEquals(err, expectedErr);
+ }
+ }
+});
+
+test({
+ name: "[encoding.hex] decodeString error",
+ fn(): void {
+ for (const [input, output, expectedErr] of errCases) {
+ if (expectedErr) {
+ assertThrows(
+ (): void => {
+ decodeString(input as string);
+ },
+ Error,
+ (expectedErr as Error).message
+ );
+ } else {
+ const out = decodeString(input as string);
+ assertEquals(new TextDecoder("ascii").decode(out), output as string);
+ }
+ }
+ }
+});
+
+runIfMain(import.meta);
diff --git a/encoding/test.ts b/encoding/test.ts
index e7f779c86..c7a1c9716 100644
--- a/encoding/test.ts
+++ b/encoding/test.ts
@@ -1,3 +1,4 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import "./hex_test.ts";
import "./toml_test.ts";
import "./csv_test.ts";
diff --git a/test.ts b/test.ts
index c1381cb27..5747be1c5 100755
--- a/test.ts
+++ b/test.ts
@@ -22,6 +22,7 @@ import "./testing/test.ts";
import "./textproto/test.ts";
import "./util/test.ts";
import "./ws/test.ts";
+import "./encoding/test.ts";
import { xrun } from "./prettier/util.ts";
import { red, green } from "./colors/mod.ts";