@btoon/js 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/dist/cjs/constants.d.ts +34 -0
- package/dist/cjs/constants.js +32 -0
- package/dist/cjs/core.d.ts +15 -0
- package/dist/cjs/core.js +275 -0
- package/dist/cjs/decode.d.ts +10 -0
- package/dist/cjs/decode.js +554 -0
- package/dist/cjs/encode.d.ts +2 -0
- package/dist/cjs/encode.js +946 -0
- package/dist/cjs/errors.d.ts +53 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +86 -0
- package/dist/cjs/inflate.d.ts +9 -0
- package/dist/cjs/inflate.js +353 -0
- package/dist/cjs/node.d.ts +64 -0
- package/dist/cjs/node.js +82 -0
- package/dist/cjs/options.d.ts +95 -0
- package/dist/cjs/options.js +128 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform-node.d.ts +3 -0
- package/dist/cjs/platform-node.js +132 -0
- package/dist/cjs/platform-portable.d.ts +7 -0
- package/dist/cjs/platform-portable.js +16 -0
- package/dist/cjs/sha256.d.ts +5 -0
- package/dist/cjs/sha256.js +141 -0
- package/dist/cjs/utf8.d.ts +19 -0
- package/dist/cjs/utf8.js +107 -0
- package/dist/cjs/values.d.ts +58 -0
- package/dist/cjs/values.js +129 -0
- package/dist/cjs/web.d.ts +25 -0
- package/dist/cjs/web.js +151 -0
- package/dist/esm/constants.d.ts +34 -0
- package/dist/esm/constants.js +29 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +269 -0
- package/dist/esm/decode.d.ts +10 -0
- package/dist/esm/decode.js +549 -0
- package/dist/esm/encode.d.ts +2 -0
- package/dist/esm/encode.js +943 -0
- package/dist/esm/errors.d.ts +53 -0
- package/dist/esm/errors.js +39 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +74 -0
- package/dist/esm/inflate.d.ts +9 -0
- package/dist/esm/inflate.js +349 -0
- package/dist/esm/node.d.ts +64 -0
- package/dist/esm/node.js +70 -0
- package/dist/esm/options.d.ts +95 -0
- package/dist/esm/options.js +124 -0
- package/dist/esm/platform-node.d.ts +3 -0
- package/dist/esm/platform-node.js +96 -0
- package/dist/esm/platform-portable.d.ts +7 -0
- package/dist/esm/platform-portable.js +13 -0
- package/dist/esm/sha256.d.ts +5 -0
- package/dist/esm/sha256.js +136 -0
- package/dist/esm/utf8.d.ts +19 -0
- package/dist/esm/utf8.js +99 -0
- package/dist/esm/values.d.ts +58 -0
- package/dist/esm/values.js +120 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.js +143 -0
- package/package.json +85 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-readable error codes. All but `invalid_argument` are the decoder error codes
|
|
3
|
+
* of SPEC §7; `invalid_argument` reports a value or option the library cannot accept.
|
|
4
|
+
*/
|
|
5
|
+
export type BtoonErrorCode =
|
|
6
|
+
/** Input ends before a value is complete, or a length runs past the input. */
|
|
7
|
+
'truncated'
|
|
8
|
+
/** The reserved marker byte `0xc1`. */
|
|
9
|
+
| 'invalid_marker'
|
|
10
|
+
/** Bytes remain after the top-level value (strict mode). */
|
|
11
|
+
| 'trailing_data'
|
|
12
|
+
/** A string, map key or column name is not well-formed UTF-8 (or, when encoding, a string contains a lone surrogate). */
|
|
13
|
+
| 'invalid_utf8'
|
|
14
|
+
/** A map key is not a string. */
|
|
15
|
+
| 'non_string_key'
|
|
16
|
+
/** A map contains the same key twice (strict mode). */
|
|
17
|
+
| 'duplicate_key'
|
|
18
|
+
/** Nesting deeper than `maxDepth`, or a cyclic value. */
|
|
19
|
+
| 'max_depth'
|
|
20
|
+
/** A malformed BTOON extension payload (timestamp, date, datetime, bigint, vectors). */
|
|
21
|
+
| 'invalid_extension'
|
|
22
|
+
/** A malformed tabular payload. */
|
|
23
|
+
| 'invalid_tabular'
|
|
24
|
+
/** A malformed compression or signed envelope, or a corrupt compressed payload. */
|
|
25
|
+
| 'invalid_envelope'
|
|
26
|
+
/** A size above a configured limit (for example `maxDecompressedSize`). */
|
|
27
|
+
| 'size_limit'
|
|
28
|
+
/** A compression algorithm that is unknown or not available in this environment. */
|
|
29
|
+
| 'unsupported_compression'
|
|
30
|
+
/** HMAC verification failed, a signature was required but missing, or a signed input arrived without a key. */
|
|
31
|
+
| 'authentication_failed'
|
|
32
|
+
/** A value that cannot be encoded, or an invalid option. */
|
|
33
|
+
| 'invalid_argument';
|
|
34
|
+
/**
|
|
35
|
+
* The only error type thrown by this library's `encode`/`decode` functions (and the only
|
|
36
|
+
* rejection reason of the async variants). Branch on {@link BtoonError.code}, which is
|
|
37
|
+
* stable across BTOON implementations; the message is for humans and may change.
|
|
38
|
+
*/
|
|
39
|
+
export declare class BtoonError extends Error {
|
|
40
|
+
/** Machine-readable error code (SPEC §7, plus `invalid_argument`). */
|
|
41
|
+
readonly code: BtoonErrorCode;
|
|
42
|
+
constructor(code: BtoonErrorCode, message: string, options?: {
|
|
43
|
+
cause?: unknown;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Shorthand used internally: `throw fail('truncated', '...')`. */
|
|
47
|
+
export declare function fail(code: BtoonErrorCode, message: string, cause?: unknown): BtoonError;
|
|
48
|
+
/**
|
|
49
|
+
* True for the engine's "too much recursion" error. Raising `maxDepth` far above the
|
|
50
|
+
* default can exhaust the JavaScript call stack before the depth limit triggers; that
|
|
51
|
+
* case is reported as `max_depth` instead of leaking a RangeError.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isStackOverflow(e: unknown): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only error type thrown by this library's `encode`/`decode` functions (and the only
|
|
3
|
+
* rejection reason of the async variants). Branch on {@link BtoonError.code}, which is
|
|
4
|
+
* stable across BTOON implementations; the message is for humans and may change.
|
|
5
|
+
*/
|
|
6
|
+
export class BtoonError extends Error {
|
|
7
|
+
constructor(code, message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'BtoonError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
if (options !== undefined && 'cause' in options) {
|
|
12
|
+
Object.defineProperty(this, 'cause', {
|
|
13
|
+
value: options.cause,
|
|
14
|
+
writable: true,
|
|
15
|
+
enumerable: false,
|
|
16
|
+
configurable: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Shorthand used internally: `throw fail('truncated', '...')`. */
|
|
22
|
+
export function fail(code, message, cause) {
|
|
23
|
+
return cause === undefined ? new BtoonError(code, message) : new BtoonError(code, message, { cause });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* True for the engine's "too much recursion" error. Raising `maxDepth` far above the
|
|
27
|
+
* default can exhaust the JavaScript call stack before the depth limit triggers; that
|
|
28
|
+
* case is reported as `max_depth` instead of leaking a RangeError.
|
|
29
|
+
*/
|
|
30
|
+
export function isStackOverflow(e) {
|
|
31
|
+
if (e instanceof BtoonError)
|
|
32
|
+
return false;
|
|
33
|
+
if (typeof e !== 'object' || e === null)
|
|
34
|
+
return false;
|
|
35
|
+
const err = e;
|
|
36
|
+
if (err.name !== 'RangeError' && err.name !== 'InternalError')
|
|
37
|
+
return false;
|
|
38
|
+
return typeof err.message === 'string' && /call stack|recursion/i.test(err.message);
|
|
39
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BTOON for JavaScript and TypeScript — portable entry point (browsers, Deno, Bun-for-web,
|
|
3
|
+
* Cloudflare Workers and other edge runtimes). It imports no `node:` modules.
|
|
4
|
+
*
|
|
5
|
+
* Synchronous `decode` handles zlib compression envelopes (pure-JS inflate) and signed
|
|
6
|
+
* envelopes (pure-JS HMAC-SHA256) everywhere; synchronous compression needs a codec from
|
|
7
|
+
* `options.codecs`, while `encodeAsync` uses `CompressionStream`. Node.js resolves the
|
|
8
|
+
* package to a sibling entry with the same API that uses `node:zlib` and `node:crypto`.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import { type BinaryInput } from './core.js';
|
|
13
|
+
import type { DecodeOptions, EncodeOptions } from './options.js';
|
|
14
|
+
export { SPEC_VERSION, VERSION } from './constants.js';
|
|
15
|
+
export type { BinaryInput } from './core.js';
|
|
16
|
+
export { BtoonError, type BtoonErrorCode } from './errors.js';
|
|
17
|
+
export type { Codec, CodecRegistry, CompressionAlgorithm, DecodeOptions, EncodeOptions } from './options.js';
|
|
18
|
+
export { Ext, Timestamp } from './values.js';
|
|
19
|
+
/**
|
|
20
|
+
* Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
|
|
21
|
+
* in every conforming implementation.
|
|
22
|
+
*
|
|
23
|
+
* Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
|
|
24
|
+
* everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
|
|
25
|
+
* `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
|
|
26
|
+
* typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
|
|
27
|
+
* arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
|
|
28
|
+
* `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
|
|
29
|
+
* the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
|
|
30
|
+
* (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
|
|
31
|
+
* `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
|
|
32
|
+
* rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
|
|
33
|
+
*
|
|
34
|
+
* With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
|
|
35
|
+
* envelope, layered as sign(compress(document)).
|
|
36
|
+
*
|
|
37
|
+
* @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
|
|
38
|
+
* for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
|
|
39
|
+
* with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
|
|
40
|
+
* implementation here (see `encodeAsync` and `options.codecs`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function encode(value: unknown, options?: EncodeOptions): Uint8Array<ArrayBuffer>;
|
|
43
|
+
/**
|
|
44
|
+
* Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
|
|
45
|
+
* no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
|
|
46
|
+
* trailing bytes. Compression envelopes are decompressed (with the output capped at the
|
|
47
|
+
* declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
|
|
48
|
+
* anything is parsed.
|
|
49
|
+
*
|
|
50
|
+
* Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
|
|
51
|
+
* `JSON.parse`); it never changes an object's prototype.
|
|
52
|
+
*
|
|
53
|
+
* @throws {BtoonError} with the SPEC §7 error code describing the problem.
|
|
54
|
+
*/
|
|
55
|
+
export declare function decode(data: BinaryInput, options?: DecodeOptions): unknown;
|
|
56
|
+
/**
|
|
57
|
+
* Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
|
|
58
|
+
* the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
|
|
59
|
+
* Rejects with a {@link BtoonError}.
|
|
60
|
+
*/
|
|
61
|
+
export declare function encodeAsync(value: unknown, options?: EncodeOptions): Promise<Uint8Array<ArrayBuffer>>;
|
|
62
|
+
/**
|
|
63
|
+
* Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
|
|
64
|
+
* supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
|
|
65
|
+
* to the synchronous implementations. The input is copied first, so the caller may reuse
|
|
66
|
+
* its buffer immediately. Rejects with a {@link BtoonError}.
|
|
67
|
+
*/
|
|
68
|
+
export declare function decodeAsync(data: BinaryInput, options?: DecodeOptions): Promise<unknown>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BTOON for JavaScript and TypeScript — portable entry point (browsers, Deno, Bun-for-web,
|
|
3
|
+
* Cloudflare Workers and other edge runtimes). It imports no `node:` modules.
|
|
4
|
+
*
|
|
5
|
+
* Synchronous `decode` handles zlib compression envelopes (pure-JS inflate) and signed
|
|
6
|
+
* envelopes (pure-JS HMAC-SHA256) everywhere; synchronous compression needs a codec from
|
|
7
|
+
* `options.codecs`, while `encodeAsync` uses `CompressionStream`. Node.js resolves the
|
|
8
|
+
* package to a sibling entry with the same API that uses `node:zlib` and `node:crypto`.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import { decodeAsyncWith, decodeWith, encodeAsyncWith, encodeWith } from './core.js';
|
|
13
|
+
import { portablePlatform } from './platform-portable.js';
|
|
14
|
+
export { SPEC_VERSION, VERSION } from './constants.js';
|
|
15
|
+
export { BtoonError } from './errors.js';
|
|
16
|
+
export { Ext, Timestamp } from './values.js';
|
|
17
|
+
/**
|
|
18
|
+
* Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
|
|
19
|
+
* in every conforming implementation.
|
|
20
|
+
*
|
|
21
|
+
* Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
|
|
22
|
+
* everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
|
|
23
|
+
* `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
|
|
24
|
+
* typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
|
|
25
|
+
* arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
|
|
26
|
+
* `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
|
|
27
|
+
* the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
|
|
28
|
+
* (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
|
|
29
|
+
* `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
|
|
30
|
+
* rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
|
|
31
|
+
*
|
|
32
|
+
* With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
|
|
33
|
+
* envelope, layered as sign(compress(document)).
|
|
34
|
+
*
|
|
35
|
+
* @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
|
|
36
|
+
* for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
|
|
37
|
+
* with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
|
|
38
|
+
* implementation here (see `encodeAsync` and `options.codecs`).
|
|
39
|
+
*/
|
|
40
|
+
export function encode(value, options) {
|
|
41
|
+
return encodeWith(value, options, portablePlatform);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
|
|
45
|
+
* no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
|
|
46
|
+
* trailing bytes. Compression envelopes are decompressed (with the output capped at the
|
|
47
|
+
* declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
|
|
48
|
+
* anything is parsed.
|
|
49
|
+
*
|
|
50
|
+
* Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
|
|
51
|
+
* `JSON.parse`); it never changes an object's prototype.
|
|
52
|
+
*
|
|
53
|
+
* @throws {BtoonError} with the SPEC §7 error code describing the problem.
|
|
54
|
+
*/
|
|
55
|
+
export function decode(data, options) {
|
|
56
|
+
return decodeWith(data, options, portablePlatform);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
|
|
60
|
+
* the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
|
|
61
|
+
* Rejects with a {@link BtoonError}.
|
|
62
|
+
*/
|
|
63
|
+
export function encodeAsync(value, options) {
|
|
64
|
+
return encodeAsyncWith(value, options, portablePlatform);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
|
|
68
|
+
* supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
|
|
69
|
+
* to the synchronous implementations. The input is copied first, so the caller may reuse
|
|
70
|
+
* its buffer immediately. Rejects with a {@link BtoonError}.
|
|
71
|
+
*/
|
|
72
|
+
export function decodeAsync(data, options) {
|
|
73
|
+
return decodeAsyncWith(data, options, portablePlatform);
|
|
74
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decompresses an RFC 1950 zlib stream that must expand to at most `limit` bytes. Throws
|
|
3
|
+
* `invalid_envelope` for corrupt or truncated input, trailing bytes after the stream, or
|
|
4
|
+
* output beyond `limit`. The output buffer grows geometrically, so a lying size header
|
|
5
|
+
* cannot force a large allocation for a small input.
|
|
6
|
+
*/
|
|
7
|
+
export declare function inflateZlib(input: Uint8Array, limit: number): Uint8Array;
|
|
8
|
+
/** Adler-32 checksum of `d[start, end)` (RFC 1950). */
|
|
9
|
+
export declare function adler32(d: Uint8Array, start?: number, end?: number): number;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// A small, bounded zlib (RFC 1950) / DEFLATE (RFC 1951) decoder for runtimes without a
|
|
2
|
+
// synchronous native one (browsers, Deno, edge workers). It accepts exactly what zlib's
|
|
3
|
+
// inflate accepts (same rules for incomplete and over-subscribed Huffman codes, distance
|
|
4
|
+
// limits and the Adler-32 trailer), rejects trailing bytes after the stream, and never
|
|
5
|
+
// writes past the declared output size. Work per input byte is bounded: Huffman tables
|
|
6
|
+
// cost at most 512 entries to build, and longer codes are decoded bit by bit.
|
|
7
|
+
import { fail } from './errors.js';
|
|
8
|
+
const FAST_BITS = 9;
|
|
9
|
+
const FAST_SIZE = 1 << FAST_BITS;
|
|
10
|
+
const FAST_MASK = FAST_SIZE - 1;
|
|
11
|
+
const LEN_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
|
|
12
|
+
const LEN_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
|
|
13
|
+
const DIST_BASE = [
|
|
14
|
+
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
|
15
|
+
8193, 12289, 16385, 24577,
|
|
16
|
+
];
|
|
17
|
+
const DIST_EXTRA = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
|
|
18
|
+
const CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
19
|
+
function corrupt(what) {
|
|
20
|
+
return fail('invalid_envelope', `zlib payload ${what}`);
|
|
21
|
+
}
|
|
22
|
+
/** Canonical Huffman code: counts per length, symbols in canonical order, a 9-bit fast table. */
|
|
23
|
+
class Huffman {
|
|
24
|
+
constructor(n) {
|
|
25
|
+
this.count = new Uint16Array(16);
|
|
26
|
+
/** Indexed by the next 9 input bits: (symbol << 4) | length, or 0 for "decode slowly". */
|
|
27
|
+
this.fast = new Uint16Array(FAST_SIZE);
|
|
28
|
+
/** Longest code length in use (0 = no codes). */
|
|
29
|
+
this.max = 0;
|
|
30
|
+
this.symbol = new Uint16Array(n);
|
|
31
|
+
}
|
|
32
|
+
/** Builds from `n` code lengths; returns unused code space (0 = complete, < 0 = over-subscribed). */
|
|
33
|
+
build(lengths, offset, n) {
|
|
34
|
+
const count = this.count;
|
|
35
|
+
count.fill(0);
|
|
36
|
+
for (let i = 0; i < n; i++)
|
|
37
|
+
count[lengths[offset + i]]++;
|
|
38
|
+
count[0] = 0;
|
|
39
|
+
let max = 0;
|
|
40
|
+
let left = 1;
|
|
41
|
+
for (let len = 1; len <= 15; len++) {
|
|
42
|
+
left <<= 1;
|
|
43
|
+
left -= count[len];
|
|
44
|
+
if (left < 0)
|
|
45
|
+
return left;
|
|
46
|
+
if (count[len])
|
|
47
|
+
max = len;
|
|
48
|
+
}
|
|
49
|
+
this.max = max;
|
|
50
|
+
const offs = new Uint16Array(16);
|
|
51
|
+
for (let len = 1; len < 15; len++)
|
|
52
|
+
offs[len + 1] = offs[len] + count[len];
|
|
53
|
+
for (let sym = 0; sym < n; sym++) {
|
|
54
|
+
const len = lengths[offset + sym];
|
|
55
|
+
if (len)
|
|
56
|
+
this.symbol[offs[len]++] = sym;
|
|
57
|
+
}
|
|
58
|
+
const fast = this.fast;
|
|
59
|
+
fast.fill(0);
|
|
60
|
+
let code = 0;
|
|
61
|
+
let idx = 0;
|
|
62
|
+
for (let len = 1; len <= FAST_BITS; len++) {
|
|
63
|
+
for (let k = count[len]; k > 0; k--) {
|
|
64
|
+
let rev = 0;
|
|
65
|
+
for (let b = 0; b < len; b++)
|
|
66
|
+
rev |= ((code >> b) & 1) << (len - 1 - b);
|
|
67
|
+
const entry = (this.symbol[idx] << 4) | len;
|
|
68
|
+
for (let j = rev; j < FAST_SIZE; j += 1 << len)
|
|
69
|
+
fast[j] = entry;
|
|
70
|
+
idx++;
|
|
71
|
+
code++;
|
|
72
|
+
}
|
|
73
|
+
code <<= 1;
|
|
74
|
+
}
|
|
75
|
+
return left;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let fixedLit = null;
|
|
79
|
+
let fixedDist = null;
|
|
80
|
+
function fixedTables() {
|
|
81
|
+
if (fixedLit === null || fixedDist === null) {
|
|
82
|
+
const l = new Uint8Array(288);
|
|
83
|
+
l.fill(8, 0, 144);
|
|
84
|
+
l.fill(9, 144, 256);
|
|
85
|
+
l.fill(7, 256, 280);
|
|
86
|
+
l.fill(8, 280, 288);
|
|
87
|
+
fixedLit = new Huffman(288);
|
|
88
|
+
fixedLit.build(l, 0, 288);
|
|
89
|
+
const d = new Uint8Array(30).fill(5);
|
|
90
|
+
fixedDist = new Huffman(30);
|
|
91
|
+
fixedDist.build(d, 0, 30); // incomplete (30 of 32 codes), as in zlib
|
|
92
|
+
}
|
|
93
|
+
return [fixedLit, fixedDist];
|
|
94
|
+
}
|
|
95
|
+
class Inflater {
|
|
96
|
+
constructor(input, limit) {
|
|
97
|
+
this.inPos = 0;
|
|
98
|
+
this.bitBuf = 0;
|
|
99
|
+
this.bitCnt = 0;
|
|
100
|
+
this.outPos = 0;
|
|
101
|
+
this.input = input;
|
|
102
|
+
this.limit = limit;
|
|
103
|
+
this.out = new Uint8Array(Math.min(limit, Math.max(64 * 1024, input.length * 4)));
|
|
104
|
+
}
|
|
105
|
+
bits(n) {
|
|
106
|
+
while (this.bitCnt < n) {
|
|
107
|
+
if (this.inPos >= this.input.length)
|
|
108
|
+
throw corrupt('is truncated');
|
|
109
|
+
this.bitBuf |= this.input[this.inPos++] << this.bitCnt;
|
|
110
|
+
this.bitCnt += 8;
|
|
111
|
+
}
|
|
112
|
+
const v = this.bitBuf & ((1 << n) - 1);
|
|
113
|
+
this.bitBuf >>>= n;
|
|
114
|
+
this.bitCnt -= n;
|
|
115
|
+
return v;
|
|
116
|
+
}
|
|
117
|
+
/** Next Huffman symbol, or -1 if the bits match no code. */
|
|
118
|
+
sym(h) {
|
|
119
|
+
while (this.bitCnt < FAST_BITS && this.inPos < this.input.length) {
|
|
120
|
+
this.bitBuf |= this.input[this.inPos++] << this.bitCnt;
|
|
121
|
+
this.bitCnt += 8;
|
|
122
|
+
}
|
|
123
|
+
const e = h.fast[this.bitBuf & FAST_MASK];
|
|
124
|
+
const len = e & 15;
|
|
125
|
+
if (len !== 0) {
|
|
126
|
+
if (len > this.bitCnt)
|
|
127
|
+
throw corrupt('is truncated');
|
|
128
|
+
this.bitBuf >>>= len;
|
|
129
|
+
this.bitCnt -= len;
|
|
130
|
+
return e >> 4;
|
|
131
|
+
}
|
|
132
|
+
// Longer code (or none): canonical decoding, one bit at a time.
|
|
133
|
+
const count = h.count;
|
|
134
|
+
let code = 0;
|
|
135
|
+
let first = 0;
|
|
136
|
+
let index = 0;
|
|
137
|
+
for (let l = 1; l <= 15; l++) {
|
|
138
|
+
code |= this.bits(1);
|
|
139
|
+
const c = count[l];
|
|
140
|
+
if (code - first < c)
|
|
141
|
+
return h.symbol[index + code - first];
|
|
142
|
+
index += c;
|
|
143
|
+
first = (first + c) << 1;
|
|
144
|
+
code <<= 1;
|
|
145
|
+
}
|
|
146
|
+
return -1;
|
|
147
|
+
}
|
|
148
|
+
room(n) {
|
|
149
|
+
const need = this.outPos + n;
|
|
150
|
+
if (need <= this.out.length)
|
|
151
|
+
return;
|
|
152
|
+
if (need > this.limit)
|
|
153
|
+
throw corrupt('decompresses to more than the declared size');
|
|
154
|
+
const next = new Uint8Array(Math.min(this.limit, Math.max(this.out.length * 2, need)));
|
|
155
|
+
next.set(this.out.subarray(0, this.outPos));
|
|
156
|
+
this.out = next;
|
|
157
|
+
}
|
|
158
|
+
/** Byte-aligns the bit reader and hands back whole bytes it had buffered. */
|
|
159
|
+
align() {
|
|
160
|
+
this.inPos -= this.bitCnt >> 3;
|
|
161
|
+
this.bitBuf = 0;
|
|
162
|
+
this.bitCnt = 0;
|
|
163
|
+
}
|
|
164
|
+
stored() {
|
|
165
|
+
this.align();
|
|
166
|
+
const inp = this.input;
|
|
167
|
+
const p = this.inPos;
|
|
168
|
+
if (p + 4 > inp.length)
|
|
169
|
+
throw corrupt('is truncated');
|
|
170
|
+
const len = inp[p] | (inp[p + 1] << 8);
|
|
171
|
+
const nlen = inp[p + 2] | (inp[p + 3] << 8);
|
|
172
|
+
if (len !== (~nlen & 0xffff))
|
|
173
|
+
throw corrupt('is corrupt (invalid stored block lengths)');
|
|
174
|
+
if (p + 4 + len > inp.length)
|
|
175
|
+
throw corrupt('is truncated');
|
|
176
|
+
this.room(len);
|
|
177
|
+
this.out.set(inp.subarray(p + 4, p + 4 + len), this.outPos);
|
|
178
|
+
this.outPos += len;
|
|
179
|
+
this.inPos = p + 4 + len;
|
|
180
|
+
}
|
|
181
|
+
dynamic(lit, dist, cl, lens) {
|
|
182
|
+
const nlen = this.bits(5) + 257;
|
|
183
|
+
const ndist = this.bits(5) + 1;
|
|
184
|
+
const ncode = this.bits(4) + 4;
|
|
185
|
+
if (nlen > 286 || ndist > 30)
|
|
186
|
+
throw corrupt('is corrupt (too many length or distance symbols)');
|
|
187
|
+
const clLens = new Uint8Array(19);
|
|
188
|
+
for (let i = 0; i < ncode; i++)
|
|
189
|
+
clLens[CL_ORDER[i]] = this.bits(3);
|
|
190
|
+
if (cl.build(clLens, 0, 19) !== 0)
|
|
191
|
+
throw corrupt('is corrupt (invalid code lengths set)');
|
|
192
|
+
const total = nlen + ndist;
|
|
193
|
+
let i = 0;
|
|
194
|
+
while (i < total) {
|
|
195
|
+
const s = this.sym(cl);
|
|
196
|
+
if (s < 16) {
|
|
197
|
+
if (s < 0)
|
|
198
|
+
throw corrupt('is corrupt (invalid code lengths set)');
|
|
199
|
+
lens[i++] = s;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
let rep;
|
|
203
|
+
let val = 0;
|
|
204
|
+
if (s === 16) {
|
|
205
|
+
if (i === 0)
|
|
206
|
+
throw corrupt('is corrupt (invalid bit length repeat)');
|
|
207
|
+
val = lens[i - 1];
|
|
208
|
+
rep = 3 + this.bits(2);
|
|
209
|
+
}
|
|
210
|
+
else if (s === 17) {
|
|
211
|
+
rep = 3 + this.bits(3);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
rep = 11 + this.bits(7);
|
|
215
|
+
}
|
|
216
|
+
if (i + rep > total)
|
|
217
|
+
throw corrupt('is corrupt (invalid bit length repeat)');
|
|
218
|
+
while (rep-- > 0)
|
|
219
|
+
lens[i++] = val;
|
|
220
|
+
}
|
|
221
|
+
if (lens[256] === 0)
|
|
222
|
+
throw corrupt('is corrupt (missing end-of-block code)');
|
|
223
|
+
// Incomplete codes are allowed only when they consist of a single 1-bit code (zlib).
|
|
224
|
+
let left = lit.build(lens, 0, nlen);
|
|
225
|
+
if (left < 0 || (left > 0 && lit.max !== 1))
|
|
226
|
+
throw corrupt('is corrupt (invalid literal/lengths set)');
|
|
227
|
+
left = dist.build(lens, nlen, ndist);
|
|
228
|
+
if (left < 0 || (left > 0 && dist.max > 1))
|
|
229
|
+
throw corrupt('is corrupt (invalid distances set)');
|
|
230
|
+
this.codes(lit, dist);
|
|
231
|
+
}
|
|
232
|
+
codes(lit, dist) {
|
|
233
|
+
for (;;) {
|
|
234
|
+
let s = this.sym(lit);
|
|
235
|
+
if (s < 256) {
|
|
236
|
+
if (s < 0)
|
|
237
|
+
throw corrupt('is corrupt (invalid literal/length code)');
|
|
238
|
+
if (this.outPos >= this.out.length)
|
|
239
|
+
this.room(1);
|
|
240
|
+
this.out[this.outPos++] = s;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (s === 256)
|
|
244
|
+
return;
|
|
245
|
+
s -= 257;
|
|
246
|
+
if (s >= 29)
|
|
247
|
+
throw corrupt('is corrupt (invalid literal/length code)');
|
|
248
|
+
const len = LEN_BASE[s] + this.bits(LEN_EXTRA[s]);
|
|
249
|
+
const ds = this.sym(dist);
|
|
250
|
+
if (ds < 0 || ds >= 30)
|
|
251
|
+
throw corrupt('is corrupt (invalid distance code)');
|
|
252
|
+
const d = DIST_BASE[ds] + this.bits(DIST_EXTRA[ds]);
|
|
253
|
+
if (d > this.outPos)
|
|
254
|
+
throw corrupt('is corrupt (invalid distance too far back)');
|
|
255
|
+
this.room(len);
|
|
256
|
+
const out = this.out;
|
|
257
|
+
let to = this.outPos;
|
|
258
|
+
let from = to - d;
|
|
259
|
+
if (d >= len && len > 16) {
|
|
260
|
+
out.copyWithin(to, from, from + len);
|
|
261
|
+
to += len;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
for (const end = to + len; to < end;)
|
|
265
|
+
out[to++] = out[from++];
|
|
266
|
+
}
|
|
267
|
+
this.outPos = to;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
run() {
|
|
271
|
+
const inp = this.input;
|
|
272
|
+
if (inp.length < 2)
|
|
273
|
+
throw corrupt('is truncated');
|
|
274
|
+
const cmf = inp[0];
|
|
275
|
+
const flg = inp[1];
|
|
276
|
+
if (((cmf << 8) | flg) % 31 !== 0)
|
|
277
|
+
throw corrupt('is corrupt (incorrect header check)');
|
|
278
|
+
if ((cmf & 0x0f) !== 8)
|
|
279
|
+
throw corrupt('is corrupt (unknown compression method)');
|
|
280
|
+
if (cmf >> 4 > 7)
|
|
281
|
+
throw corrupt('is corrupt (invalid window size)');
|
|
282
|
+
if (flg & 0x20)
|
|
283
|
+
throw corrupt('needs a preset dictionary, which BTOON does not support');
|
|
284
|
+
this.inPos = 2;
|
|
285
|
+
let lit = null;
|
|
286
|
+
let dist = null;
|
|
287
|
+
let cl = null;
|
|
288
|
+
let lens = null;
|
|
289
|
+
let last = 0;
|
|
290
|
+
while (!last) {
|
|
291
|
+
last = this.bits(1);
|
|
292
|
+
const type = this.bits(2);
|
|
293
|
+
if (type === 0) {
|
|
294
|
+
this.stored();
|
|
295
|
+
}
|
|
296
|
+
else if (type === 1) {
|
|
297
|
+
const [fl, fd] = fixedTables();
|
|
298
|
+
this.codes(fl, fd);
|
|
299
|
+
}
|
|
300
|
+
else if (type === 2) {
|
|
301
|
+
if (lit === null) {
|
|
302
|
+
lit = new Huffman(286);
|
|
303
|
+
dist = new Huffman(30);
|
|
304
|
+
cl = new Huffman(19);
|
|
305
|
+
lens = new Uint8Array(286 + 30);
|
|
306
|
+
}
|
|
307
|
+
this.dynamic(lit, dist, cl, lens);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
throw corrupt('is corrupt (invalid block type)');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
this.align();
|
|
314
|
+
const p = this.inPos;
|
|
315
|
+
if (p + 4 > inp.length)
|
|
316
|
+
throw corrupt('is truncated');
|
|
317
|
+
const expected = ((inp[p] << 24) | (inp[p + 1] << 16) | (inp[p + 2] << 8) | inp[p + 3]) >>> 0;
|
|
318
|
+
if (adler32(this.out, 0, this.outPos) !== expected)
|
|
319
|
+
throw corrupt('is corrupt (incorrect data check)');
|
|
320
|
+
if (p + 4 !== inp.length)
|
|
321
|
+
throw corrupt('has trailing bytes');
|
|
322
|
+
return this.out.subarray(0, this.outPos);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Decompresses an RFC 1950 zlib stream that must expand to at most `limit` bytes. Throws
|
|
327
|
+
* `invalid_envelope` for corrupt or truncated input, trailing bytes after the stream, or
|
|
328
|
+
* output beyond `limit`. The output buffer grows geometrically, so a lying size header
|
|
329
|
+
* cannot force a large allocation for a small input.
|
|
330
|
+
*/
|
|
331
|
+
export function inflateZlib(input, limit) {
|
|
332
|
+
return new Inflater(input, limit).run();
|
|
333
|
+
}
|
|
334
|
+
/** Adler-32 checksum of `d[start, end)` (RFC 1950). */
|
|
335
|
+
export function adler32(d, start = 0, end = d.length) {
|
|
336
|
+
let a = 1;
|
|
337
|
+
let b = 0;
|
|
338
|
+
let i = start;
|
|
339
|
+
while (i < end) {
|
|
340
|
+
const stop = Math.min(end, i + 3800); // keeps b below 2^31 between reductions
|
|
341
|
+
for (; i < stop; i++) {
|
|
342
|
+
a += d[i];
|
|
343
|
+
b += a;
|
|
344
|
+
}
|
|
345
|
+
a %= 65521;
|
|
346
|
+
b %= 65521;
|
|
347
|
+
}
|
|
348
|
+
return ((b << 16) | a) >>> 0;
|
|
349
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BTOON for JavaScript and TypeScript — Node.js entry point (also used by Bun). Same API
|
|
3
|
+
* as the portable entry; synchronous compression and signing use `node:zlib` (zlib,
|
|
4
|
+
* Brotli, and Zstandard where Node.js has it) and `node:crypto`.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { type BinaryInput } from './core.js';
|
|
9
|
+
import type { DecodeOptions, EncodeOptions } from './options.js';
|
|
10
|
+
export { SPEC_VERSION, VERSION } from './constants.js';
|
|
11
|
+
export type { BinaryInput } from './core.js';
|
|
12
|
+
export { BtoonError, type BtoonErrorCode } from './errors.js';
|
|
13
|
+
export type { Codec, CodecRegistry, CompressionAlgorithm, DecodeOptions, EncodeOptions } from './options.js';
|
|
14
|
+
export { Ext, Timestamp } from './values.js';
|
|
15
|
+
/**
|
|
16
|
+
* Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
|
|
17
|
+
* in every conforming implementation.
|
|
18
|
+
*
|
|
19
|
+
* Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
|
|
20
|
+
* everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
|
|
21
|
+
* `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
|
|
22
|
+
* typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
|
|
23
|
+
* arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
|
|
24
|
+
* `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
|
|
25
|
+
* the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
|
|
26
|
+
* (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
|
|
27
|
+
* `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
|
|
28
|
+
* rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
|
|
29
|
+
*
|
|
30
|
+
* With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
|
|
31
|
+
* envelope, layered as sign(compress(document)).
|
|
32
|
+
*
|
|
33
|
+
* @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
|
|
34
|
+
* for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
|
|
35
|
+
* with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
|
|
36
|
+
* implementation here (see `encodeAsync` and `options.codecs`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function encode(value: unknown, options?: EncodeOptions): Uint8Array<ArrayBuffer>;
|
|
39
|
+
/**
|
|
40
|
+
* Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
|
|
41
|
+
* no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
|
|
42
|
+
* trailing bytes. Compression envelopes are decompressed (with the output capped at the
|
|
43
|
+
* declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
|
|
44
|
+
* anything is parsed.
|
|
45
|
+
*
|
|
46
|
+
* Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
|
|
47
|
+
* `JSON.parse`); it never changes an object's prototype.
|
|
48
|
+
*
|
|
49
|
+
* @throws {BtoonError} with the SPEC §7 error code describing the problem.
|
|
50
|
+
*/
|
|
51
|
+
export declare function decode(data: BinaryInput, options?: DecodeOptions): unknown;
|
|
52
|
+
/**
|
|
53
|
+
* Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
|
|
54
|
+
* the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
|
|
55
|
+
* Rejects with a {@link BtoonError}.
|
|
56
|
+
*/
|
|
57
|
+
export declare function encodeAsync(value: unknown, options?: EncodeOptions): Promise<Uint8Array<ArrayBuffer>>;
|
|
58
|
+
/**
|
|
59
|
+
* Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
|
|
60
|
+
* supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
|
|
61
|
+
* to the synchronous implementations. The input is copied first, so the caller may reuse
|
|
62
|
+
* its buffer immediately. Rejects with a {@link BtoonError}.
|
|
63
|
+
*/
|
|
64
|
+
export declare function decodeAsync(data: BinaryInput, options?: DecodeOptions): Promise<unknown>;
|