@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.global.js
CHANGED
|
@@ -98,8 +98,6 @@ var SofaBuffers = (function (exports) {
|
|
|
98
98
|
var SofabErrorCode = {
|
|
99
99
|
/** A caller argument was invalid (e.g. id out of range, empty array). */
|
|
100
100
|
Argument: "ARGUMENT",
|
|
101
|
-
/** The API was used incorrectly (e.g. unbalanced sequence end). */
|
|
102
|
-
Usage: "USAGE",
|
|
103
101
|
/** The output buffer is full and no flush sink was provided. */
|
|
104
102
|
BufferFull: "BUFFER_FULL",
|
|
105
103
|
/** The input being decoded is malformed regardless of what follows (`INVALID`). */
|
|
@@ -134,9 +132,6 @@ var SofaBuffers = (function (exports) {
|
|
|
134
132
|
function argumentError(message) {
|
|
135
133
|
return new SofabError(SofabErrorCode.Argument, message);
|
|
136
134
|
}
|
|
137
|
-
function usageError(message) {
|
|
138
|
-
return new SofabError(SofabErrorCode.Usage, message);
|
|
139
|
-
}
|
|
140
135
|
function bufferFullError(message) {
|
|
141
136
|
return new SofabError(SofabErrorCode.BufferFull, message);
|
|
142
137
|
}
|
|
@@ -450,6 +445,26 @@ var SofaBuffers = (function (exports) {
|
|
|
450
445
|
var OStream = class {
|
|
451
446
|
constructor(buffer, offset = 0, flush) {
|
|
452
447
|
this.depth = 0;
|
|
448
|
+
/**
|
|
449
|
+
* Ids of the innermost open sequences whose header has not been written yet
|
|
450
|
+
* (MESSAGE_SPEC §2 lazy framing, {@link OStream.writeSequenceBeginLazy}).
|
|
451
|
+
* Always a contiguous suffix of the open sequences: writing any field commits
|
|
452
|
+
* the whole run at once, so {@link OStream.writeSequenceEnd} can drop the
|
|
453
|
+
* innermost one by dropping the last entry. Held-back ids are encoder state,
|
|
454
|
+
* never buffer content, so a flush can never split a run.
|
|
455
|
+
*
|
|
456
|
+
* Storage plus an explicit count rather than `push`/`pop`, so the slots are
|
|
457
|
+
* reused across messages (no allocation on a pooled encoder) and so
|
|
458
|
+
* {@link OStream.commitPending} can zero the count *before* it writes.
|
|
459
|
+
*
|
|
460
|
+
* The array grows on demand and is bounded only by `MAX_DEPTH` — there is no
|
|
461
|
+
* fixed hold-back window and hence no eager-framing fallback, which is what
|
|
462
|
+
* CORELIB_PLAN §6 ("How deep the hold-back reaches") demands of an
|
|
463
|
+
* implementation that can allocate: canonical output at *every* depth.
|
|
464
|
+
*/
|
|
465
|
+
this.pending = [];
|
|
466
|
+
/** Valid entries in {@link OStream.pending}. */
|
|
467
|
+
this.nPending = 0;
|
|
453
468
|
this.kernel = getKernel();
|
|
454
469
|
if (buffer === void 0) {
|
|
455
470
|
this.buf = new Uint8Array(DEFAULT_CAPACITY);
|
|
@@ -511,6 +526,7 @@ var SofaBuffers = (function (exports) {
|
|
|
511
526
|
reset() {
|
|
512
527
|
this.pos = this.start;
|
|
513
528
|
this.depth = 0;
|
|
529
|
+
this.nPending = 0;
|
|
514
530
|
}
|
|
515
531
|
// --- scalars ------------------------------------------------------------
|
|
516
532
|
/** Write an unsigned integer field. */
|
|
@@ -661,6 +677,23 @@ var SofaBuffers = (function (exports) {
|
|
|
661
677
|
}
|
|
662
678
|
}
|
|
663
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Write an fp32 array from its raw little-endian element payload. The bytes
|
|
682
|
+
* are emitted verbatim — no per-element `setFloat32` — so a signaling NaN
|
|
683
|
+
* survives bit-for-bit (§4.6), which {@link writeFp32Array} cannot guarantee
|
|
684
|
+
* because it re-quantizes each JS `number`. `payload.length` must be a
|
|
685
|
+
* multiple of 4; the element count is `payload.length / 4`.
|
|
686
|
+
*/
|
|
687
|
+
writeFp32ArrayRaw(id, payload) {
|
|
688
|
+
if ((payload.length & 3) !== 0) {
|
|
689
|
+
throw argumentError(
|
|
690
|
+
`fp32 array payload length ${payload.length} is not a multiple of 4`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
this.arrayHead(id, WireType.ArrayFixlen, payload.length >> 2);
|
|
694
|
+
this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
|
|
695
|
+
this.writeRaw(payload);
|
|
696
|
+
}
|
|
664
697
|
/** Write an array of IEEE-754 64-bit doubles. */
|
|
665
698
|
writeFp64Array(id, values) {
|
|
666
699
|
this.arrayHead(id, WireType.ArrayFixlen, values.length);
|
|
@@ -676,20 +709,82 @@ var SofaBuffers = (function (exports) {
|
|
|
676
709
|
}
|
|
677
710
|
}
|
|
678
711
|
// --- sequences ----------------------------------------------------------
|
|
679
|
-
/**
|
|
680
|
-
|
|
712
|
+
/**
|
|
713
|
+
* Open a nested sequence (a fresh id scope) whose header is **held back**
|
|
714
|
+
* until the sequence turns out to have content.
|
|
715
|
+
*
|
|
716
|
+
* MESSAGE_SPEC §2 omits a sequence-typed field whose value equals its declared
|
|
717
|
+
* default, and "not one child was written" is exactly that condition —
|
|
718
|
+
* evaluated per child field, recursively, for free, because the message layer
|
|
719
|
+
* already omits every child equal to its default. A sequence closed with
|
|
720
|
+
* nothing in it therefore emits **nothing** instead of a two-byte empty frame,
|
|
721
|
+
* and an all-default message becomes the empty byte string. No byte image is
|
|
722
|
+
* ever compared, so in-memory layout never enters the decision.
|
|
723
|
+
*
|
|
724
|
+
* This is the only way to open a sequence. How it closes decides whether a
|
|
725
|
+
* contentless one survives: {@link OStream.writeSequenceEnd} drops it,
|
|
726
|
+
* {@link OStream.writeSequenceEndKeep} forces the frame out.
|
|
727
|
+
*/
|
|
728
|
+
writeSequenceBeginLazy(id) {
|
|
681
729
|
if (this.depth >= MAX_DEPTH) {
|
|
682
|
-
throw
|
|
730
|
+
throw argumentError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
683
731
|
}
|
|
684
|
-
|
|
732
|
+
if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
|
|
733
|
+
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
734
|
+
}
|
|
735
|
+
this.pending[this.nPending++] = id;
|
|
685
736
|
this.depth++;
|
|
686
737
|
}
|
|
687
|
-
/**
|
|
738
|
+
/**
|
|
739
|
+
* Close the current sequence, letting it **vanish** if it received no content.
|
|
740
|
+
*
|
|
741
|
+
* Use it wherever absence encodes the same value as an empty frame: a
|
|
742
|
+
* `struct`/`union` field, and an array field whose declared `default` is the
|
|
743
|
+
* empty collection (MESSAGE_SPEC §2). Where the frame must be visible, close
|
|
744
|
+
* with {@link OStream.writeSequenceEndKeep} instead.
|
|
745
|
+
*
|
|
746
|
+
* An end with no matching begin is not rejected: the encoder writes what it is
|
|
747
|
+
* told, and the resulting bytes are then malformed, which is the decoder's
|
|
748
|
+
* verdict to make. No other port refuses it. The depth counter stops at zero
|
|
749
|
+
* so the MAX_DEPTH check on begin cannot be fooled by an underflow.
|
|
750
|
+
*/
|
|
688
751
|
writeSequenceEnd() {
|
|
689
|
-
if (this.
|
|
752
|
+
if (this.nPending !== 0) {
|
|
753
|
+
this.nPending--;
|
|
754
|
+
if (this.depth > 0) this.depth--;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
690
757
|
this.ensure(1);
|
|
691
758
|
this.buf[this.pos++] = WireType.SequenceEnd;
|
|
692
|
-
this.depth--;
|
|
759
|
+
if (this.depth > 0) this.depth--;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Close the current sequence, **keeping** its frame even when it received no
|
|
763
|
+
* content.
|
|
764
|
+
*
|
|
765
|
+
* Behaves like a write: it first emits any held-back headers — this frame's
|
|
766
|
+
* and every enclosing one's — and then the end marker, so an empty sequence
|
|
767
|
+
* reaches the wire as `begin` + `end`.
|
|
768
|
+
*
|
|
769
|
+
* Required wherever the frame carries information beyond its contents:
|
|
770
|
+
* - a **wrapper-array element** (`struct`/`union`/nested row): element
|
|
771
|
+
* presence is what carries a dynamic array's length — *highest present id +
|
|
772
|
+
* 1* (MESSAGE_SPEC §5.1) — so dropping an all-default element would change
|
|
773
|
+
* the decoded length, not just the bytes;
|
|
774
|
+
* - an array field already known to **differ from a non-empty declared
|
|
775
|
+
* `default`**: absence would reconstruct that default, so the empty frame is
|
|
776
|
+
* the only encoding of "explicitly empty" (§2, §3).
|
|
777
|
+
*
|
|
778
|
+
* The two failure directions are not symmetric, which is why this is the safe
|
|
779
|
+
* choice when in doubt: using it where {@link OStream.writeSequenceEnd} would
|
|
780
|
+
* do costs one non-canonical empty frame that a decoder normalizes away, while
|
|
781
|
+
* the reverse silently changes an array's length.
|
|
782
|
+
*/
|
|
783
|
+
writeSequenceEndKeep() {
|
|
784
|
+
if (this.nPending !== 0) this.commitPending();
|
|
785
|
+
this.ensure(1);
|
|
786
|
+
this.buf[this.pos++] = WireType.SequenceEnd;
|
|
787
|
+
if (this.depth > 0) this.depth--;
|
|
693
788
|
}
|
|
694
789
|
// --- internals ----------------------------------------------------------
|
|
695
790
|
/** Ensure exactly `value`'s varint size, then write it (bigint path). */
|
|
@@ -702,12 +797,44 @@ var SofaBuffers = (function (exports) {
|
|
|
702
797
|
this.ensure(varintSizeNum(value));
|
|
703
798
|
this.pos = encodeVarintNum(value, this.buf, this.pos);
|
|
704
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Write a field header, the `(id << 3) | wireType` tag, as a varint.
|
|
802
|
+
*
|
|
803
|
+
* This is the single choke point every field write passes through — the
|
|
804
|
+
* scalar, fixlen, float, string, blob and both array writers all reach the
|
|
805
|
+
* wire through `header` / `fixlenHead` / `arrayHead`, and `fixlenHead` and
|
|
806
|
+
* `arrayHead` are themselves nothing but `header` plus a follow-up varint. So
|
|
807
|
+
* this is also where a held-back sequence run is committed: the field about to
|
|
808
|
+
* be written is content, which means every enclosing sequence is non-default
|
|
809
|
+
* and must be framed after all (MESSAGE_SPEC §2).
|
|
810
|
+
*
|
|
811
|
+
* The only writers that do *not* pass through here are the two sequence
|
|
812
|
+
* closers, which must not commit ({@link OStream.writeSequenceEnd}) or commit
|
|
813
|
+
* explicitly ({@link OStream.writeSequenceEndKeep}), and
|
|
814
|
+
* {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
|
|
815
|
+
*/
|
|
705
816
|
header(id, type) {
|
|
706
817
|
if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
|
|
707
818
|
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
708
819
|
}
|
|
820
|
+
if (this.nPending !== 0) this.commitPending();
|
|
709
821
|
this.putVarintNum(id * 8 + type);
|
|
710
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Write out the held-back sequence headers, **outermost first**, and clear the
|
|
825
|
+
* run. Runs at most once per non-default sequence, never per field — the cost
|
|
826
|
+
* on the hot path is the single `nPending` test in {@link header}.
|
|
827
|
+
*
|
|
828
|
+
* The count is zeroed before the first byte goes out, so a write re-entered
|
|
829
|
+
* from a flush sink cannot emit the same run twice.
|
|
830
|
+
*/
|
|
831
|
+
commitPending() {
|
|
832
|
+
const n = this.nPending;
|
|
833
|
+
this.nPending = 0;
|
|
834
|
+
for (let i = 0; i < n; i++) {
|
|
835
|
+
this.putVarintNum(this.pending[i] * 8 + WireType.SequenceStart);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
711
838
|
fixlenHead(id, length, subtype) {
|
|
712
839
|
this.header(id, WireType.Fixlen);
|
|
713
840
|
this.putVarintNum(length * 8 + subtype);
|
|
@@ -827,9 +954,14 @@ var SofaBuffers = (function (exports) {
|
|
|
827
954
|
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
828
955
|
const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
|
|
829
956
|
if (len !== want) throw invalidMsgError("fixlen float length mismatch");
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
957
|
+
if (sub === FixlenSubtype.Fp32) {
|
|
958
|
+
const p = this.p;
|
|
959
|
+
const value = this.readFp32();
|
|
960
|
+
top.fp32?.(id, value, top.fp32Raw ? this.buf.subarray(p, p + 4) : void 0);
|
|
961
|
+
} else {
|
|
962
|
+
const value = this.readFp64();
|
|
963
|
+
top.fp64?.(id, value);
|
|
964
|
+
}
|
|
833
965
|
} else {
|
|
834
966
|
const chunk = this.take(len);
|
|
835
967
|
if (sub === FixlenSubtype.String) top.string?.(id, len, 0, chunk);
|
|
@@ -868,9 +1000,11 @@ var SofaBuffers = (function (exports) {
|
|
|
868
1000
|
else throw invalidMsgError("invalid fixlen array element type");
|
|
869
1001
|
top.arrayBegin?.(id, kind, count);
|
|
870
1002
|
if (kind === ArrayKind.Fp32) {
|
|
1003
|
+
const wantRaw = top.fp32Raw === true;
|
|
871
1004
|
for (let i = 0; i < count; i++) {
|
|
1005
|
+
const p = this.p;
|
|
872
1006
|
const value = this.readFp32();
|
|
873
|
-
top.arrayFp32?.(id, i, value);
|
|
1007
|
+
top.arrayFp32?.(id, i, value, wantRaw ? this.buf.subarray(p, p + 4) : void 0);
|
|
874
1008
|
}
|
|
875
1009
|
} else {
|
|
876
1010
|
for (let i = 0; i < count; i++) {
|
|
@@ -1131,8 +1265,10 @@ var SofaBuffers = (function (exports) {
|
|
|
1131
1265
|
i = this.fpStep(input, i);
|
|
1132
1266
|
if (this.have < this.need) return;
|
|
1133
1267
|
const value = this.fixSub === FixlenSubtype.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
|
|
1134
|
-
if (this.fixSub === FixlenSubtype.Fp32)
|
|
1135
|
-
|
|
1268
|
+
if (this.fixSub === FixlenSubtype.Fp32) {
|
|
1269
|
+
const top = this.top();
|
|
1270
|
+
top.fp32?.(this.id, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
|
|
1271
|
+
} else this.top().fp64?.(this.id, value);
|
|
1136
1272
|
this.state = 0 /* Header */;
|
|
1137
1273
|
break;
|
|
1138
1274
|
}
|
|
@@ -1214,8 +1350,10 @@ var SofaBuffers = (function (exports) {
|
|
|
1214
1350
|
i = this.fpStep(input, i);
|
|
1215
1351
|
if (this.have < this.need) return;
|
|
1216
1352
|
const value = this.arrKind === ArrayKind.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
|
|
1217
|
-
if (this.arrKind === ArrayKind.Fp32)
|
|
1218
|
-
|
|
1353
|
+
if (this.arrKind === ArrayKind.Fp32) {
|
|
1354
|
+
const top = this.top();
|
|
1355
|
+
top.arrayFp32?.(this.id, this.arrIndex, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
|
|
1356
|
+
} else this.top().arrayFp64?.(this.id, this.arrIndex, value);
|
|
1219
1357
|
this.have = 0;
|
|
1220
1358
|
this.advanceArray();
|
|
1221
1359
|
break;
|
|
@@ -1510,7 +1648,12 @@ var SofaBuffers = (function (exports) {
|
|
|
1510
1648
|
}
|
|
1511
1649
|
const id = this.upper();
|
|
1512
1650
|
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
1513
|
-
if (wire === WireType.SequenceStart)
|
|
1651
|
+
if (wire === WireType.SequenceStart) {
|
|
1652
|
+
if (this.depth >= MAX_DEPTH) {
|
|
1653
|
+
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1654
|
+
}
|
|
1655
|
+
this.depth++;
|
|
1656
|
+
}
|
|
1514
1657
|
this.id = id;
|
|
1515
1658
|
this.wire = wire;
|
|
1516
1659
|
this.fixSub = this.peekFixSub(wire);
|
|
@@ -1531,14 +1674,40 @@ var SofaBuffers = (function (exports) {
|
|
|
1531
1674
|
this.fixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1532
1675
|
return this.rawFp32();
|
|
1533
1676
|
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Read a 32-bit float scalar as its raw 4 wire bytes (little-endian), zero-copy
|
|
1679
|
+
* — the bit-preserving companion to {@link readFp32}.
|
|
1680
|
+
*
|
|
1681
|
+
* {@link readFp32} returns a JS `number` (a 64-bit double), and widening an
|
|
1682
|
+
* fp32 *signaling* NaN into a double quiets it (0x7F800001 → 0x7FC00001), so a
|
|
1683
|
+
* value consumer can never round-trip one bit-for-bit (§4.6). Generated
|
|
1684
|
+
* bit-exact decode reads the bytes here instead and re-emits them verbatim with
|
|
1685
|
+
* {@link OStream.writeFixlen} (subtype fp32) — mirroring the visitor `raw`
|
|
1686
|
+
* channel on the push paths (fast.ts / state.ts), which the pull path was
|
|
1687
|
+
* missing (corelib-ts#66).
|
|
1688
|
+
*
|
|
1689
|
+
* The header (subtype fp32, length 4) is validated exactly as in
|
|
1690
|
+
* {@link readFp32}; the returned view aliases the source buffer, valid only
|
|
1691
|
+
* until it is reused, like {@link readBlob}.
|
|
1692
|
+
*/
|
|
1693
|
+
readFp32Raw() {
|
|
1694
|
+
this.fixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1695
|
+
return this.take(4);
|
|
1696
|
+
}
|
|
1534
1697
|
/** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
|
|
1535
1698
|
readFp64() {
|
|
1536
1699
|
this.fixlenHeader(FixlenSubtype.Fp64, 8);
|
|
1537
1700
|
return this.rawFp64();
|
|
1538
1701
|
}
|
|
1539
|
-
/**
|
|
1540
|
-
|
|
1541
|
-
|
|
1702
|
+
/**
|
|
1703
|
+
* Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string).
|
|
1704
|
+
* Pass the schema `maxlen` (byte length) for a bounded string so an
|
|
1705
|
+
* over-length is rejected as `INVALID` at the header, before the payload is
|
|
1706
|
+
* taken (see {@link fixlenLen}); the wire length is exactly the UTF-8 byte
|
|
1707
|
+
* length, so the check is exact. Omit for an unbounded string.
|
|
1708
|
+
*/
|
|
1709
|
+
readString(schemaMaxlen) {
|
|
1710
|
+
const len = this.fixlenLen(FixlenSubtype.String, schemaMaxlen);
|
|
1542
1711
|
const bytes = this.take(len);
|
|
1543
1712
|
try {
|
|
1544
1713
|
return _utf8.decode(bytes);
|
|
@@ -1550,13 +1719,18 @@ var SofaBuffers = (function (exports) {
|
|
|
1550
1719
|
* Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
|
|
1551
1720
|
* zero-copy {@link Uint8Array} view into the source buffer.
|
|
1552
1721
|
*/
|
|
1553
|
-
readBlob() {
|
|
1554
|
-
const len = this.fixlenLen(FixlenSubtype.Blob);
|
|
1722
|
+
readBlob(schemaMaxlen) {
|
|
1723
|
+
const len = this.fixlenLen(FixlenSubtype.Blob, schemaMaxlen);
|
|
1555
1724
|
return this.take(len);
|
|
1556
1725
|
}
|
|
1557
|
-
/**
|
|
1558
|
-
|
|
1559
|
-
|
|
1726
|
+
/**
|
|
1727
|
+
* Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first
|
|
1728
|
+
* per element. Pass the schema `count` for a bounded array so an over-count is
|
|
1729
|
+
* rejected as `INVALID` at the header (see {@link arrayCount}); omit it for an
|
|
1730
|
+
* unbounded array (today's behavior).
|
|
1731
|
+
*/
|
|
1732
|
+
readUnsignedArray(schemaCount) {
|
|
1733
|
+
const count = this.arrayCount(schemaCount);
|
|
1560
1734
|
const out = new Array(count);
|
|
1561
1735
|
for (let i = 0; i < count; i++) {
|
|
1562
1736
|
this.readVarint();
|
|
@@ -1565,8 +1739,8 @@ var SofaBuffers = (function (exports) {
|
|
|
1565
1739
|
return out;
|
|
1566
1740
|
}
|
|
1567
1741
|
/** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
|
|
1568
|
-
readSignedArray() {
|
|
1569
|
-
const count = this.arrayCount();
|
|
1742
|
+
readSignedArray(schemaCount) {
|
|
1743
|
+
const count = this.arrayCount(schemaCount);
|
|
1570
1744
|
const out = new Array(count);
|
|
1571
1745
|
for (let i = 0; i < count; i++) {
|
|
1572
1746
|
this.readVarint();
|
|
@@ -1579,8 +1753,8 @@ var SofaBuffers = (function (exports) {
|
|
|
1579
1753
|
* Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
|
|
1580
1754
|
* materialise only the values the caller actually needs.
|
|
1581
1755
|
*/
|
|
1582
|
-
readUnsignedArrayLong() {
|
|
1583
|
-
const count = this.arrayCount();
|
|
1756
|
+
readUnsignedArrayLong(schemaCount) {
|
|
1757
|
+
const count = this.arrayCount(schemaCount);
|
|
1584
1758
|
const out = new Array(count);
|
|
1585
1759
|
for (let i = 0; i < count; i++) {
|
|
1586
1760
|
this.readVarint();
|
|
@@ -1589,8 +1763,8 @@ var SofaBuffers = (function (exports) {
|
|
|
1589
1763
|
return out;
|
|
1590
1764
|
}
|
|
1591
1765
|
/** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
|
|
1592
|
-
readSignedArrayLong() {
|
|
1593
|
-
const count = this.arrayCount();
|
|
1766
|
+
readSignedArrayLong(schemaCount) {
|
|
1767
|
+
const count = this.arrayCount(schemaCount);
|
|
1594
1768
|
const out = new Array(count);
|
|
1595
1769
|
for (let i = 0; i < count; i++) {
|
|
1596
1770
|
this.readVarint();
|
|
@@ -1602,15 +1776,29 @@ var SofaBuffers = (function (exports) {
|
|
|
1602
1776
|
return out;
|
|
1603
1777
|
}
|
|
1604
1778
|
/** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
|
|
1605
|
-
readFp32Array() {
|
|
1606
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1779
|
+
readFp32Array(schemaCount) {
|
|
1780
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
|
|
1607
1781
|
const out = new Array(count);
|
|
1608
1782
|
for (let i = 0; i < count; i++) out[i] = this.rawFp32();
|
|
1609
1783
|
return out;
|
|
1610
1784
|
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Read an fp32 array as its raw little-endian element payload (`count * 4`
|
|
1787
|
+
* bytes), zero-copy — the bit-preserving companion to {@link readFp32Array}.
|
|
1788
|
+
* Widening each element to a JS `number` quiets an fp32 *signaling* NaN just as
|
|
1789
|
+
* on the scalar path (§4.6; see {@link readFp32Raw}), so bit-exact decode reads
|
|
1790
|
+
* the whole payload here and re-emits it with {@link OStream.writeFp32ArrayRaw}
|
|
1791
|
+
* (corelib-ts#66). The header (element subtype fp32, size 4) is validated
|
|
1792
|
+
* exactly as in {@link readFp32Array}; the returned view aliases the source
|
|
1793
|
+
* buffer, like {@link readBlob}.
|
|
1794
|
+
*/
|
|
1795
|
+
readFp32ArrayRaw(schemaCount) {
|
|
1796
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
|
|
1797
|
+
return this.take(count * 4);
|
|
1798
|
+
}
|
|
1611
1799
|
/** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
|
|
1612
|
-
readFp64Array() {
|
|
1613
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8);
|
|
1800
|
+
readFp64Array(schemaCount) {
|
|
1801
|
+
const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8, schemaCount);
|
|
1614
1802
|
const out = new Array(count);
|
|
1615
1803
|
for (let i = 0; i < count; i++) out[i] = this.rawFp64();
|
|
1616
1804
|
return out;
|
|
@@ -1690,8 +1878,14 @@ var SofaBuffers = (function (exports) {
|
|
|
1690
1878
|
}
|
|
1691
1879
|
const id = this.upper();
|
|
1692
1880
|
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
1693
|
-
if (wire === WireType.SequenceStart)
|
|
1694
|
-
|
|
1881
|
+
if (wire === WireType.SequenceStart) {
|
|
1882
|
+
if (this.depth + depth - 1 >= MAX_DEPTH) {
|
|
1883
|
+
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1884
|
+
}
|
|
1885
|
+
depth++;
|
|
1886
|
+
} else {
|
|
1887
|
+
this.skipValue(wire);
|
|
1888
|
+
}
|
|
1695
1889
|
}
|
|
1696
1890
|
}
|
|
1697
1891
|
// --- field helpers ------------------------------------------------------
|
|
@@ -1718,11 +1912,18 @@ var SofaBuffers = (function (exports) {
|
|
|
1718
1912
|
}
|
|
1719
1913
|
return -1;
|
|
1720
1914
|
}
|
|
1721
|
-
/**
|
|
1722
|
-
|
|
1915
|
+
/**
|
|
1916
|
+
* Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). When a
|
|
1917
|
+
* `schemaCount` is given, a count above it is a schema-bound violation and is
|
|
1918
|
+
* rejected as `INVALID` — see the check below.
|
|
1919
|
+
*/
|
|
1920
|
+
arrayCount(schemaCount) {
|
|
1723
1921
|
this.readVarint();
|
|
1724
1922
|
const count = this.num();
|
|
1725
1923
|
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1924
|
+
if (schemaCount !== void 0 && count > schemaCount) {
|
|
1925
|
+
throw invalidMsgError("array count above schema capacity");
|
|
1926
|
+
}
|
|
1726
1927
|
if (count > this.maxArrayCount) {
|
|
1727
1928
|
throw limitExceededError(
|
|
1728
1929
|
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|
|
@@ -1739,13 +1940,20 @@ var SofaBuffers = (function (exports) {
|
|
|
1739
1940
|
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1740
1941
|
if (len !== wantLen) throw invalidMsgError("fixlen float length mismatch");
|
|
1741
1942
|
}
|
|
1742
|
-
/**
|
|
1743
|
-
|
|
1943
|
+
/**
|
|
1944
|
+
* Read a scalar fixlen sub-header for a string/blob, asserting subtype;
|
|
1945
|
+
* returns the byte length. When a `schemaMaxlen` is given, a length above it
|
|
1946
|
+
* is a schema-bound violation and is rejected as `INVALID` — see below.
|
|
1947
|
+
*/
|
|
1948
|
+
fixlenLen(wantSub, schemaMaxlen) {
|
|
1744
1949
|
this.readVarint();
|
|
1745
1950
|
const sub = this.lo & 7;
|
|
1746
1951
|
const len = this.upper();
|
|
1747
1952
|
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1748
1953
|
if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
|
|
1954
|
+
if (schemaMaxlen !== void 0 && len > schemaMaxlen) {
|
|
1955
|
+
throw invalidMsgError("fixlen length above schema maxlen");
|
|
1956
|
+
}
|
|
1749
1957
|
const limit = wantSub === FixlenSubtype.String ? this.maxStringLen : this.maxBlobLen;
|
|
1750
1958
|
if (len > limit) {
|
|
1751
1959
|
const what = wantSub === FixlenSubtype.String ? "string" : "blob";
|
|
@@ -1756,11 +1964,18 @@ var SofaBuffers = (function (exports) {
|
|
|
1756
1964
|
}
|
|
1757
1965
|
return len;
|
|
1758
1966
|
}
|
|
1759
|
-
/**
|
|
1760
|
-
|
|
1967
|
+
/**
|
|
1968
|
+
* Read an array fixlen element header (count + element type); returns the
|
|
1969
|
+
* count. When a `schemaCount` is given, a count above it is a schema-bound
|
|
1970
|
+
* violation and is rejected as `INVALID` — see below.
|
|
1971
|
+
*/
|
|
1972
|
+
arrayFixlenHeader(wantSub, wantSize, schemaCount) {
|
|
1761
1973
|
this.readVarint();
|
|
1762
1974
|
const count = this.num();
|
|
1763
1975
|
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1976
|
+
if (schemaCount !== void 0 && count > schemaCount) {
|
|
1977
|
+
throw invalidMsgError("array count above schema capacity");
|
|
1978
|
+
}
|
|
1764
1979
|
if (count > this.maxArrayCount) {
|
|
1765
1980
|
throw limitExceededError(
|
|
1766
1981
|
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|