mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
add encoding/hex module (#434)
This commit is contained in:
parent
aa628cb926
commit
418cdff25d
137
encoding/hex.ts
Normal file
137
encoding/hex.ts
Normal file
@ -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);
|
||||
}
|
182
encoding/hex_test.ts
Normal file
182
encoding/hex_test.ts
Normal file
@ -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);
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user