mirror of
https://github.com/denoland/deno.git
synced 2025-10-03 07:34:36 +00:00
BREAKING(std/encoding/hex): simplify API (#6690)
This commit is contained in:
parent
634d6af7a1
commit
dc6b3ef714
2 changed files with 56 additions and 118 deletions
|
@ -18,17 +18,16 @@ export function errLength(): Error {
|
||||||
return new Error("encoding/hex: odd length hex string");
|
return new Error("encoding/hex: odd length hex string");
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromHexChar converts a hex character into its value and a success flag.
|
// fromHexChar converts a hex character into its value.
|
||||||
function fromHexChar(byte: number): [number, boolean] {
|
function fromHexChar(byte: number): number {
|
||||||
switch (true) {
|
// '0' <= byte && byte <= '9'
|
||||||
case 48 <= byte && byte <= 57: // '0' <= byte && byte <= '9'
|
if (48 <= byte && byte <= 57) return byte - 48;
|
||||||
return [byte - 48, true];
|
// 'a' <= byte && byte <= 'f'
|
||||||
case 97 <= byte && byte <= 102: // 'a' <= byte && byte <= 'f'
|
if (97 <= byte && byte <= 102) return byte - 97 + 10;
|
||||||
return [byte - 97 + 10, true];
|
// 'A' <= byte && byte <= 'F'
|
||||||
case 65 <= byte && byte <= 70: // 'A' <= byte && byte <= 'F'
|
if (65 <= byte && byte <= 70) return byte - 65 + 10;
|
||||||
return [byte - 65 + 10, true];
|
|
||||||
}
|
throw errInvalidByte(byte);
|
||||||
return [0, false];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,24 +40,17 @@ export function encodedLen(n: number): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode encodes `src` into `encodedLen(src.length)` bytes of `dst`.
|
* Encode encodes `src` into `encodedLen(src.length)` bytes.
|
||||||
* 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
|
* @param src
|
||||||
*/
|
*/
|
||||||
export function encode(src: Uint8Array, dst: Uint8Array): number {
|
export function encode(src: Uint8Array): Uint8Array {
|
||||||
const srcLength = encodedLen(src.length);
|
const dst = new Uint8Array(encodedLen(src.length));
|
||||||
if (dst.length !== srcLength) {
|
for (let i = 0; i < dst.length; i++) {
|
||||||
throw new Error("Out of index.");
|
|
||||||
}
|
|
||||||
for (let i = 0; i < src.length; i++) {
|
|
||||||
const v = src[i];
|
const v = src[i];
|
||||||
dst[i * 2] = hextable[v >> 4];
|
dst[i * 2] = hextable[v >> 4];
|
||||||
dst[i * 2 + 1] = hextable[v & 0x0f];
|
dst[i * 2 + 1] = hextable[v & 0x0f];
|
||||||
}
|
}
|
||||||
return srcLength;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,78 +58,49 @@ export function encode(src: Uint8Array, dst: Uint8Array): number {
|
||||||
* @param src
|
* @param src
|
||||||
*/
|
*/
|
||||||
export function encodeToString(src: Uint8Array): string {
|
export function encodeToString(src: Uint8Array): string {
|
||||||
const dest = new Uint8Array(encodedLen(src.length));
|
return new TextDecoder().decode(encode(src));
|
||||||
encode(src, dest);
|
|
||||||
return new TextDecoder().decode(dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode decodes `src` into `decodedLen(src.length)` bytes
|
* Decode decodes `src` into `decodedLen(src.length)` bytes
|
||||||
* returning the actual number of bytes written to `dst`.
|
* If the input is malformed an error will be thrown
|
||||||
* 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.
|
* the error.
|
||||||
* @param dst
|
|
||||||
* @param src
|
* @param src
|
||||||
*/
|
*/
|
||||||
export function decode(
|
export function decode(src: Uint8Array): Uint8Array {
|
||||||
src: Uint8Array,
|
const dst = new Uint8Array(decodedLen(src.length));
|
||||||
dst: Uint8Array
|
for (let i = 0; i < dst.length; i++) {
|
||||||
): [number, Error | void] {
|
const a = fromHexChar(src[i * 2]);
|
||||||
let i = 0;
|
const b = fromHexChar(src[i * 2 + 1]);
|
||||||
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;
|
dst[i] = (a << 4) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.length % 2 == 1) {
|
if (src.length % 2 == 1) {
|
||||||
// Check for invalid char before reporting bad length,
|
// Check for invalid char before reporting bad length,
|
||||||
// since the invalid char (if present) is an earlier problem.
|
// since the invalid char (if present) is an earlier problem.
|
||||||
const [, ok] = fromHexChar(src[i * 2]);
|
fromHexChar(src[dst.length * 2]);
|
||||||
if (!ok) {
|
throw errLength();
|
||||||
return [i, errInvalidByte(src[i * 2])];
|
|
||||||
}
|
|
||||||
return [i, errLength()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [i, undefined];
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DecodedLen returns the length of a decoding of `x` source bytes.
|
* DecodedLen returns the length of decoding `x` source bytes.
|
||||||
* Specifically, it returns `x / 2`.
|
* Specifically, it returns `x / 2`.
|
||||||
* @param x
|
* @param x
|
||||||
*/
|
*/
|
||||||
export function decodedLen(x: number): number {
|
export function decodedLen(x: number): number {
|
||||||
return Math.floor(x / 2);
|
return x >>> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DecodeString returns the bytes represented by the hexadecimal string `s`.
|
* DecodeString returns the bytes represented by the hexadecimal string `s`.
|
||||||
* DecodeString expects that src contains only hexadecimal characters and that
|
* DecodeString expects that src contains only hexadecimal characters and that
|
||||||
* src has even length.
|
* src has even length.
|
||||||
* If the input is malformed, DecodeString will throws an error.
|
* If the input is malformed, DecodeString will throw an error.
|
||||||
* @param s the `string` need to decode to `Uint8Array`
|
* @param s the `string` to decode to `Uint8Array`
|
||||||
*/
|
*/
|
||||||
export function decodeString(s: string): Uint8Array {
|
export function decodeString(s: string): Uint8Array {
|
||||||
const src = new TextEncoder().encode(s);
|
return decode(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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,15 +34,14 @@ const testCases = [
|
||||||
|
|
||||||
const errCases = [
|
const errCases = [
|
||||||
// encoded(hex) / error
|
// encoded(hex) / error
|
||||||
["", "", undefined],
|
["0", errLength()],
|
||||||
["0", "", errLength()],
|
["zd4aa", errInvalidByte(toByte("z"))],
|
||||||
["zd4aa", "", errInvalidByte(toByte("z"))],
|
["d4aaz", errInvalidByte(toByte("z"))],
|
||||||
["d4aaz", "\xd4\xaa", errInvalidByte(toByte("z"))],
|
["30313", errLength()],
|
||||||
["30313", "01", errLength()],
|
["0g", errInvalidByte(new TextEncoder().encode("g")[0])],
|
||||||
["0g", "", errInvalidByte(new TextEncoder().encode("g")[0])],
|
["00gg", errInvalidByte(new TextEncoder().encode("g")[0])],
|
||||||
["00gg", "\x00", errInvalidByte(new TextEncoder().encode("g")[0])],
|
["0\x01", errInvalidByte(new TextEncoder().encode("\x01")[0])],
|
||||||
["0\x01", "", errInvalidByte(new TextEncoder().encode("\x01")[0])],
|
["ffeed", errLength()],
|
||||||
["ffeed", "\xff\xee", errLength()],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
Deno.test({
|
Deno.test({
|
||||||
|
@ -62,30 +61,15 @@ Deno.test({
|
||||||
{
|
{
|
||||||
const srcStr = "abc";
|
const srcStr = "abc";
|
||||||
const src = new TextEncoder().encode(srcStr);
|
const src = new TextEncoder().encode(srcStr);
|
||||||
const dest = new Uint8Array(encodedLen(src.length));
|
const dest = encode(src);
|
||||||
const int = encode(src, dest);
|
|
||||||
assertEquals(src, new Uint8Array([97, 98, 99]));
|
assertEquals(src, new Uint8Array([97, 98, 99]));
|
||||||
assertEquals(int, 6);
|
assertEquals(dest.length, 6);
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const srcStr = "abc";
|
|
||||||
const src = new TextEncoder().encode(srcStr);
|
|
||||||
const dest = new Uint8Array(2); // out of index
|
|
||||||
assertThrows(
|
|
||||||
(): void => {
|
|
||||||
encode(src, dest);
|
|
||||||
},
|
|
||||||
Error,
|
|
||||||
"Out of index."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [enc, dec] of testCases) {
|
for (const [enc, dec] of testCases) {
|
||||||
const dest = new Uint8Array(encodedLen(dec.length));
|
|
||||||
const src = new Uint8Array(dec as number[]);
|
const src = new Uint8Array(dec as number[]);
|
||||||
const n = encode(src, dest);
|
const dest = encode(src);
|
||||||
assertEquals(dest.length, n);
|
assertEquals(dest.length, src.length * 2);
|
||||||
assertEquals(new TextDecoder().decode(dest), enc);
|
assertEquals(new TextDecoder().decode(dest), enc);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -123,10 +107,8 @@ Deno.test({
|
||||||
const cases = testCases.concat(extraTestcase);
|
const cases = testCases.concat(extraTestcase);
|
||||||
|
|
||||||
for (const [enc, dec] of cases) {
|
for (const [enc, dec] of cases) {
|
||||||
const dest = new Uint8Array(decodedLen(enc.length));
|
|
||||||
const src = new TextEncoder().encode(enc as string);
|
const src = new TextEncoder().encode(enc as string);
|
||||||
const [, err] = decode(src, dest);
|
const dest = decode(src);
|
||||||
assertEquals(err, undefined);
|
|
||||||
assertEquals(Array.from(dest), Array.from(dec as number[]));
|
assertEquals(Array.from(dest), Array.from(dec as number[]));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -146,14 +128,12 @@ Deno.test({
|
||||||
Deno.test({
|
Deno.test({
|
||||||
name: "[encoding.hex] decode error",
|
name: "[encoding.hex] decode error",
|
||||||
fn(): void {
|
fn(): void {
|
||||||
for (const [input, output, expectedErr] of errCases) {
|
for (const [input, expectedErr] of errCases) {
|
||||||
const out = new Uint8Array((input as string).length + 10);
|
assertThrows(
|
||||||
const [n, err] = decode(new TextEncoder().encode(input as string), out);
|
() => decode(new TextEncoder().encode(input as string)),
|
||||||
assertEquals(
|
Error,
|
||||||
new TextDecoder("ascii").decode(out.slice(0, n)),
|
(expectedErr as Error).message
|
||||||
output as string
|
|
||||||
);
|
);
|
||||||
assertEquals(err, expectedErr);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -161,8 +141,7 @@ Deno.test({
|
||||||
Deno.test({
|
Deno.test({
|
||||||
name: "[encoding.hex] decodeString error",
|
name: "[encoding.hex] decodeString error",
|
||||||
fn(): void {
|
fn(): void {
|
||||||
for (const [input, output, expectedErr] of errCases) {
|
for (const [input, expectedErr] of errCases) {
|
||||||
if (expectedErr) {
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => {
|
(): void => {
|
||||||
decodeString(input as string);
|
decodeString(input as string);
|
||||||
|
@ -170,10 +149,6 @@ Deno.test({
|
||||||
Error,
|
Error,
|
||||||
(expectedErr as Error).message
|
(expectedErr as Error).message
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
const out = decodeString(input as string);
|
|
||||||
assertEquals(new TextDecoder("ascii").decode(out), output as string);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue