@evolu/common 8.6.2 → 8.7.0
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/dist/src/Buffer.d.ts +67 -8
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +722 -15
- package/dist/src/local-first/Protocol.d.ts +14 -0
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +24 -31
- package/package.json +1 -2
- package/src/Buffer.ts +966 -22
- package/src/local-first/Protocol.ts +24 -36
package/src/Buffer.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Binary data handling and byte array utilities.
|
|
3
3
|
*
|
|
4
|
+
* Buffer-based decoding functions intentionally throw errors instead of
|
|
5
|
+
* returning {@link Result}. This is a deliberate micro-optimization for Evolu
|
|
6
|
+
* Protocol's hot paths: Result is inexpensive, but returning decoded values
|
|
7
|
+
* directly avoids its success-case allocation, and using `Error` objects
|
|
8
|
+
* preserves stack traces. In the future, we will try returning `Error` objects
|
|
9
|
+
* in `Result` values to measure the real-world performance impact.
|
|
10
|
+
*
|
|
4
11
|
* @module
|
|
5
12
|
*/
|
|
6
13
|
|
|
7
14
|
import type { Result } from "./Result.ts";
|
|
8
|
-
import {
|
|
15
|
+
import type { JsonValue, NonNegativeInt } from "./Type.ts";
|
|
9
16
|
export {
|
|
10
17
|
bytesToHex,
|
|
11
18
|
bytesToUtf8,
|
|
@@ -41,12 +48,7 @@ export class BufferError extends Error {
|
|
|
41
48
|
* reused within functions by leveraging `reset` to clear contents while
|
|
42
49
|
* preserving capacity, or `truncate` to adjust the length to a specific size,
|
|
43
50
|
* reducing the need for new allocations. Pass Buffers to `encode*` functions to
|
|
44
|
-
* append serialized data and use `decode*` functions to extract data.
|
|
45
|
-
* `shift` and `shiftN` throw an {@link BufferError} with message "Buffer parse
|
|
46
|
-
* ended prematurely" on failure, as do higher-level `decode*` functions,
|
|
47
|
-
* providing stack traces for debugging instead of using {@link Result}. This
|
|
48
|
-
* avoids allocation overhead in success cases and leverages exceptions'
|
|
49
|
-
* diagnostic benefits.
|
|
51
|
+
* append serialized data and use `decode*` functions to extract data.
|
|
50
52
|
*
|
|
51
53
|
* ### Example
|
|
52
54
|
*
|
|
@@ -101,7 +103,7 @@ export interface Buffer {
|
|
|
101
103
|
|
|
102
104
|
/**
|
|
103
105
|
* Appends binary data to the buffer, resizing if necessary. Throws if
|
|
104
|
-
* `arg.length` is not a non-negative integer.
|
|
106
|
+
* `arg.length` is not a non-negative safe integer.
|
|
105
107
|
*/
|
|
106
108
|
extend: (arg: Uint8Array | ArrayLike<number>) => void;
|
|
107
109
|
|
|
@@ -144,18 +146,26 @@ export interface Buffer {
|
|
|
144
146
|
export const createBuffer = (
|
|
145
147
|
arrayLike?: Uint8Array | ArrayLike<number>,
|
|
146
148
|
): Buffer => {
|
|
149
|
+
const initialLength = arrayLike?.length ?? 0;
|
|
150
|
+
assertNonNegativeInt(initialLength, "arrayLike.length");
|
|
151
|
+
|
|
147
152
|
let value = arrayLike
|
|
148
153
|
? new globalThis.Uint8Array(arrayLike)
|
|
149
154
|
: new globalThis.Uint8Array(512);
|
|
150
|
-
let length =
|
|
155
|
+
let length = initialLength;
|
|
151
156
|
|
|
152
157
|
const buffer: Buffer = {
|
|
153
|
-
getCapacity: () =>
|
|
158
|
+
getCapacity: () => value.length as NonNegativeInt,
|
|
154
159
|
|
|
155
160
|
getLength: () => length,
|
|
156
161
|
|
|
157
162
|
extend: (arg) => {
|
|
158
|
-
const
|
|
163
|
+
const argLength = arg.length;
|
|
164
|
+
assertNonNegativeInt(argLength, "arg.length");
|
|
165
|
+
|
|
166
|
+
const targetSize = length + argLength;
|
|
167
|
+
assertNonNegativeInt(targetSize, "Buffer length");
|
|
168
|
+
|
|
159
169
|
if (value.length < targetSize) {
|
|
160
170
|
const oldValue = value;
|
|
161
171
|
const newCapacity = Math.max(value.length * 2, targetSize);
|
|
@@ -163,26 +173,22 @@ export const createBuffer = (
|
|
|
163
173
|
value.set(oldValue);
|
|
164
174
|
}
|
|
165
175
|
value.set(arg, length);
|
|
166
|
-
length =
|
|
176
|
+
length = targetSize;
|
|
167
177
|
},
|
|
168
178
|
|
|
169
179
|
shift: () => {
|
|
170
|
-
|
|
171
|
-
throw new BufferError("Buffer parse ended prematurely");
|
|
172
|
-
}
|
|
180
|
+
assertBufferHasRemainingBytes(length, 1);
|
|
173
181
|
const first = value[0];
|
|
174
182
|
value = value.subarray(1);
|
|
175
183
|
length--;
|
|
176
|
-
return NonNegativeInt
|
|
184
|
+
return first as NonNegativeInt;
|
|
177
185
|
},
|
|
178
186
|
|
|
179
187
|
shiftN: (n) => {
|
|
180
|
-
|
|
181
|
-
throw new BufferError("Buffer parse ended prematurely");
|
|
182
|
-
}
|
|
188
|
+
assertBufferHasRemainingBytes(length, n);
|
|
183
189
|
const subarray = value.subarray(0, n);
|
|
184
190
|
value = value.subarray(n);
|
|
185
|
-
length =
|
|
191
|
+
length = (length - n) as NonNegativeInt;
|
|
186
192
|
return subarray;
|
|
187
193
|
},
|
|
188
194
|
|
|
@@ -196,11 +202,949 @@ export const createBuffer = (
|
|
|
196
202
|
},
|
|
197
203
|
|
|
198
204
|
reset: () => {
|
|
199
|
-
length =
|
|
205
|
+
length = 0 as NonNegativeInt;
|
|
200
206
|
},
|
|
201
207
|
|
|
202
|
-
unwrap: () => value.subarray(0, length),
|
|
208
|
+
unwrap: () => (value.length === length ? value : value.subarray(0, length)),
|
|
203
209
|
};
|
|
204
210
|
|
|
205
211
|
return buffer;
|
|
206
212
|
};
|
|
213
|
+
|
|
214
|
+
// Inspired by msgpackr 2.0.5, licensed under the MIT License.
|
|
215
|
+
// This implementation is specialized for Evolu's JsonValue domain.
|
|
216
|
+
//
|
|
217
|
+
// MIT License
|
|
218
|
+
//
|
|
219
|
+
// Copyright (c) 2020 Kris Zyp
|
|
220
|
+
//
|
|
221
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
222
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
223
|
+
// in the Software without restriction, including without limitation the rights
|
|
224
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
225
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
226
|
+
// furnished to do so, subject to the following conditions:
|
|
227
|
+
//
|
|
228
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
229
|
+
// copies or substantial portions of the Software.
|
|
230
|
+
//
|
|
231
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
232
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
233
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
234
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
235
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
236
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
237
|
+
// SOFTWARE.
|
|
238
|
+
|
|
239
|
+
interface JsonKeyCacheEntry {
|
|
240
|
+
readonly bytes: Uint8Array;
|
|
241
|
+
readonly value: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const jsonKeyCacheSize = 4096;
|
|
245
|
+
const maxCachedJsonKeyByteLength = 32;
|
|
246
|
+
const maxJsonNestingDepth = 1_000;
|
|
247
|
+
let jsonEncoderTarget = new globalThis.Uint8Array(8192);
|
|
248
|
+
let jsonEncoderTargetView = new globalThis.DataView(jsonEncoderTarget.buffer);
|
|
249
|
+
let jsonEncoderPosition = 0;
|
|
250
|
+
let jsonEncoderDepth = 0;
|
|
251
|
+
let jsonEncoderIsActive = false;
|
|
252
|
+
const emptyJsonDecoderSource: Uint8Array = new globalThis.Uint8Array(0);
|
|
253
|
+
const emptyJsonDecoderView: DataView = new globalThis.DataView(
|
|
254
|
+
emptyJsonDecoderSource.buffer,
|
|
255
|
+
);
|
|
256
|
+
let jsonDecoderSource: Uint8Array = emptyJsonDecoderSource;
|
|
257
|
+
let jsonDecoderView: DataView = emptyJsonDecoderView;
|
|
258
|
+
let jsonDecoderPosition = 0;
|
|
259
|
+
let jsonDecoderDepth = 0;
|
|
260
|
+
const jsonStringFromCharCode = globalThis.String.fromCharCode;
|
|
261
|
+
// Cache only short keys and use a fixed table to bound retained memory.
|
|
262
|
+
const jsonKeyCache: Array<JsonKeyCacheEntry | undefined> =
|
|
263
|
+
globalThis.Array.from({ length: jsonKeyCacheSize }, () => undefined);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Encodes a {@link JsonValue} using the MessagePack format.
|
|
267
|
+
*
|
|
268
|
+
* ### Example
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* import {
|
|
272
|
+
* assertEqual,
|
|
273
|
+
* createBuffer,
|
|
274
|
+
* encodeJsonValue,
|
|
275
|
+
* JsonValue,
|
|
276
|
+
* } from "@evolu/common";
|
|
277
|
+
*
|
|
278
|
+
* const buffer = createBuffer();
|
|
279
|
+
* const value = JsonValue.orThrow({ name: "Ada" });
|
|
280
|
+
*
|
|
281
|
+
* encodeJsonValue(buffer, value);
|
|
282
|
+
*
|
|
283
|
+
* assertEqual(
|
|
284
|
+
* buffer.unwrap(),
|
|
285
|
+
* new Uint8Array([
|
|
286
|
+
* 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
|
|
287
|
+
* ]),
|
|
288
|
+
* );
|
|
289
|
+
* ```
|
|
290
|
+
*
|
|
291
|
+
* Encoding is artificially limited to 1,000 nested arrays or objects to keep
|
|
292
|
+
* recursive encoding and decoding safe and symmetric. JSON data should not
|
|
293
|
+
* require such depth; flatten or split deeply nested data, or use a
|
|
294
|
+
* purpose-built serialization format.
|
|
295
|
+
*/
|
|
296
|
+
export const encodeJsonValue = (buffer: Buffer, value: JsonValue): void => {
|
|
297
|
+
if (jsonEncoderIsActive) {
|
|
298
|
+
throw new BufferError("Reentrant JSON encoding is not supported.");
|
|
299
|
+
}
|
|
300
|
+
jsonEncoderIsActive = true;
|
|
301
|
+
jsonEncoderPosition = 0;
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
encodeJsonValueToTarget(value);
|
|
305
|
+
buffer.extend(jsonEncoderTarget.subarray(0, jsonEncoderPosition));
|
|
306
|
+
} finally {
|
|
307
|
+
jsonEncoderPosition = 0;
|
|
308
|
+
jsonEncoderDepth = 0;
|
|
309
|
+
jsonEncoderIsActive = false;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Decodes a {@link JsonValue} using the MessagePack format.
|
|
315
|
+
*
|
|
316
|
+
* Throws a {@link BufferError} without modifying the Buffer if the encoded value
|
|
317
|
+
* is malformed, truncated, unsupported, outside the JsonValue domain, or
|
|
318
|
+
* exceeds 1,000 nested arrays or objects.
|
|
319
|
+
*
|
|
320
|
+
* ### Example
|
|
321
|
+
*
|
|
322
|
+
* ```ts
|
|
323
|
+
* import {
|
|
324
|
+
* assertEqual,
|
|
325
|
+
* createBuffer,
|
|
326
|
+
* decodeJsonValue,
|
|
327
|
+
* } from "@evolu/common";
|
|
328
|
+
*
|
|
329
|
+
* const buffer = createBuffer([
|
|
330
|
+
* 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
|
|
331
|
+
* ]);
|
|
332
|
+
*
|
|
333
|
+
* assertEqual(decodeJsonValue(buffer), { name: "Ada" });
|
|
334
|
+
* assertEqual(buffer.unwrap(), new Uint8Array());
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
export const decodeJsonValue = (buffer: Buffer): JsonValue => {
|
|
338
|
+
const source = buffer.unwrap();
|
|
339
|
+
jsonDecoderSource = source;
|
|
340
|
+
jsonDecoderView = new globalThis.DataView(
|
|
341
|
+
source.buffer,
|
|
342
|
+
source.byteOffset,
|
|
343
|
+
source.byteLength,
|
|
344
|
+
);
|
|
345
|
+
jsonDecoderPosition = 0;
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const value = decodeJsonValueFromSource();
|
|
349
|
+
buffer.shiftN(jsonDecoderPosition as NonNegativeInt);
|
|
350
|
+
return value;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error instanceof BufferError) throw error;
|
|
353
|
+
|
|
354
|
+
throw new BufferError("Invalid MessagePack data");
|
|
355
|
+
} finally {
|
|
356
|
+
jsonDecoderSource = emptyJsonDecoderSource;
|
|
357
|
+
jsonDecoderView = emptyJsonDecoderView;
|
|
358
|
+
jsonDecoderPosition = 0;
|
|
359
|
+
jsonDecoderDepth = 0;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const encodeJsonValueToTarget = (value: JsonValue): void => {
|
|
364
|
+
if (value === null) {
|
|
365
|
+
writeJsonEncoderByte(0xc0);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
|
|
370
|
+
switch (typeof value) {
|
|
371
|
+
case "string":
|
|
372
|
+
encodeJsonStringToTarget(value);
|
|
373
|
+
return;
|
|
374
|
+
case "number": {
|
|
375
|
+
if (globalThis.Object.is(value, -0)) {
|
|
376
|
+
ensureJsonEncoderCapacity(9);
|
|
377
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
|
|
378
|
+
jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
|
|
379
|
+
jsonEncoderPosition += 8;
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (value >>> 0 === value) {
|
|
384
|
+
if (value < 0x80) {
|
|
385
|
+
writeJsonEncoderByte(value);
|
|
386
|
+
} else if (value < 0x100) {
|
|
387
|
+
ensureJsonEncoderCapacity(2);
|
|
388
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcc;
|
|
389
|
+
jsonEncoderTarget[jsonEncoderPosition++] = value;
|
|
390
|
+
} else if (value < 0x10000) {
|
|
391
|
+
ensureJsonEncoderCapacity(3);
|
|
392
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcd;
|
|
393
|
+
jsonEncoderTargetView.setUint16(jsonEncoderPosition, value);
|
|
394
|
+
jsonEncoderPosition += 2;
|
|
395
|
+
} else {
|
|
396
|
+
ensureJsonEncoderCapacity(5);
|
|
397
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xce;
|
|
398
|
+
jsonEncoderTargetView.setUint32(jsonEncoderPosition, value);
|
|
399
|
+
jsonEncoderPosition += 4;
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (
|
|
405
|
+
globalThis.Number.isInteger(value) &&
|
|
406
|
+
value >= -0x80000000 &&
|
|
407
|
+
value < 0
|
|
408
|
+
) {
|
|
409
|
+
if (value >= -0x20) {
|
|
410
|
+
writeJsonEncoderByte(0x100 + value);
|
|
411
|
+
} else if (value >= -0x80) {
|
|
412
|
+
ensureJsonEncoderCapacity(2);
|
|
413
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd0;
|
|
414
|
+
jsonEncoderTargetView.setInt8(jsonEncoderPosition++, value);
|
|
415
|
+
} else if (value >= -0x8000) {
|
|
416
|
+
ensureJsonEncoderCapacity(3);
|
|
417
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd1;
|
|
418
|
+
jsonEncoderTargetView.setInt16(jsonEncoderPosition, value);
|
|
419
|
+
jsonEncoderPosition += 2;
|
|
420
|
+
} else {
|
|
421
|
+
ensureJsonEncoderCapacity(5);
|
|
422
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd2;
|
|
423
|
+
jsonEncoderTargetView.setInt32(jsonEncoderPosition, value);
|
|
424
|
+
jsonEncoderPosition += 4;
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
ensureJsonEncoderCapacity(9);
|
|
430
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
|
|
431
|
+
jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
|
|
432
|
+
jsonEncoderPosition += 8;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
case "boolean":
|
|
436
|
+
writeJsonEncoderByte(value ? 0xc3 : 0xc2);
|
|
437
|
+
return;
|
|
438
|
+
case "object": {
|
|
439
|
+
if (jsonEncoderDepth >= maxJsonNestingDepth) {
|
|
440
|
+
throw new BufferError(
|
|
441
|
+
`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
jsonEncoderDepth++;
|
|
445
|
+
|
|
446
|
+
if (globalThis.Array.isArray(value)) {
|
|
447
|
+
const array = value as ReadonlyArray<JsonValue>;
|
|
448
|
+
const length = array.length;
|
|
449
|
+
writeJsonCollectionHeader(length, 0x90, 0xdc, 0xdd);
|
|
450
|
+
|
|
451
|
+
for (const item of array) encodeJsonValueToTarget(item);
|
|
452
|
+
jsonEncoderDepth--;
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const object = value as Readonly<Record<string, JsonValue>>;
|
|
457
|
+
const keys = globalThis.Object.keys(object);
|
|
458
|
+
writeJsonCollectionHeader(keys.length, 0x80, 0xde, 0xdf);
|
|
459
|
+
|
|
460
|
+
for (const key of keys) {
|
|
461
|
+
encodeJsonStringToTarget(key);
|
|
462
|
+
encodeJsonValueToTarget(object[key]);
|
|
463
|
+
}
|
|
464
|
+
jsonEncoderDepth--;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const encodeJsonStringToTarget = (value: string): void => {
|
|
470
|
+
const valueLength = value.length;
|
|
471
|
+
const headerLength =
|
|
472
|
+
valueLength < 0x20
|
|
473
|
+
? 1
|
|
474
|
+
: valueLength < 0x100
|
|
475
|
+
? 2
|
|
476
|
+
: valueLength < 0x10000
|
|
477
|
+
? 3
|
|
478
|
+
: 5;
|
|
479
|
+
ensureJsonEncoderCapacity(5 + valueLength * 3);
|
|
480
|
+
|
|
481
|
+
const headerPosition = jsonEncoderPosition;
|
|
482
|
+
jsonEncoderPosition += headerLength;
|
|
483
|
+
|
|
484
|
+
for (let index = 0; index < valueLength; index++) {
|
|
485
|
+
let first = value.charCodeAt(index);
|
|
486
|
+
|
|
487
|
+
if (first < 0x80) {
|
|
488
|
+
jsonEncoderTarget[jsonEncoderPosition++] = first;
|
|
489
|
+
} else if (first < 0x800) {
|
|
490
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 6) | 0xc0;
|
|
491
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
492
|
+
} else if (
|
|
493
|
+
(first & 0xfc00) === 0xd800 &&
|
|
494
|
+
(value.charCodeAt(index + 1) & 0xfc00) === 0xdc00
|
|
495
|
+
) {
|
|
496
|
+
const second = value.charCodeAt(++index);
|
|
497
|
+
first = 0x10000 + ((first & 0x03ff) << 10) + (second & 0x03ff);
|
|
498
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 18) | 0xf0;
|
|
499
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 12) & 0x3f) | 0x80;
|
|
500
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
|
|
501
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
502
|
+
} else {
|
|
503
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 12) | 0xe0;
|
|
504
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
|
|
505
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const byteLength = jsonEncoderPosition - headerPosition - headerLength;
|
|
510
|
+
assertMessagePackLength(byteLength, "String byte length");
|
|
511
|
+
|
|
512
|
+
if (byteLength < 0x20) {
|
|
513
|
+
jsonEncoderTarget[headerPosition] = 0xa0 | byteLength;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (byteLength < 0x100) {
|
|
518
|
+
if (headerLength === 1) {
|
|
519
|
+
jsonEncoderTarget.copyWithin(
|
|
520
|
+
headerPosition + 2,
|
|
521
|
+
headerPosition + 1,
|
|
522
|
+
jsonEncoderPosition,
|
|
523
|
+
);
|
|
524
|
+
jsonEncoderPosition++;
|
|
525
|
+
}
|
|
526
|
+
jsonEncoderTarget[headerPosition] = 0xd9;
|
|
527
|
+
jsonEncoderTarget[headerPosition + 1] = byteLength;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (byteLength < 0x10000) {
|
|
532
|
+
if (headerLength < 3) {
|
|
533
|
+
const additionalHeaderLength = 3 - headerLength;
|
|
534
|
+
jsonEncoderTarget.copyWithin(
|
|
535
|
+
headerPosition + 3,
|
|
536
|
+
headerPosition + headerLength,
|
|
537
|
+
jsonEncoderPosition,
|
|
538
|
+
);
|
|
539
|
+
jsonEncoderPosition += additionalHeaderLength;
|
|
540
|
+
}
|
|
541
|
+
jsonEncoderTarget[headerPosition] = 0xda;
|
|
542
|
+
jsonEncoderTargetView.setUint16(headerPosition + 1, byteLength);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (headerLength < 5) {
|
|
547
|
+
const additionalHeaderLength = 5 - headerLength;
|
|
548
|
+
jsonEncoderTarget.copyWithin(
|
|
549
|
+
headerPosition + 5,
|
|
550
|
+
headerPosition + headerLength,
|
|
551
|
+
jsonEncoderPosition,
|
|
552
|
+
);
|
|
553
|
+
jsonEncoderPosition += additionalHeaderLength;
|
|
554
|
+
}
|
|
555
|
+
jsonEncoderTarget[headerPosition] = 0xdb;
|
|
556
|
+
jsonEncoderTargetView.setUint32(headerPosition + 1, byteLength);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const writeJsonCollectionHeader = (
|
|
560
|
+
length: number,
|
|
561
|
+
fixedMarker: number,
|
|
562
|
+
marker16: number,
|
|
563
|
+
marker32: number,
|
|
564
|
+
): void => {
|
|
565
|
+
assertMessagePackLength(length, "Collection length");
|
|
566
|
+
|
|
567
|
+
if (length < 0x10) {
|
|
568
|
+
writeJsonEncoderByte(fixedMarker | length);
|
|
569
|
+
} else if (length < 0x10000) {
|
|
570
|
+
ensureJsonEncoderCapacity(3);
|
|
571
|
+
jsonEncoderTarget[jsonEncoderPosition++] = marker16;
|
|
572
|
+
jsonEncoderTargetView.setUint16(jsonEncoderPosition, length);
|
|
573
|
+
jsonEncoderPosition += 2;
|
|
574
|
+
} else {
|
|
575
|
+
ensureJsonEncoderCapacity(5);
|
|
576
|
+
jsonEncoderTarget[jsonEncoderPosition++] = marker32;
|
|
577
|
+
jsonEncoderTargetView.setUint32(jsonEncoderPosition, length);
|
|
578
|
+
jsonEncoderPosition += 4;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const writeJsonEncoderByte = (value: number): void => {
|
|
583
|
+
ensureJsonEncoderCapacity(1);
|
|
584
|
+
jsonEncoderTarget[jsonEncoderPosition++] = value;
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const ensureJsonEncoderCapacity = (additionalLength: number): void => {
|
|
588
|
+
const requiredLength = jsonEncoderPosition + additionalLength;
|
|
589
|
+
assertNonNegativeInt(requiredLength, "Encoded JSON value length");
|
|
590
|
+
|
|
591
|
+
if (requiredLength <= jsonEncoderTarget.length) return;
|
|
592
|
+
|
|
593
|
+
const newCapacity = globalThis.Math.max(
|
|
594
|
+
jsonEncoderTarget.length * 2,
|
|
595
|
+
requiredLength,
|
|
596
|
+
);
|
|
597
|
+
assertNonNegativeInt(newCapacity, "JSON encoder capacity");
|
|
598
|
+
|
|
599
|
+
const oldTarget = jsonEncoderTarget;
|
|
600
|
+
jsonEncoderTarget = new globalThis.Uint8Array(newCapacity);
|
|
601
|
+
jsonEncoderTarget.set(oldTarget.subarray(0, jsonEncoderPosition));
|
|
602
|
+
jsonEncoderTargetView = new globalThis.DataView(jsonEncoderTarget.buffer);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const assertMessagePackLength = (length: number, name: string): void => {
|
|
606
|
+
if (length > 0xffffffff) {
|
|
607
|
+
throw new BufferError(`${name} exceeds the MessagePack uint32 limit.`);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const decodeJsonValueFromSource = (): JsonValue => {
|
|
612
|
+
const marker = readJsonDecoderByte();
|
|
613
|
+
|
|
614
|
+
if (marker <= 0x7f) return marker as JsonValue;
|
|
615
|
+
if (marker <= 0x8f) return decodeJsonMap(marker - 0x80);
|
|
616
|
+
if (marker <= 0x9f) return decodeJsonArray(marker - 0x90);
|
|
617
|
+
if (marker <= 0xbf) return decodeJsonString(marker - 0xa0);
|
|
618
|
+
if (marker >= 0xe0) return (marker - 0x100) as JsonValue;
|
|
619
|
+
|
|
620
|
+
switch (marker) {
|
|
621
|
+
case 0xc0:
|
|
622
|
+
return null;
|
|
623
|
+
case 0xc2:
|
|
624
|
+
return false;
|
|
625
|
+
case 0xc3:
|
|
626
|
+
return true;
|
|
627
|
+
case 0xca:
|
|
628
|
+
return decodeJsonFloat(4);
|
|
629
|
+
case 0xcb:
|
|
630
|
+
return decodeJsonFloat(8);
|
|
631
|
+
case 0xcc:
|
|
632
|
+
return readJsonDecoderByte() as JsonValue;
|
|
633
|
+
case 0xcd:
|
|
634
|
+
return readJsonUint16() as JsonValue;
|
|
635
|
+
case 0xce:
|
|
636
|
+
return readJsonUint32() as JsonValue;
|
|
637
|
+
case 0xd0: {
|
|
638
|
+
assertJsonDecoderHasRemainingBytes(1);
|
|
639
|
+
return jsonDecoderView.getInt8(jsonDecoderPosition++) as JsonValue;
|
|
640
|
+
}
|
|
641
|
+
case 0xd1: {
|
|
642
|
+
assertJsonDecoderHasRemainingBytes(2);
|
|
643
|
+
const value = jsonDecoderView.getInt16(jsonDecoderPosition);
|
|
644
|
+
jsonDecoderPosition += 2;
|
|
645
|
+
return value as JsonValue;
|
|
646
|
+
}
|
|
647
|
+
case 0xd2: {
|
|
648
|
+
assertJsonDecoderHasRemainingBytes(4);
|
|
649
|
+
const value = jsonDecoderView.getInt32(jsonDecoderPosition);
|
|
650
|
+
jsonDecoderPosition += 4;
|
|
651
|
+
return value as JsonValue;
|
|
652
|
+
}
|
|
653
|
+
case 0xd9:
|
|
654
|
+
return decodeJsonString(readJsonDecoderByte());
|
|
655
|
+
case 0xda:
|
|
656
|
+
return decodeJsonString(readJsonUint16());
|
|
657
|
+
case 0xdb:
|
|
658
|
+
return decodeJsonString(readJsonUint32());
|
|
659
|
+
case 0xdc:
|
|
660
|
+
return decodeJsonArray(readJsonUint16());
|
|
661
|
+
case 0xdd:
|
|
662
|
+
return decodeJsonArray(readJsonUint32());
|
|
663
|
+
case 0xde:
|
|
664
|
+
return decodeJsonMap(readJsonUint16());
|
|
665
|
+
case 0xdf:
|
|
666
|
+
return decodeJsonMap(readJsonUint32());
|
|
667
|
+
default:
|
|
668
|
+
throw new BufferError(
|
|
669
|
+
`Unsupported MessagePack marker 0x${marker.toString(16).padStart(2, "0")}.`,
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const decodeJsonFloat = (byteLength: 4 | 8): JsonValue => {
|
|
675
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
676
|
+
const value =
|
|
677
|
+
byteLength === 4
|
|
678
|
+
? jsonDecoderView.getFloat32(jsonDecoderPosition)
|
|
679
|
+
: jsonDecoderView.getFloat64(jsonDecoderPosition);
|
|
680
|
+
jsonDecoderPosition += byteLength;
|
|
681
|
+
|
|
682
|
+
if (!globalThis.Number.isFinite(value)) {
|
|
683
|
+
throw new BufferError("A decoded JSON number must be finite.");
|
|
684
|
+
}
|
|
685
|
+
return value as JsonValue;
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const decodeJsonString = (byteLength: number): string => {
|
|
689
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
690
|
+
|
|
691
|
+
shortAscii: if (byteLength < 16) {
|
|
692
|
+
if (byteLength === 0) return "";
|
|
693
|
+
|
|
694
|
+
const start = jsonDecoderPosition;
|
|
695
|
+
const first = jsonDecoderSource[jsonDecoderPosition++];
|
|
696
|
+
|
|
697
|
+
if ((first & 0x80) !== 0) {
|
|
698
|
+
jsonDecoderPosition = start;
|
|
699
|
+
break shortAscii;
|
|
700
|
+
}
|
|
701
|
+
if (byteLength === 1) return jsonStringFromCharCode(first);
|
|
702
|
+
|
|
703
|
+
const second = jsonDecoderSource[jsonDecoderPosition++];
|
|
704
|
+
if ((second & 0x80) !== 0) {
|
|
705
|
+
jsonDecoderPosition = start;
|
|
706
|
+
break shortAscii;
|
|
707
|
+
}
|
|
708
|
+
if (byteLength === 2) return jsonStringFromCharCode(first, second);
|
|
709
|
+
|
|
710
|
+
const third = jsonDecoderSource[jsonDecoderPosition++];
|
|
711
|
+
if ((third & 0x80) !== 0) {
|
|
712
|
+
jsonDecoderPosition = start;
|
|
713
|
+
break shortAscii;
|
|
714
|
+
}
|
|
715
|
+
if (byteLength === 3) return jsonStringFromCharCode(first, second, third);
|
|
716
|
+
|
|
717
|
+
const fourth = jsonDecoderSource[jsonDecoderPosition++];
|
|
718
|
+
if ((fourth & 0x80) !== 0) {
|
|
719
|
+
jsonDecoderPosition = start;
|
|
720
|
+
break shortAscii;
|
|
721
|
+
}
|
|
722
|
+
if (byteLength === 4) {
|
|
723
|
+
return jsonStringFromCharCode(first, second, third, fourth);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const fifth = jsonDecoderSource[jsonDecoderPosition++];
|
|
727
|
+
if ((fifth & 0x80) !== 0) {
|
|
728
|
+
jsonDecoderPosition = start;
|
|
729
|
+
break shortAscii;
|
|
730
|
+
}
|
|
731
|
+
if (byteLength === 5) {
|
|
732
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const sixth = jsonDecoderSource[jsonDecoderPosition++];
|
|
736
|
+
if ((sixth & 0x80) !== 0) {
|
|
737
|
+
jsonDecoderPosition = start;
|
|
738
|
+
break shortAscii;
|
|
739
|
+
}
|
|
740
|
+
if (byteLength === 6) {
|
|
741
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const seventh = jsonDecoderSource[jsonDecoderPosition++];
|
|
745
|
+
if ((seventh & 0x80) !== 0) {
|
|
746
|
+
jsonDecoderPosition = start;
|
|
747
|
+
break shortAscii;
|
|
748
|
+
}
|
|
749
|
+
if (byteLength === 7) {
|
|
750
|
+
return jsonStringFromCharCode(
|
|
751
|
+
first,
|
|
752
|
+
second,
|
|
753
|
+
third,
|
|
754
|
+
fourth,
|
|
755
|
+
fifth,
|
|
756
|
+
sixth,
|
|
757
|
+
seventh,
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const eighth = jsonDecoderSource[jsonDecoderPosition++];
|
|
762
|
+
if ((eighth & 0x80) !== 0) {
|
|
763
|
+
jsonDecoderPosition = start;
|
|
764
|
+
break shortAscii;
|
|
765
|
+
}
|
|
766
|
+
if (byteLength === 8) {
|
|
767
|
+
return jsonStringFromCharCode(
|
|
768
|
+
first,
|
|
769
|
+
second,
|
|
770
|
+
third,
|
|
771
|
+
fourth,
|
|
772
|
+
fifth,
|
|
773
|
+
sixth,
|
|
774
|
+
seventh,
|
|
775
|
+
eighth,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const ninth = jsonDecoderSource[jsonDecoderPosition++];
|
|
780
|
+
if ((ninth & 0x80) !== 0) {
|
|
781
|
+
jsonDecoderPosition = start;
|
|
782
|
+
break shortAscii;
|
|
783
|
+
}
|
|
784
|
+
if (byteLength === 9) {
|
|
785
|
+
return jsonStringFromCharCode(
|
|
786
|
+
first,
|
|
787
|
+
second,
|
|
788
|
+
third,
|
|
789
|
+
fourth,
|
|
790
|
+
fifth,
|
|
791
|
+
sixth,
|
|
792
|
+
seventh,
|
|
793
|
+
eighth,
|
|
794
|
+
ninth,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const tenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
799
|
+
if ((tenth & 0x80) !== 0) {
|
|
800
|
+
jsonDecoderPosition = start;
|
|
801
|
+
break shortAscii;
|
|
802
|
+
}
|
|
803
|
+
if (byteLength === 10) {
|
|
804
|
+
return jsonStringFromCharCode(
|
|
805
|
+
first,
|
|
806
|
+
second,
|
|
807
|
+
third,
|
|
808
|
+
fourth,
|
|
809
|
+
fifth,
|
|
810
|
+
sixth,
|
|
811
|
+
seventh,
|
|
812
|
+
eighth,
|
|
813
|
+
ninth,
|
|
814
|
+
tenth,
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const eleventh = jsonDecoderSource[jsonDecoderPosition++];
|
|
819
|
+
if ((eleventh & 0x80) !== 0) {
|
|
820
|
+
jsonDecoderPosition = start;
|
|
821
|
+
break shortAscii;
|
|
822
|
+
}
|
|
823
|
+
if (byteLength === 11) {
|
|
824
|
+
return jsonStringFromCharCode(
|
|
825
|
+
first,
|
|
826
|
+
second,
|
|
827
|
+
third,
|
|
828
|
+
fourth,
|
|
829
|
+
fifth,
|
|
830
|
+
sixth,
|
|
831
|
+
seventh,
|
|
832
|
+
eighth,
|
|
833
|
+
ninth,
|
|
834
|
+
tenth,
|
|
835
|
+
eleventh,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const twelfth = jsonDecoderSource[jsonDecoderPosition++];
|
|
840
|
+
if ((twelfth & 0x80) !== 0) {
|
|
841
|
+
jsonDecoderPosition = start;
|
|
842
|
+
break shortAscii;
|
|
843
|
+
}
|
|
844
|
+
if (byteLength === 12) {
|
|
845
|
+
return jsonStringFromCharCode(
|
|
846
|
+
first,
|
|
847
|
+
second,
|
|
848
|
+
third,
|
|
849
|
+
fourth,
|
|
850
|
+
fifth,
|
|
851
|
+
sixth,
|
|
852
|
+
seventh,
|
|
853
|
+
eighth,
|
|
854
|
+
ninth,
|
|
855
|
+
tenth,
|
|
856
|
+
eleventh,
|
|
857
|
+
twelfth,
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const thirteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
862
|
+
if ((thirteenth & 0x80) !== 0) {
|
|
863
|
+
jsonDecoderPosition = start;
|
|
864
|
+
break shortAscii;
|
|
865
|
+
}
|
|
866
|
+
if (byteLength === 13) {
|
|
867
|
+
return jsonStringFromCharCode(
|
|
868
|
+
first,
|
|
869
|
+
second,
|
|
870
|
+
third,
|
|
871
|
+
fourth,
|
|
872
|
+
fifth,
|
|
873
|
+
sixth,
|
|
874
|
+
seventh,
|
|
875
|
+
eighth,
|
|
876
|
+
ninth,
|
|
877
|
+
tenth,
|
|
878
|
+
eleventh,
|
|
879
|
+
twelfth,
|
|
880
|
+
thirteenth,
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const fourteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
885
|
+
if ((fourteenth & 0x80) !== 0) {
|
|
886
|
+
jsonDecoderPosition = start;
|
|
887
|
+
break shortAscii;
|
|
888
|
+
}
|
|
889
|
+
if (byteLength === 14) {
|
|
890
|
+
return jsonStringFromCharCode(
|
|
891
|
+
first,
|
|
892
|
+
second,
|
|
893
|
+
third,
|
|
894
|
+
fourth,
|
|
895
|
+
fifth,
|
|
896
|
+
sixth,
|
|
897
|
+
seventh,
|
|
898
|
+
eighth,
|
|
899
|
+
ninth,
|
|
900
|
+
tenth,
|
|
901
|
+
eleventh,
|
|
902
|
+
twelfth,
|
|
903
|
+
thirteenth,
|
|
904
|
+
fourteenth,
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const fifteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
909
|
+
if ((fifteenth & 0x80) !== 0) {
|
|
910
|
+
jsonDecoderPosition = start;
|
|
911
|
+
break shortAscii;
|
|
912
|
+
}
|
|
913
|
+
return jsonStringFromCharCode(
|
|
914
|
+
first,
|
|
915
|
+
second,
|
|
916
|
+
third,
|
|
917
|
+
fourth,
|
|
918
|
+
fifth,
|
|
919
|
+
sixth,
|
|
920
|
+
seventh,
|
|
921
|
+
eighth,
|
|
922
|
+
ninth,
|
|
923
|
+
tenth,
|
|
924
|
+
eleventh,
|
|
925
|
+
twelfth,
|
|
926
|
+
thirteenth,
|
|
927
|
+
fourteenth,
|
|
928
|
+
fifteenth,
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const end = jsonDecoderPosition + byteLength;
|
|
933
|
+
const units: Array<number> = [];
|
|
934
|
+
let result = "";
|
|
935
|
+
|
|
936
|
+
while (jsonDecoderPosition < end) {
|
|
937
|
+
const first = jsonDecoderSource[jsonDecoderPosition++];
|
|
938
|
+
|
|
939
|
+
if (first < 0x80) {
|
|
940
|
+
units.push(first);
|
|
941
|
+
} else if (first >= 0xc2 && first <= 0xdf) {
|
|
942
|
+
assertJsonStringHasRemainingBytes(end, 1);
|
|
943
|
+
const second = readJsonContinuationByte();
|
|
944
|
+
units.push(((first & 0x1f) << 6) | second);
|
|
945
|
+
} else if (first >= 0xe0 && first <= 0xef) {
|
|
946
|
+
assertJsonStringHasRemainingBytes(end, 2);
|
|
947
|
+
const secondByte = jsonDecoderSource[jsonDecoderPosition];
|
|
948
|
+
if (first === 0xe0 && secondByte < 0xa0) {
|
|
949
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
950
|
+
}
|
|
951
|
+
const second = readJsonContinuationByte();
|
|
952
|
+
const third = readJsonContinuationByte();
|
|
953
|
+
units.push(((first & 0x0f) << 12) | (second << 6) | third);
|
|
954
|
+
} else if (first >= 0xf0 && first <= 0xf4) {
|
|
955
|
+
assertJsonStringHasRemainingBytes(end, 3);
|
|
956
|
+
const secondByte = jsonDecoderSource[jsonDecoderPosition];
|
|
957
|
+
if (
|
|
958
|
+
(first === 0xf0 && secondByte < 0x90) ||
|
|
959
|
+
(first === 0xf4 && secondByte > 0x8f)
|
|
960
|
+
) {
|
|
961
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
962
|
+
}
|
|
963
|
+
const second = readJsonContinuationByte();
|
|
964
|
+
const third = readJsonContinuationByte();
|
|
965
|
+
const fourth = readJsonContinuationByte();
|
|
966
|
+
const codePoint =
|
|
967
|
+
((first & 0x07) << 18) | (second << 12) | (third << 6) | fourth;
|
|
968
|
+
const pair = codePoint - 0x10000;
|
|
969
|
+
units.push(0xd800 | (pair >> 10), 0xdc00 | (pair & 0x3ff));
|
|
970
|
+
} else {
|
|
971
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (units.length >= 0x1000) {
|
|
975
|
+
result += jsonStringFromCharCode(...units);
|
|
976
|
+
units.length = 0;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (units.length > 0) {
|
|
981
|
+
result += jsonStringFromCharCode(...units);
|
|
982
|
+
}
|
|
983
|
+
return result;
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
const decodeJsonArray = (length: number): JsonValue => {
|
|
987
|
+
if (jsonDecoderDepth >= maxJsonNestingDepth) {
|
|
988
|
+
throw new BufferError(
|
|
989
|
+
`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
if (length > jsonDecoderSource.length - jsonDecoderPosition) {
|
|
993
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
|
|
997
|
+
const value = new Array<JsonValue>(length);
|
|
998
|
+
jsonDecoderDepth++;
|
|
999
|
+
for (let index = 0; index < length; index++) {
|
|
1000
|
+
value[index] = decodeJsonValueFromSource();
|
|
1001
|
+
}
|
|
1002
|
+
jsonDecoderDepth--;
|
|
1003
|
+
return value;
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const decodeJsonMap = (length: number): JsonValue => {
|
|
1007
|
+
if (jsonDecoderDepth >= maxJsonNestingDepth) {
|
|
1008
|
+
throw new BufferError(
|
|
1009
|
+
`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
if (length > (jsonDecoderSource.length - jsonDecoderPosition) / 2) {
|
|
1013
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const value: Record<string, JsonValue> = {};
|
|
1017
|
+
jsonDecoderDepth++;
|
|
1018
|
+
for (let index = 0; index < length; index++) {
|
|
1019
|
+
const marker = readJsonDecoderByte();
|
|
1020
|
+
let key: string;
|
|
1021
|
+
|
|
1022
|
+
if (marker >= 0xa0 && marker <= 0xbf) {
|
|
1023
|
+
key = decodeCachedJsonKey(marker - 0xa0);
|
|
1024
|
+
} else if (marker === 0xd9) {
|
|
1025
|
+
key = decodeCachedJsonKey(readJsonDecoderByte());
|
|
1026
|
+
} else if (marker === 0xda) {
|
|
1027
|
+
key = decodeCachedJsonKey(readJsonUint16());
|
|
1028
|
+
} else if (marker === 0xdb) {
|
|
1029
|
+
key = decodeCachedJsonKey(readJsonUint32());
|
|
1030
|
+
} else {
|
|
1031
|
+
jsonDecoderPosition--;
|
|
1032
|
+
decodeJsonValueFromSource();
|
|
1033
|
+
throw new BufferError("A decoded JSON object key must be a string.");
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const entryValue = decodeJsonValueFromSource();
|
|
1037
|
+
|
|
1038
|
+
if (key === "__proto__") {
|
|
1039
|
+
globalThis.Object.defineProperty(value, key, {
|
|
1040
|
+
value: entryValue,
|
|
1041
|
+
configurable: true,
|
|
1042
|
+
enumerable: true,
|
|
1043
|
+
writable: true,
|
|
1044
|
+
});
|
|
1045
|
+
} else {
|
|
1046
|
+
value[key] = entryValue;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
jsonDecoderDepth--;
|
|
1050
|
+
return value;
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const decodeCachedJsonKey = (byteLength: number): string => {
|
|
1054
|
+
if (byteLength > maxCachedJsonKeyByteLength) {
|
|
1055
|
+
return decodeJsonString(byteLength);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
1059
|
+
const start = jsonDecoderPosition;
|
|
1060
|
+
const end = start + byteLength;
|
|
1061
|
+
const firstBytes =
|
|
1062
|
+
byteLength > 1
|
|
1063
|
+
? jsonDecoderView.getUint16(start)
|
|
1064
|
+
: byteLength === 1
|
|
1065
|
+
? jsonDecoderSource[start]
|
|
1066
|
+
: 0;
|
|
1067
|
+
const cacheIndex = ((byteLength << 5) ^ firstBytes) & (jsonKeyCacheSize - 1);
|
|
1068
|
+
const entry = jsonKeyCache[cacheIndex];
|
|
1069
|
+
|
|
1070
|
+
if (entry?.bytes.length === byteLength) {
|
|
1071
|
+
let index = 0;
|
|
1072
|
+
while (
|
|
1073
|
+
index < byteLength &&
|
|
1074
|
+
entry.bytes[index] === jsonDecoderSource[start + index]
|
|
1075
|
+
) {
|
|
1076
|
+
index++;
|
|
1077
|
+
}
|
|
1078
|
+
if (index === byteLength) {
|
|
1079
|
+
jsonDecoderPosition = end;
|
|
1080
|
+
return entry.value;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const value = decodeJsonString(byteLength);
|
|
1085
|
+
jsonKeyCache[cacheIndex] = {
|
|
1086
|
+
bytes: jsonDecoderSource.slice(start, end),
|
|
1087
|
+
value,
|
|
1088
|
+
};
|
|
1089
|
+
return value;
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
const readJsonContinuationByte = (): number => {
|
|
1093
|
+
const byte = jsonDecoderSource[jsonDecoderPosition++];
|
|
1094
|
+
if ((byte & 0xc0) !== 0x80) {
|
|
1095
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
1096
|
+
}
|
|
1097
|
+
return byte & 0x3f;
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
const assertJsonStringHasRemainingBytes = (
|
|
1101
|
+
end: number,
|
|
1102
|
+
requiredBytes: number,
|
|
1103
|
+
): void => {
|
|
1104
|
+
if (end - jsonDecoderPosition < requiredBytes) {
|
|
1105
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const readJsonDecoderByte = (): number => {
|
|
1110
|
+
assertJsonDecoderHasRemainingBytes(1);
|
|
1111
|
+
return jsonDecoderSource[jsonDecoderPosition++];
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
const readJsonUint16 = (): number => {
|
|
1115
|
+
assertJsonDecoderHasRemainingBytes(2);
|
|
1116
|
+
const value = jsonDecoderView.getUint16(jsonDecoderPosition);
|
|
1117
|
+
jsonDecoderPosition += 2;
|
|
1118
|
+
return value;
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
const readJsonUint32 = (): number => {
|
|
1122
|
+
assertJsonDecoderHasRemainingBytes(4);
|
|
1123
|
+
const value = jsonDecoderView.getUint32(jsonDecoderPosition);
|
|
1124
|
+
jsonDecoderPosition += 4;
|
|
1125
|
+
return value;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const assertJsonDecoderHasRemainingBytes = (requiredBytes: number): void => {
|
|
1129
|
+
if (jsonDecoderSource.length - jsonDecoderPosition < requiredBytes) {
|
|
1130
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
const assertNonNegativeInt: (
|
|
1135
|
+
value: number,
|
|
1136
|
+
name: string,
|
|
1137
|
+
) => asserts value is NonNegativeInt = (value, name) => {
|
|
1138
|
+
if (!globalThis.Number.isSafeInteger(value) || value < 0) {
|
|
1139
|
+
throw new BufferError(`${name} must be a non-negative safe integer.`);
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
const assertBufferHasRemainingBytes = (
|
|
1144
|
+
remainingBytes: number,
|
|
1145
|
+
requiredBytes: number,
|
|
1146
|
+
): void => {
|
|
1147
|
+
if (remainingBytes < requiredBytes) {
|
|
1148
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
1149
|
+
}
|
|
1150
|
+
};
|