Skip to content

Commit

Permalink
BREAKING(encoding/hex): remove encodedLen, encodeToString, decodedLen…
Browse files Browse the repository at this point in the history
…, decodeString, errInvalidByte, errLength (#733)
  • Loading branch information
zhangyuannie authored Jul 6, 2021
1 parent 7c3f017 commit 30a9669
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 202 deletions.
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

0 comments on commit 30a9669

Please sign in to comment.