From 185c233b2f9b2e2e3475ff4134d31c8901bb836a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 19 Nov 2023 16:02:18 +0100 Subject: [PATCH] Refactor base64 --- .../javascript/packages/core/src/base64.ts | 46 +++++++++++++++++++ .../javascript/packages/core/src/bytes.ts | 11 ++++- .../javascript/packages/core/src/reader.ts | 27 +---------- .../javascript/packages/core/src/runtime.ts | 1 + .../javascript/packages/core/src/writer.ts | 23 +--------- 5 files changed, 59 insertions(+), 49 deletions(-) create mode 100644 implementations/javascript/packages/core/src/base64.ts diff --git a/implementations/javascript/packages/core/src/base64.ts b/implementations/javascript/packages/core/src/base64.ts new file mode 100644 index 0000000..16cba9a --- /dev/null +++ b/implementations/javascript/packages/core/src/base64.ts @@ -0,0 +1,46 @@ +const BASE64_DEC: {[key: string]: number} = {}; +[... 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'].forEach( + (c, i) => BASE64_DEC[c] = i); +BASE64_DEC['+'] = BASE64_DEC['-'] = 62; +BASE64_DEC['/'] = BASE64_DEC['_'] = 63; + +export function decodeBase64(s: string): Uint8Array { + const bs = new Uint8Array(Math.floor(s.length * 3/4)); + let i = 0; + let j = 0; + while (i < s.length) { + const v1 = BASE64_DEC[s[i++]]; + const v2 = BASE64_DEC[s[i++]]; + const v3 = BASE64_DEC[s[i++]]; + const v4 = BASE64_DEC[s[i++]]; + const v = (v1 << 18) | (v2 << 12) | (v3 << 6) | v4; + bs[j++] = (v >> 16) & 255; + if (v3 === void 0) break; + bs[j++] = (v >> 8) & 255; + if (v4 === void 0) break; + bs[j++] = v & 255; + } + return bs.subarray(0, j); +} + +const BASE64_ENC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +export function encodeBase64(bs: Uint8Array): string { + let s = ''; + let buffer = 0; + let bitcount = 0; + for (let b of bs) { + buffer = ((buffer & 0x3f) << 8) | b; + bitcount += 8; + while (bitcount >= 6) { + bitcount -= 6; + const v = (buffer >> bitcount) & 0x3f; + s = s + BASE64_ENC[v]; + } + } + if (bitcount > 0) { + const v = (buffer << (6 - bitcount)) & 0x3f; + s = s + BASE64_ENC[v]; + } + return s; +} diff --git a/implementations/javascript/packages/core/src/bytes.ts b/implementations/javascript/packages/core/src/bytes.ts index 3f09e8a..ff711e0 100644 --- a/implementations/javascript/packages/core/src/bytes.ts +++ b/implementations/javascript/packages/core/src/bytes.ts @@ -2,7 +2,8 @@ import { Tag } from './constants'; import { GenericEmbedded } from './embedded'; import { Encoder, Preservable } from './encoder'; import { Value } from './values'; -import { Writer, PreserveWritable } from './writer'; +import type { Writer, PreserveWritable } from './writer'; +import { decodeBase64, encodeBase64 } from './base64'; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); @@ -51,6 +52,10 @@ export class Bytes implements Preservable, PreserveWritable { return new Bytes(Uint8Array.of(...bytes)); } + static fromBase64(s: string): Bytes { + return new Bytes(decodeBase64(s)); + } + static fromHex(s: string): Bytes { if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string"); const result = new Bytes(s.length >> 1); @@ -139,6 +144,10 @@ export class Bytes implements Preservable, PreserveWritable { return Bytes.isBytes(v) ? v : void 0; } + toBase64(): string { + return encodeBase64(this._view); + } + toHex(digit = hexDigit): string { var nibbles = []; for (let i = 0; i < this.length; i++) { diff --git a/implementations/javascript/packages/core/src/reader.ts b/implementations/javascript/packages/core/src/reader.ts index 7561013..a780c74 100644 --- a/implementations/javascript/packages/core/src/reader.ts +++ b/implementations/javascript/packages/core/src/reader.ts @@ -155,7 +155,7 @@ export class ReaderState { if (c === ']') break; acc = acc + c; } - return decodeBase64(acc); + return Bytes.fromBase64(acc); } requireDelimiter(prefix: string): void { @@ -436,31 +436,6 @@ export class Reader { } } -const BASE64: {[key: string]: number} = {}; -[... 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'].forEach( - (c, i) => BASE64[c] = i); -BASE64['+'] = BASE64['-'] = 62; -BASE64['/'] = BASE64['_'] = 63; - -export function decodeBase64(s: string): Bytes { - const bs = new Uint8Array(Math.floor(s.length * 3/4)); - let i = 0; - let j = 0; - while (i < s.length) { - const v1 = BASE64[s[i++]]; - const v2 = BASE64[s[i++]]; - const v3 = BASE64[s[i++]]; - const v4 = BASE64[s[i++]]; - const v = (v1 << 18) | (v2 << 12) | (v3 << 6) | v4; - bs[j++] = (v >> 16) & 255; - if (v3 === void 0) break; - bs[j++] = (v >> 8) & 255; - if (v4 === void 0) break; - bs[j++] = v & 255; - } - return Bytes.from(bs.subarray(0, j)); -} - function isSpace(s: string): boolean { return ' \t\n\r'.indexOf(s) !== -1; } diff --git a/implementations/javascript/packages/core/src/runtime.ts b/implementations/javascript/packages/core/src/runtime.ts index 4b9909e..4433c97 100644 --- a/implementations/javascript/packages/core/src/runtime.ts +++ b/implementations/javascript/packages/core/src/runtime.ts @@ -1,4 +1,5 @@ export * from './annotated'; +export * from './base64'; export * from './bytes'; export * from './codec'; export * from './compound'; diff --git a/implementations/javascript/packages/core/src/writer.ts b/implementations/javascript/packages/core/src/writer.ts index 93c9d20..a89a19c 100644 --- a/implementations/javascript/packages/core/src/writer.ts +++ b/implementations/javascript/packages/core/src/writer.ts @@ -4,6 +4,7 @@ import type { GenericEmbedded, Embedded, EmbeddedTypeEncode } from "./embedded"; import { Encoder, EncoderState } from "./encoder"; import type { Value } from "./values"; import { NUMBER_RE } from './reader'; +import { encodeBase64 } from './base64'; export type Writable = Value | PreserveWritable | Iterable> | ArrayBufferView; @@ -205,28 +206,6 @@ export class WriterState { } } -const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - -export function encodeBase64(bs: Uint8Array): string { - let s = ''; - let buffer = 0; - let bitcount = 0; - for (let b of bs) { - buffer = ((buffer & 0x3f) << 8) | b; - bitcount += 8; - while (bitcount >= 6) { - bitcount -= 6; - const v = (buffer >> bitcount) & 0x3f; - s = s + BASE64[v]; - } - } - if (bitcount > 0) { - const v = (buffer << (6 - bitcount)) & 0x3f; - s = s + BASE64[v]; - } - return s; -} - export class Writer { state: WriterState; embeddedWrite: EmbeddedWriter;