Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING(encoding/hex): remove encodedLen, encodeToString, decodedLen, decodeString, errInvalidByte, errLength #733

Merged
merged 8 commits into from
Jul 6, 2021
70 changes: 10 additions & 60 deletions encoding/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,15 @@

const hexTable = new TextEncoder().encode("0123456789abcdef");

/**
* ErrInvalidByte takes an invalid byte and returns an Error.
* @param byte
*/
export function errInvalidByte(byte: number): Error {
return new Error(
"encoding/hex: invalid byte: " +
new TextDecoder().decode(new Uint8Array([byte])),
);
function errInvalidByte(byte: number) {
return new TypeError(`Invalid byte '${String.fromCharCode(byte)}'`);
}

/** ErrLength returns an error about odd string length. */
export function errLength(): Error {
return new Error("encoding/hex: odd length hex string");
function errLength() {
return new RangeError("Odd length hex string");
}

// fromHexChar converts a hex character into its value.
/** Converts a hex character into its value. */
function fromHexChar(byte: number): number {
// '0' <= byte && byte <= '9'
if (48 <= byte && byte <= 57) return byte - 48;
Expand All @@ -35,21 +27,9 @@ function fromHexChar(byte: number): number {
throw errInvalidByte(byte);
}

/**
* 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.
* @param src
*/
/** Encodes `src` into `src.length * 2` bytes. */
export function encode(src: Uint8Array): Uint8Array {
const dst = new Uint8Array(encodedLen(src.length));
const dst = new Uint8Array(src.length * 2);
for (let i = 0; i < dst.length; i++) {
const v = src[i];
dst[i * 2] = hexTable[v >> 4];
Expand All @@ -59,21 +39,11 @@ export function encode(src: Uint8Array): Uint8Array {
}

/**
* EncodeToString returns the hexadecimal encoding of `src`.
* @param src
*/
export function encodeToString(src: Uint8Array): string {
return new TextDecoder().decode(encode(src));
}

/**
* Decode decodes `src` into `decodedLen(src.length)` bytes
* If the input is malformed an error will be thrown
* the error.
* @param src
* Decodes `src` into `src.length / 2` bytes.
* If the input is malformed, an error will be thrown.
*/
export function decode(src: Uint8Array): Uint8Array {
const dst = new Uint8Array(decodedLen(src.length));
const dst = new Uint8Array(src.length / 2);
for (let i = 0; i < dst.length; i++) {
const a = fromHexChar(src[i * 2]);
const b = fromHexChar(src[i * 2 + 1]);
Expand All @@ -89,23 +59,3 @@ export function decode(src: Uint8Array): Uint8Array {

return dst;
}

/**
* DecodedLen returns the length of decoding `x` source bytes.
* Specifically, it returns `x / 2`.
* @param x
*/
export function decodedLen(x: number): number {
return x >>> 1;
}

/**
* 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 throw an error.
* @param s the `string` to decode to `Uint8Array`
*/
export function decodeString(s: string): Uint8Array {
return decode(new TextEncoder().encode(s));
}
171 changes: 46 additions & 125 deletions encoding/hex_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import { assertEquals, assertThrows } from "../testing/asserts.ts";

import {
decode,
decodedLen,
decodeString,
encode,
encodedLen,
encodeToString,
errInvalidByte,
errLength,
} from "./hex.ts";

function toByte(s: string): number {
return new TextEncoder().encode(s)[0];
}
import { decode, encode } from "./hex.ts";

const testCases = [
// encoded(hex) / decoded(Uint8Array)
Expand All @@ -32,123 +19,57 @@ const testCases = [
["e3a1", [0xe3, 0xa1]],
];

const errCases = [
// encoded(hex) / error
["0", errLength()],
["zd4aa", errInvalidByte(toByte("z"))],
["d4aaz", errInvalidByte(toByte("z"))],
["30313", errLength()],
["0g", errInvalidByte(new TextEncoder().encode("g")[0])],
["00gg", errInvalidByte(new TextEncoder().encode("g")[0])],
["0\x01", errInvalidByte(new TextEncoder().encode("\x01")[0])],
["ffeed", errLength()],
const errCases: [string, ErrorConstructor, string][] = [
// encoded(hex) / error / msg
["0", RangeError, ""],
["zd4aa", TypeError, "'z'"],
["d4aaz", TypeError, "'z'"],
["30313", RangeError, ""],
["0g", TypeError, "'g'"],
["00gg", TypeError, "'g'"],
["0\x01", TypeError, "'\x01'"],
["ffeed", RangeError, ""],
];

Deno.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);
},
});

Deno.test({
name: "[encoding.hex] encode",
fn(): void {
{
const srcStr = "abc";
const src = new TextEncoder().encode(srcStr);
const dest = encode(src);
assertEquals(src, new Uint8Array([97, 98, 99]));
assertEquals(dest.length, 6);
}

for (const [enc, dec] of testCases) {
const src = new Uint8Array(dec as number[]);
const dest = encode(src);
assertEquals(dest.length, src.length * 2);
assertEquals(new TextDecoder().decode(dest), enc);
}
},
Deno.test("[encoding.hex] encode", () => {
{
const srcStr = "abc";
const src = new TextEncoder().encode(srcStr);
const dest = encode(src);
assertEquals(src, new Uint8Array([97, 98, 99]));
assertEquals(dest.length, 6);
}

for (const [enc, dec] of testCases) {
const src = new Uint8Array(dec as number[]);
const dest = encode(src);
assertEquals(dest.length, src.length * 2);
assertEquals(new TextDecoder().decode(dest), enc);
}
});

Deno.test({
name: "[encoding.hex] encodeToString",
fn(): void {
for (const [enc, dec] of testCases) {
assertEquals(encodeToString(new Uint8Array(dec as number[])), enc);
}
},
});

Deno.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);
},
});
Deno.test("[encoding.hex] decode", () => {
// Case for decoding uppercase hex characters, since
// Encode always uses lowercase.
const extraTestcase = [
["F8F9FAFBFCFDFEFF", [0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]],
];

Deno.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 src = new TextEncoder().encode(enc as string);
const dest = decode(src);
assertEquals(Array.from(dest), Array.from(dec as number[]));
}
},
});

Deno.test({
name: "[encoding.hex] decodeString",
fn(): void {
for (const [enc, dec] of testCases) {
const dst = decodeString(enc as string);

assertEquals(dec, Array.from(dst));
}
},
});
const cases = testCases.concat(extraTestcase);

Deno.test({
name: "[encoding.hex] decode error",
fn(): void {
for (const [input, expectedErr] of errCases) {
assertThrows(
() => decode(new TextEncoder().encode(input as string)),
Error,
(expectedErr as Error).message,
);
}
},
for (const [enc, dec] of cases) {
const src = new TextEncoder().encode(enc as string);
const dest = decode(src);
assertEquals(Array.from(dest), Array.from(dec as number[]));
}
});

Deno.test({
name: "[encoding.hex] decodeString error",
fn(): void {
for (const [input, expectedErr] of errCases) {
assertThrows(
(): void => {
decodeString(input as string);
},
Error,
(expectedErr as Error).message,
);
}
},
Deno.test("[encoding.hex] decode error", () => {
for (const [input, expectedErr, msg] of errCases) {
assertThrows(
() => decode(new TextEncoder().encode(input)),
expectedErr,
msg,
);
}
});
2 changes: 1 addition & 1 deletion hash/_sha3/sponge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class Sponge {
const rawOutput = this.squeeze(this.#option.bitsize >> 3);
switch (format) {
case "hex":
return hex.encodeToString(rawOutput);
return new TextDecoder().decode(hex.encode(rawOutput));
default:
throw new Error("sha3: invalid output format");
}
Expand Down
2 changes: 1 addition & 1 deletion hash/_wasm/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class Hash implements Hasher {

switch (format) {
case "hex":
return hex.encodeToString(finalized);
return new TextDecoder().decode(hex.encode(finalized));
case "base64":
return base64.encode(finalized);
default:
Expand Down
2 changes: 1 addition & 1 deletion hash/md5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export class Md5 {

switch (format) {
case "hex":
return hex.encodeToString(new Uint8Array(hash));
return new TextDecoder().decode(hex.encode(new Uint8Array(hash)));
case "base64": {
const data = new Uint8Array(hash);
let dataString = "";
Expand Down
Loading