diff options
-rw-r--r-- | std/encoding/hex.ts | 97 | ||||
-rw-r--r-- | std/encoding/hex_test.ts | 77 |
2 files changed, 56 insertions, 118 deletions
diff --git a/std/encoding/hex.ts b/std/encoding/hex.ts index c246f7182..729df6c49 100644 --- a/std/encoding/hex.ts +++ b/std/encoding/hex.ts @@ -18,17 +18,16 @@ 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]; +// fromHexChar converts a hex character into its value. +function fromHexChar(byte: number): number { + // '0' <= byte && byte <= '9' + if (48 <= byte && byte <= 57) return byte - 48; + // 'a' <= byte && byte <= 'f' + if (97 <= byte && byte <= 102) return byte - 97 + 10; + // 'A' <= byte && byte <= 'F' + if (65 <= byte && byte <= 70) return byte - 65 + 10; + + throw errInvalidByte(byte); } /** @@ -41,24 +40,17 @@ export function encodedLen(n: number): number { } /** - * 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 + * Encode encodes `src` into `encodedLen(src.length)` bytes. * @param src */ -export function encode(src: Uint8Array, dst: 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++) { +export function encode(src: Uint8Array): Uint8Array { + const dst = new Uint8Array(encodedLen(src.length)); + for (let i = 0; i < dst.length; i++) { const v = src[i]; dst[i * 2] = hextable[v >> 4]; 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 */ export function encodeToString(src: Uint8Array): string { - const dest = new Uint8Array(encodedLen(src.length)); - encode(src, dest); - return new TextDecoder().decode(dest); + return new TextDecoder().decode(encode(src)); } /** * 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 + * If the input is malformed an error will be thrown * the error. - * @param dst * @param src */ -export function decode( - src: Uint8Array, - dst: Uint8Array -): [number, Error | void] { - let 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])]; - } - +export function decode(src: Uint8Array): Uint8Array { + const dst = new Uint8Array(decodedLen(src.length)); + for (let i = 0; i < dst.length; i++) { + const a = fromHexChar(src[i * 2]); + const b = fromHexChar(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()]; + fromHexChar(src[dst.length * 2]); + throw 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`. * @param x */ 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 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` + * If the input is malformed, DecodeString will throw an error. + * @param s the `string` 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); + return decode(new TextEncoder().encode(s)); } diff --git a/std/encoding/hex_test.ts b/std/encoding/hex_test.ts index ad1ffc2d2..a42238e53 100644 --- a/std/encoding/hex_test.ts +++ b/std/encoding/hex_test.ts @@ -34,15 +34,14 @@ const testCases = [ 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()], + ["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()], ]; Deno.test({ @@ -62,30 +61,15 @@ Deno.test({ { const srcStr = "abc"; const src = new TextEncoder().encode(srcStr); - const dest = new Uint8Array(encodedLen(src.length)); - const int = encode(src, dest); + const dest = encode(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(src, dest); - }, - Error, - "Out of index." - ); + assertEquals(dest.length, 6); } for (const [enc, dec] of testCases) { - const dest = new Uint8Array(encodedLen(dec.length)); const src = new Uint8Array(dec as number[]); - const n = encode(src, dest); - assertEquals(dest.length, n); + const dest = encode(src); + assertEquals(dest.length, src.length * 2); assertEquals(new TextDecoder().decode(dest), enc); } }, @@ -123,10 +107,8 @@ Deno.test({ 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(src, dest); - assertEquals(err, undefined); + const dest = decode(src); assertEquals(Array.from(dest), Array.from(dec as number[])); } }, @@ -146,14 +128,12 @@ Deno.test({ Deno.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(new TextEncoder().encode(input as string), out); - assertEquals( - new TextDecoder("ascii").decode(out.slice(0, n)), - output as string + for (const [input, expectedErr] of errCases) { + assertThrows( + () => decode(new TextEncoder().encode(input as string)), + Error, + (expectedErr as Error).message ); - assertEquals(err, expectedErr); } }, }); @@ -161,19 +141,14 @@ Deno.test({ Deno.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); - } + for (const [input, expectedErr] of errCases) { + assertThrows( + (): void => { + decodeString(input as string); + }, + Error, + (expectedErr as Error).message + ); } }, }); |