@sofa-buffers/corelib 0.8.0 → 0.10.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/CHANGELOG.md +54 -1
- package/README.md +55 -0
- package/dist/index.cjs +262 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -26
- package/dist/index.d.ts +204 -26
- package/dist/index.global.js +262 -47
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +262 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -94,8 +94,6 @@ var DecodeStatus = {
|
|
|
94
94
|
var SofabErrorCode = {
|
|
95
95
|
/** A caller argument was invalid (e.g. id out of range, empty array). */
|
|
96
96
|
Argument: "ARGUMENT",
|
|
97
|
-
/** The API was used incorrectly (e.g. unbalanced sequence end). */
|
|
98
|
-
Usage: "USAGE",
|
|
99
97
|
/** The output buffer is full and no flush sink was provided. */
|
|
100
98
|
BufferFull: "BUFFER_FULL",
|
|
101
99
|
/** The input being decoded is malformed regardless of what follows (`INVALID`). */
|
|
@@ -130,9 +128,6 @@ var SofabError = class _SofabError extends Error {
|
|
|
130
128
|
function argumentError(message) {
|
|
131
129
|
return new SofabError(SofabErrorCode.Argument, message);
|
|
132
130
|
}
|
|
133
|
-
function usageError(message) {
|
|
134
|
-
return new SofabError(SofabErrorCode.Usage, message);
|
|
135
|
-
}
|
|
136
131
|
function bufferFullError(message) {
|
|
137
132
|
return new SofabError(SofabErrorCode.BufferFull, message);
|
|
138
133
|
}
|
|
@@ -446,6 +441,26 @@ var SIGNED_FAST_MAX2 = 4503599627370496;
|
|
|
446
441
|
var OStream = class {
|
|
447
442
|
constructor(buffer, offset = 0, flush) {
|
|
448
443
|
this.depth = 0;
|
|
444
|
+
/**
|
|
445
|
+
* Ids of the innermost open sequences whose header has not been written yet
|
|
446
|
+
* (MESSAGE_SPEC §2 lazy framing, {@link OStream.writeSequenceBeginLazy}).
|
|
447
|
+
* Always a contiguous suffix of the open sequences: writing any field commits
|
|
448
|
+
* the whole run at once, so {@link OStream.writeSequenceEnd} can drop the
|
|
449
|
+
* innermost one by dropping the last entry. Held-back ids are encoder state,
|
|
450
|
+
* never buffer content, so a flush can never split a run.
|
|
451
|
+
*
|
|
452
|
+
* Storage plus an explicit count rather than `push`/`pop`, so the slots are
|
|
453
|
+
* reused across messages (no allocation on a pooled encoder) and so
|
|
454
|
+
* {@link OStream.commitPending} can zero the count *before* it writes.
|
|
455
|
+
*
|
|
456
|
+
* The array grows on demand and is bounded only by `MAX_DEPTH` — there is no
|
|
457
|
+
* fixed hold-back window and hence no eager-framing fallback, which is what
|
|
458
|
+
* CORELIB_PLAN §6 ("How deep the hold-back reaches") demands of an
|
|
459
|
+
* implementation that can allocate: canonical output at *every* depth.
|
|
460
|
+
*/
|
|
461
|
+
this.pending = [];
|
|
462
|
+
/** Valid entries in {@link OStream.pending}. */
|
|
463
|
+
this.nPending = 0;
|
|
449
464
|
this.kernel = getKernel();
|
|
450
465
|
if (buffer === void 0) {
|
|
451
466
|
this.buf = new Uint8Array(DEFAULT_CAPACITY);
|
|
@@ -507,6 +522,7 @@ var OStream = class {
|
|
|
507
522
|
reset() {
|
|
508
523
|
this.pos = this.start;
|
|
509
524
|
this.depth = 0;
|
|
525
|
+
this.nPending = 0;
|
|
510
526
|
}
|
|
511
527
|
// --- scalars ------------------------------------------------------------
|
|
512
528
|
/** Write an unsigned integer field. */
|
|
@@ -657,6 +673,23 @@ var OStream = class {
|
|
|
657
673
|
}
|
|
658
674
|
}
|
|
659
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Write an fp32 array from its raw little-endian element payload. The bytes
|
|
678
|
+
* are emitted verbatim — no per-element `setFloat32` — so a signaling NaN
|
|
679
|
+
* survives bit-for-bit (§4.6), which {@link writeFp32Array} cannot guarantee
|
|
680
|
+
* because it re-quantizes each JS `number`. `payload.length` must be a
|
|
681
|
+
* multiple of 4; the element count is `payload.length / 4`.
|
|
682
|
+
*/
|
|
683
|
+
writeFp32ArrayRaw(id, payload) {
|
|
684
|
+
if ((payload.length & 3) !== 0) {
|
|
685
|
+
throw argumentError(
|
|
686
|
+
`fp32 array payload length ${payload.length} is not a multiple of 4`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
this.arrayHead(id, WireType.ArrayFixlen, payload.length >> 2);
|
|
690
|
+
this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
|
|
691
|
+
this.writeRaw(payload);
|
|
692
|
+
}
|
|
660
693
|
/** Write an array of IEEE-754 64-bit doubles. */
|
|
661
694
|
writeFp64Array(id, values) {
|
|
662
695
|
this.arrayHead(id, WireType.ArrayFixlen, values.length);
|
|
@@ -672,20 +705,82 @@ var OStream = class {
|
|
|
672
705
|
}
|
|
673
706
|
}
|
|
674
707
|
// --- sequences ----------------------------------------------------------
|
|
675
|
-
/**
|
|
676
|
-
|
|
708
|
+
/**
|
|
709
|
+
* Open a nested sequence (a fresh id scope) whose header is **held back**
|
|
710
|
+
* until the sequence turns out to have content.
|
|
711
|
+
*
|
|
712
|
+
* MESSAGE_SPEC §2 omits a sequence-typed field whose value equals its declared
|
|
713
|
+
* default, and "not one child was written" is exactly that condition —
|
|
714
|
+
* evaluated per child field, recursively, for free, because the message layer
|
|
715
|
+
* already omits every child equal to its default. A sequence closed with
|
|
716
|
+
* nothing in it therefore emits **nothing** instead of a two-byte empty frame,
|
|
717
|
+
* and an all-default message becomes the empty byte string. No byte image is
|
|
718
|
+
* ever compared, so in-memory layout never enters the decision.
|
|
719
|
+
*
|
|
720
|
+
* This is the only way to open a sequence. How it closes decides whether a
|
|
721
|
+
* contentless one survives: {@link OStream.writeSequenceEnd} drops it,
|
|
722
|
+
* {@link OStream.writeSequenceEndKeep} forces the frame out.
|
|
723
|
+
*/
|
|
724
|
+
writeSequenceBeginLazy(id) {
|
|
677
725
|
if (this.depth >= MAX_DEPTH) {
|
|
678
|
-
throw
|
|
726
|
+
throw argumentError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
679
727
|
}
|
|
680
|
-
|
|
728
|
+
if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
|
|
729
|
+
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
730
|
+
}
|
|
731
|
+
this.pending[this.nPending++] = id;
|
|
681
732
|
this.depth++;
|
|
682
733
|
}
|
|
683
|
-
/**
|
|
734
|
+
/**
|
|
735
|
+
* Close the current sequence, letting it **vanish** if it received no content.
|
|
736
|
+
*
|
|
737
|
+
* Use it wherever absence encodes the same value as an empty frame: a
|
|
738
|
+
* `struct`/`union` field, and an array field whose declared `default` is the
|
|
739
|
+
* empty collection (MESSAGE_SPEC §2). Where the frame must be visible, close
|
|
740
|
+
* with {@link OStream.writeSequenceEndKeep} instead.
|
|
741
|
+
*
|
|
742
|
+
* An end with no matching begin is not rejected: the encoder writes what it is
|
|
743
|
+
* told, and the resulting bytes are then malformed, which is the decoder's
|
|
744
|
+
* verdict to make. No other port refuses it. The depth counter stops at zero
|
|
745
|
+
* so the MAX_DEPTH check on begin cannot be fooled by an underflow.
|
|
746
|
+
*/
|
|
684
747
|
writeSequenceEnd() {
|
|
685
|
-
if (this.
|
|
748
|
+
if (this.nPending !== 0) {
|
|
749
|
+
this.nPending--;
|
|
750
|
+
if (this.depth > 0) this.depth--;
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
686
753
|
this.ensure(1);
|
|
687
754
|
this.buf[this.pos++] = WireType.SequenceEnd;
|
|
688
|
-
this.depth--;
|
|
755
|
+
if (this.depth > 0) this.depth--;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Close the current sequence, **keeping** its frame even when it received no
|
|
759
|
+
* content.
|
|
760
|
+
*
|
|
761
|
+
* Behaves like a write: it first emits any held-back headers — this frame's
|
|
762
|
+
* and every enclosing one's — and then the end marker, so an empty sequence
|
|
763
|
+
* reaches the wire as `begin` + `end`.
|
|
764
|
+
*
|
|
765
|
+
* Required wherever the frame carries information beyond its contents:
|
|
766
|
+
* - a **wrapper-array element** (`struct`/`union`/nested row): element
|
|
767
|
+
* presence is what carries a dynamic array's length — *highest present id +
|
|
768
|
+
* 1* (MESSAGE_SPEC §5.1) — so dropping an all-default element would change
|
|
769
|
+
* the decoded length, not just the bytes;
|
|
770
|
+
* - an array field already known to **differ from a non-empty declared
|
|
771
|
+
* `default`**: absence would reconstruct that default, so the empty frame is
|
|
772
|
+
* the only encoding of "explicitly empty" (§2, §3).
|
|
773
|
+
*
|
|
774
|
+
* The two failure directions are not symmetric, which is why this is the safe
|
|
775
|
+
* choice when in doubt: using it where {@link OStream.writeSequenceEnd} would
|
|
776
|
+
* do costs one non-canonical empty frame that a decoder normalizes away, while
|
|
777
|
+
* the reverse silently changes an array's length.
|
|
778
|
+
*/
|
|
779
|
+
writeSequenceEndKeep() {
|
|
780
|
+
if (this.nPending !== 0) this.commitPending();
|
|
781
|
+
this.ensure(1);
|
|
782
|
+
this.buf[this.pos++] = WireType.SequenceEnd;
|
|
783
|
+
if (this.depth > 0) this.depth--;
|
|
689
784
|
}
|
|
690
785
|
// --- internals ----------------------------------------------------------
|
|
691
786
|
/** Ensure exactly `value`'s varint size, then write it (bigint path). */
|
|
@@ -698,12 +793,44 @@ var OStream = class {
|
|
|
698
793
|
this.ensure(varintSizeNum(value));
|
|
699
794
|
this.pos = encodeVarintNum(value, this.buf, this.pos);
|
|
700
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Write a field header, the `(id << 3) | wireType` tag, as a varint.
|
|
798
|
+
*
|
|
799
|
+
* This is the single choke point every field write passes through — the
|
|
800
|
+
* scalar, fixlen, float, string, blob and both array writers all reach the
|
|
801
|
+
* wire through `header` / `fixlenHead` / `arrayHead`, and `fixlenHead` and
|
|
802
|
+
* `arrayHead` are themselves nothing but `header` plus a follow-up varint. So
|
|
803
|
+
* this is also where a held-back sequence run is committed: the field about to
|
|
804
|
+
* be written is content, which means every enclosing sequence is non-default
|
|
805
|
+
* and must be framed after all (MESSAGE_SPEC §2).
|
|
806
|
+
*
|
|
807
|
+
* The only writers that do *not* pass through here are the two sequence
|
|
808
|
+
* closers, which must not commit ({@link OStream.writeSequenceEnd}) or commit
|
|
809
|
+
* explicitly ({@link OStream.writeSequenceEndKeep}), and
|
|
810
|
+
* {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
|
|
811
|
+
*/
|
|
701
812
|
header(id, type) {
|
|
702
813
|
if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
|
|
703
814
|
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
704
815
|
}
|
|
816
|
+
if (this.nPending !== 0) this.commitPending();
|
|
705
817
|
this.putVarintNum(id * 8 + type);
|
|
706
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Write out the held-back sequence headers, **outermost first**, and clear the
|
|
821
|
+
* run. Runs at most once per non-default sequence, never per field — the cost
|
|
822
|
+
* on the hot path is the single `nPending` test in {@link header}.
|
|
823
|
+
*
|
|
824
|
+
* The count is zeroed before the first byte goes out, so a write re-entered
|
|
825
|
+
* from a flush sink cannot emit the same run twice.
|
|
826
|
+
*/
|
|
827
|
+
commitPending() {
|
|
828
|
+
const n = this.nPending;
|
|
829
|
+
this.nPending = 0;
|
|
830
|
+
for (let i = 0; i < n; i++) {
|
|
831
|
+
this.putVarintNum(this.pending[i] * 8 + WireType.SequenceStart);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
707
834
|
fixlenHead(id, length, subtype) {
|
|
708
835
|
this.header(id, WireType.Fixlen);
|
|
709
836
|
this.putVarintNum(length * 8 + subtype);
|
|
@@ -823,9 +950,14 @@ var FastDecoder = class {
|
|
|
823
950
|
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
824
951
|
const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
|
|
825
952
|
if (len !== want) throw invalidMsgError("fixlen float length mismatch");
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
953
|
+
if (sub === FixlenSubtype.Fp32) {
|
|
954
|
+
const p = this.p;
|
|
955
|
+
const value = this.readFp32();
|
|
956
|
+
top.fp32?.(id, value, top.fp32Raw ? this.buf.subarray(p, p + 4) : void 0);
|
|
957
|
+
} else {
|
|
958
|
+
const value = this.readFp64();
|
|
959
|
+
top.fp64?.(id, value);
|
|
960
|
+
}
|
|
829
961
|
} else {
|
|
830
962
|
const chunk = this.take(len);
|
|
831
963
|
if (sub === FixlenSubtype.String) top.string?.(id, len, 0, chunk);
|
|
@@ -864,9 +996,11 @@ var FastDecoder = class {
|
|
|
864
996
|
else throw invalidMsgError("invalid fixlen array element type");
|
|
865
997
|
top.arrayBegin?.(id, kind, count);
|
|
866
998
|
if (kind === ArrayKind.Fp32) {
|
|
999
|
+
const wantRaw = top.fp32Raw === true;
|
|
867
1000
|
for (let i = 0; i < count; i++) {
|
|
1001
|
+
const p = this.p;
|
|
868
1002
|
const value = this.readFp32();
|
|
869
|
-
top.arrayFp32?.(id, i, value);
|
|
1003
|
+
top.arrayFp32?.(id, i, value, wantRaw ? this.buf.subarray(p, p + 4) : void 0);
|
|
870
1004
|
}
|
|
871
1005
|
} else {
|
|
872
1006
|
for (let i = 0; i < count; i++) {
|
|
@@ -1127,8 +1261,10 @@ var DecoderState = class {
|
|
|
1127
1261
|
i = this.fpStep(input, i);
|
|
1128
1262
|
if (this.have < this.need) return;
|
|
1129
1263
|
const value = this.fixSub === FixlenSubtype.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
|
|
1130
|
-
if (this.fixSub === FixlenSubtype.Fp32)
|
|
1131
|
-
|
|
1264
|
+
if (this.fixSub === FixlenSubtype.Fp32) {
|
|
1265
|
+
const top = this.top();
|
|
1266
|
+
top.fp32?.(this.id, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
|
|
1267
|
+
} else this.top().fp64?.(this.id, value);
|
|
1132
1268
|
this.state = 0 /* Header */;
|
|
1133
1269
|
break;
|
|
1134
1270
|
}
|
|
@@ -1210,8 +1346,10 @@ var DecoderState = class {
|
|
|
1210
1346
|
i = this.fpStep(input, i);
|
|
1211
1347
|
if (this.have < this.need) return;
|
|
1212
1348
|
const value = this.arrKind === ArrayKind.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
|
|
1213
|
-
if (this.arrKind === ArrayKind.Fp32)
|
|
1214
|
-
|
|
1349
|
+
if (this.arrKind === ArrayKind.Fp32) {
|
|
1350
|
+
const top = this.top();
|
|
1351
|
+
top.arrayFp32?.(this.id, this.arrIndex, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
|
|
1352
|
+
} else this.top().arrayFp64?.(this.id, this.arrIndex, value);
|
|
1215
1353
|
this.have = 0;
|
|
1216
1354
|
this.advanceArray();
|
|
1217
1355
|
break;
|
|
@@ -1506,7 +1644,12 @@ var Cursor = class {
|
|
|
1506
1644
|
}
|
|
1507
1645
|
const id = this.upper();
|
|
1508
1646
|
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
1509
|
-
if (wire === WireType.SequenceStart)
|
|
1647
|
+
if (wire === WireType.SequenceStart) {
|
|
1648
|
+
if (this.depth >= MAX_DEPTH) {
|
|
1649
|
+
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1650
|
+
}
|
|
1651
|
+
this.depth++;
|
|
1652
|
+
}
|
|
1510
1653
|
this.id = id;
|
|
1511
1654
|
this.wire = wire;
|
|
1512
1655
|
this.fixSub = this.peekFixSub(wire);
|
|
@@ -1527,14 +1670,40 @@ var Cursor = class {
|
|
|
1527
1670
|
this.fixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1528
1671
|
return this.rawFp32();
|
|
1529
1672
|
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Read a 32-bit float scalar as its raw 4 wire bytes (little-endian), zero-copy
|
|
1675
|
+
* — the bit-preserving companion to {@link readFp32}.
|
|
1676
|
+
*
|
|
1677
|
+
* {@link readFp32} returns a JS `number` (a 64-bit double), and widening an
|
|
1678
|
+
* fp32 *signaling* NaN into a double quiets it (0x7F800001 → 0x7FC00001), so a
|
|
1679
|
+
* value consumer can never round-trip one bit-for-bit (§4.6). Generated
|
|
1680
|
+
* bit-exact decode reads the bytes here instead and re-emits them verbatim with
|
|
1681
|
+
* {@link OStream.writeFixlen} (subtype fp32) — mirroring the visitor `raw`
|
|
1682
|
+
* channel on the push paths (fast.ts / state.ts), which the pull path was
|
|
1683
|
+
* missing (corelib-ts#66).
|
|
1684
|
+
*
|
|
1685
|
+
* The header (subtype fp32, length 4) is validated exactly as in
|
|
1686
|
+
* {@link readFp32}; the returned view aliases the source buffer, valid only
|
|
1687
|
+
* until it is reused, like {@link readBlob}.
|
|
1688
|
+
*/
|
|
1689
|
+
readFp32Raw() {
|
|
1690
|
+
this.fixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1691
|
+
return this.take(4);
|
|
1692
|
+
}
|
|
1530
1693
|
/** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
|
|
1531
1694
|
readFp64() {
|
|
1532
1695
|
this.fixlenHeader(FixlenSubtype.Fp64, 8);
|
|
1533
1696
|
return this.rawFp64();
|
|
1534
1697
|
}
|
|
1535
|
-
/**
|
|
1536
|
-
|
|
1537
|
-
|
|
1698
|
+
/**
|
|
1699
|
+
* Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string).
|
|
1700
|
+
* Pass the schema `maxlen` (byte length) for a bounded string so an
|
|
1701
|
+
* over-length is rejected as `INVALID` at the header, before the payload is
|
|
1702
|
+
* taken (see {@link fixlenLen}); the wire length is exactly the UTF-8 byte
|
|
1703
|
+
* length, so the check is exact. Omit for an unbounded string.
|
|
1704
|
+
*/
|
|
1705
|
+
readString(schemaMaxlen) {
|
|
1706
|
+
const len = this.fixlenLen(FixlenSubtype.String, schemaMaxlen);
|
|
1538
1707
|
const bytes = this.take(len);
|
|
1539
1708
|
try {
|
|
1540
1709
|
return _utf8.decode(bytes);
|
|
@@ -1546,13 +1715,18 @@ var Cursor = class {
|
|
|
1546
1715
|
* Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
|
|
1547
1716
|
* zero-copy {@link Uint8Array} view into the source buffer.
|
|
1548
1717
|
*/
|
|
1549
|
-
readBlob() {
|
|
1550
|
-
const len = this.fixlenLen(FixlenSubtype.Blob);
|
|
1718
|
+
readBlob(schemaMaxlen) {
|
|
1719
|
+
const len = this.fixlenLen(FixlenSubtype.Blob, schemaMaxlen);
|
|
1551
1720
|
return this.take(len);
|
|
1552
1721
|
}
|
|
1553
|
-
/**
|
|
1554
|
-
|
|
1555
|
-
|
|
1722
|
+
/**
|
|
1723
|
+
* Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first
|
|
1724
|
+
* per element. Pass the schema `count` for a bounded array so an over-count is
|
|
1725
|
+
* rejected as `INVALID` at the header (see {@link arrayCount}); omit it for an
|
|
1726
|
+
* unbounded array (today's behavior).
|
|
1727
|
+
*/
|
|
1728
|
+
readUnsignedArray(schemaCount) {
|
|
1729
|
+
const count = this.arrayCount(schemaCount);
|
|
1556
1730
|
const out = new Array(count);
|
|
1557
1731
|
for (let i = 0; i < count; i++) {
|
|
1558
1732
|
this.readVarint();
|
|
@@ -1561,8 +1735,8 @@ var Cursor = class {
|
|
|
1561
1735
|
return out;
|
|
1562
1736
|
}
|
|
1563
1737
|
/** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
|
|
1564
|
-
readSignedArray() {
|
|
1565
|
-
const count = this.arrayCount();
|
|
1738
|
+
readSignedArray(schemaCount) {
|
|
1739
|
+
const count = this.arrayCount(schemaCount);
|
|
1566
1740
|
const out = new Array(count);
|
|
1567
1741
|
for (let i = 0; i < count; i++) {
|
|
1568
1742
|
this.readVarint();
|
|
@@ -1575,8 +1749,8 @@ var Cursor = class {
|
|
|
1575
1749
|
* Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
|
|
1576
1750
|
* materialise only the values the caller actually needs.
|
|
1577
1751
|
*/
|
|
1578
|
-
readUnsignedArrayLong() {
|
|
1579
|
-
const count = this.arrayCount();
|
|
1752
|
+
readUnsignedArrayLong(schemaCount) {
|
|
1753
|
+
const count = this.arrayCount(schemaCount);
|
|
1580
1754
|
const out = new Array(count);
|
|
1581
1755
|
for (let i = 0; i < count; i++) {
|
|
1582
1756
|
this.readVarint();
|
|
@@ -1585,8 +1759,8 @@ var Cursor = class {
|
|
|
1585
1759
|
return out;
|
|
1586
1760
|
}
|
|
1587
1761
|
/** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
|
|
1588
|
-
readSignedArrayLong() {
|
|
1589
|
-
const count = this.arrayCount();
|
|
1762
|
+
readSignedArrayLong(schemaCount) {
|
|
1763
|
+
const count = this.arrayCount(schemaCount);
|
|
1590
1764
|
const out = new Array(count);
|
|
1591
1765
|
for (let i = 0; i < count; i++) {
|
|
1592
1766
|
this.readVarint();
|
|
@@ -1598,15 +1772,29 @@ var Cursor = class {
|
|
|
1598
1772
|
return out;
|
|
1599
1773
|
}
|
|
1600
1774
|
/** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
|
|
1601
|
-
readFp32Array() {
|
|
1602
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1775
|
+
readFp32Array(schemaCount) {
|
|
1776
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
|
|
1603
1777
|
const out = new Array(count);
|
|
1604
1778
|
for (let i = 0; i < count; i++) out[i] = this.rawFp32();
|
|
1605
1779
|
return out;
|
|
1606
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Read an fp32 array as its raw little-endian element payload (`count * 4`
|
|
1783
|
+
* bytes), zero-copy — the bit-preserving companion to {@link readFp32Array}.
|
|
1784
|
+
* Widening each element to a JS `number` quiets an fp32 *signaling* NaN just as
|
|
1785
|
+
* on the scalar path (§4.6; see {@link readFp32Raw}), so bit-exact decode reads
|
|
1786
|
+
* the whole payload here and re-emits it with {@link OStream.writeFp32ArrayRaw}
|
|
1787
|
+
* (corelib-ts#66). The header (element subtype fp32, size 4) is validated
|
|
1788
|
+
* exactly as in {@link readFp32Array}; the returned view aliases the source
|
|
1789
|
+
* buffer, like {@link readBlob}.
|
|
1790
|
+
*/
|
|
1791
|
+
readFp32ArrayRaw(schemaCount) {
|
|
1792
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
|
|
1793
|
+
return this.take(count * 4);
|
|
1794
|
+
}
|
|
1607
1795
|
/** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
|
|
1608
|
-
readFp64Array() {
|
|
1609
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8);
|
|
1796
|
+
readFp64Array(schemaCount) {
|
|
1797
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8, schemaCount);
|
|
1610
1798
|
const out = new Array(count);
|
|
1611
1799
|
for (let i = 0; i < count; i++) out[i] = this.rawFp64();
|
|
1612
1800
|
return out;
|
|
@@ -1686,8 +1874,14 @@ var Cursor = class {
|
|
|
1686
1874
|
}
|
|
1687
1875
|
const id = this.upper();
|
|
1688
1876
|
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
1689
|
-
if (wire === WireType.SequenceStart)
|
|
1690
|
-
|
|
1877
|
+
if (wire === WireType.SequenceStart) {
|
|
1878
|
+
if (this.depth + depth - 1 >= MAX_DEPTH) {
|
|
1879
|
+
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1880
|
+
}
|
|
1881
|
+
depth++;
|
|
1882
|
+
} else {
|
|
1883
|
+
this.skipValue(wire);
|
|
1884
|
+
}
|
|
1691
1885
|
}
|
|
1692
1886
|
}
|
|
1693
1887
|
// --- field helpers ------------------------------------------------------
|
|
@@ -1714,11 +1908,18 @@ var Cursor = class {
|
|
|
1714
1908
|
}
|
|
1715
1909
|
return -1;
|
|
1716
1910
|
}
|
|
1717
|
-
/**
|
|
1718
|
-
|
|
1911
|
+
/**
|
|
1912
|
+
* Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). When a
|
|
1913
|
+
* `schemaCount` is given, a count above it is a schema-bound violation and is
|
|
1914
|
+
* rejected as `INVALID` — see the check below.
|
|
1915
|
+
*/
|
|
1916
|
+
arrayCount(schemaCount) {
|
|
1719
1917
|
this.readVarint();
|
|
1720
1918
|
const count = this.num();
|
|
1721
1919
|
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1920
|
+
if (schemaCount !== void 0 && count > schemaCount) {
|
|
1921
|
+
throw invalidMsgError("array count above schema capacity");
|
|
1922
|
+
}
|
|
1722
1923
|
if (count > this.maxArrayCount) {
|
|
1723
1924
|
throw limitExceededError(
|
|
1724
1925
|
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|
|
@@ -1735,13 +1936,20 @@ var Cursor = class {
|
|
|
1735
1936
|
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1736
1937
|
if (len !== wantLen) throw invalidMsgError("fixlen float length mismatch");
|
|
1737
1938
|
}
|
|
1738
|
-
/**
|
|
1739
|
-
|
|
1939
|
+
/**
|
|
1940
|
+
* Read a scalar fixlen sub-header for a string/blob, asserting subtype;
|
|
1941
|
+
* returns the byte length. When a `schemaMaxlen` is given, a length above it
|
|
1942
|
+
* is a schema-bound violation and is rejected as `INVALID` — see below.
|
|
1943
|
+
*/
|
|
1944
|
+
fixlenLen(wantSub, schemaMaxlen) {
|
|
1740
1945
|
this.readVarint();
|
|
1741
1946
|
const sub = this.lo & 7;
|
|
1742
1947
|
const len = this.upper();
|
|
1743
1948
|
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1744
1949
|
if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
|
|
1950
|
+
if (schemaMaxlen !== void 0 && len > schemaMaxlen) {
|
|
1951
|
+
throw invalidMsgError("fixlen length above schema maxlen");
|
|
1952
|
+
}
|
|
1745
1953
|
const limit = wantSub === FixlenSubtype.String ? this.maxStringLen : this.maxBlobLen;
|
|
1746
1954
|
if (len > limit) {
|
|
1747
1955
|
const what = wantSub === FixlenSubtype.String ? "string" : "blob";
|
|
@@ -1752,11 +1960,18 @@ var Cursor = class {
|
|
|
1752
1960
|
}
|
|
1753
1961
|
return len;
|
|
1754
1962
|
}
|
|
1755
|
-
/**
|
|
1756
|
-
|
|
1963
|
+
/**
|
|
1964
|
+
* Read an array fixlen element header (count + element type); returns the
|
|
1965
|
+
* count. When a `schemaCount` is given, a count above it is a schema-bound
|
|
1966
|
+
* violation and is rejected as `INVALID` — see below.
|
|
1967
|
+
*/
|
|
1968
|
+
arrayFixlenHeader(wantSub, wantSize, schemaCount) {
|
|
1757
1969
|
this.readVarint();
|
|
1758
1970
|
const count = this.num();
|
|
1759
1971
|
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1972
|
+
if (schemaCount !== void 0 && count > schemaCount) {
|
|
1973
|
+
throw invalidMsgError("array count above schema capacity");
|
|
1974
|
+
}
|
|
1760
1975
|
if (count > this.maxArrayCount) {
|
|
1761
1976
|
throw limitExceededError(
|
|
1762
1977
|
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|