@sofa-buffers/corelib 0.10.0 → 0.11.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 +213 -0
- package/README.md +725 -125
- package/dist/index.cjs +2689 -1276
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1672 -477
- package/dist/index.d.ts +1672 -477
- package/dist/index.global.js +2689 -1276
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +2678 -1273
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/dist/index.global.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
var SofaBuffers = (function (exports) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
6
5
|
var __export = (target, all) => {
|
|
7
6
|
for (var name in all)
|
|
@@ -14,26 +13,35 @@ var SofaBuffers = (function (exports) {
|
|
|
14
13
|
API_VERSION: () => API_VERSION,
|
|
15
14
|
ARRAY_MAX: () => ARRAY_MAX,
|
|
16
15
|
ArrayKind: () => ArrayKind,
|
|
17
|
-
|
|
16
|
+
BlobSeq: () => BlobSeq,
|
|
18
17
|
DecodeStatus: () => DecodeStatus,
|
|
18
|
+
ElementSeq: () => ElementSeq,
|
|
19
19
|
FIXLEN_MAX: () => FIXLEN_MAX,
|
|
20
20
|
FixlenSubtype: () => FixlenSubtype,
|
|
21
|
+
FramedSeq: () => FramedSeq,
|
|
21
22
|
I64_MAX: () => I64_MAX,
|
|
22
23
|
I64_MIN: () => I64_MIN,
|
|
23
24
|
ID_MAX: () => ID_MAX,
|
|
24
25
|
IStream: () => IStream,
|
|
25
26
|
Long: () => Long,
|
|
26
27
|
MAX_DEPTH: () => MAX_DEPTH,
|
|
28
|
+
MIN_OUTPUT_BUFFER: () => MIN_OUTPUT_BUFFER,
|
|
27
29
|
OStream: () => OStream,
|
|
30
|
+
PayloadAcc: () => PayloadAcc,
|
|
28
31
|
SofabError: () => SofabError,
|
|
29
32
|
SofabErrorCode: () => SofabErrorCode,
|
|
33
|
+
StringSeq: () => StringSeq,
|
|
30
34
|
U64_MAX: () => U64_MAX,
|
|
35
|
+
UNBOUNDED: () => UNBOUNDED,
|
|
31
36
|
WireType: () => WireType,
|
|
32
37
|
decode: () => decode,
|
|
38
|
+
decodeUtf8: () => decodeUtf8,
|
|
39
|
+
elementsEqual: () => elementsEqual,
|
|
40
|
+
fp32RawBytes: () => fp32RawBytes,
|
|
33
41
|
getKernel: () => getKernel,
|
|
42
|
+
growingOStream: () => growingOStream,
|
|
34
43
|
jsKernel: () => jsKernel,
|
|
35
|
-
|
|
36
|
-
loadWasmKernel: () => loadWasmKernel,
|
|
44
|
+
longElementsEqual: () => longElementsEqual,
|
|
37
45
|
setKernel: () => setKernel
|
|
38
46
|
});
|
|
39
47
|
|
|
@@ -84,6 +92,9 @@ var SofaBuffers = (function (exports) {
|
|
|
84
92
|
var I64_MIN = -0x8000000000000000n;
|
|
85
93
|
var I64_MAX = 0x7fffffffffffffffn;
|
|
86
94
|
var VARINT_MAX_BYTES = 10;
|
|
95
|
+
var MIN_OUTPUT_BUFFER = 1;
|
|
96
|
+
var FP32_HANDLE_MIN = 64;
|
|
97
|
+
var FP64_HANDLE_MIN = 16;
|
|
87
98
|
var MAX_DEPTH = 255;
|
|
88
99
|
var DecodeStatus = {
|
|
89
100
|
/** The bytes ended exactly at a field boundary — a valid message. */
|
|
@@ -112,7 +123,8 @@ var SofaBuffers = (function (exports) {
|
|
|
112
123
|
/**
|
|
113
124
|
* A receiver-configured decode limit was exceeded — a dynamic array, string or
|
|
114
125
|
* blob on the wire claims more elements / bytes than the caller's
|
|
115
|
-
*
|
|
126
|
+
* receiver cap (`max_dyn_array_count` / `max_dyn_string_len` /
|
|
127
|
+
* `max_dyn_blob_len`, §6.2.1 — stated by generated code, never by this codec)
|
|
116
128
|
* allows. Deliberately distinct from {@link SofabErrorCode.InvalidMsg}:
|
|
117
129
|
* exceeding a limit is *policy*, not wire malformation — the identical bytes
|
|
118
130
|
* decode fine under a looser limit — so differential fuzzing must not read it
|
|
@@ -145,6 +157,34 @@ var SofaBuffers = (function (exports) {
|
|
|
145
157
|
return new SofabError(SofabErrorCode.LimitExceeded, message);
|
|
146
158
|
}
|
|
147
159
|
|
|
160
|
+
// src/varint/bits64.ts
|
|
161
|
+
var SCRATCH = new ArrayBuffer(8);
|
|
162
|
+
var S_U64 = new BigUint64Array(SCRATCH);
|
|
163
|
+
var S_I64 = new BigInt64Array(SCRATCH);
|
|
164
|
+
var S_U32 = new Uint32Array(SCRATCH);
|
|
165
|
+
S_U64[0] = 0n;
|
|
166
|
+
S_U32[0] = 1;
|
|
167
|
+
var LO = S_U64[0] === 1n ? 0 : 1;
|
|
168
|
+
var HI = LO ^ 1;
|
|
169
|
+
function splitU64(value) {
|
|
170
|
+
S_U64[0] = value;
|
|
171
|
+
return S_U64[0] === value;
|
|
172
|
+
}
|
|
173
|
+
function splitI64(value) {
|
|
174
|
+
S_I64[0] = value;
|
|
175
|
+
return S_I64[0] === value;
|
|
176
|
+
}
|
|
177
|
+
function joinU64(lo, hi) {
|
|
178
|
+
S_U32[LO] = lo;
|
|
179
|
+
S_U32[HI] = hi;
|
|
180
|
+
return S_U64[0];
|
|
181
|
+
}
|
|
182
|
+
function joinI64(lo, hi) {
|
|
183
|
+
S_U32[LO] = lo;
|
|
184
|
+
S_U32[HI] = hi;
|
|
185
|
+
return S_I64[0];
|
|
186
|
+
}
|
|
187
|
+
|
|
148
188
|
// src/long.ts
|
|
149
189
|
var _Long = class _Long {
|
|
150
190
|
constructor(low, high) {
|
|
@@ -155,9 +195,14 @@ var SofaBuffers = (function (exports) {
|
|
|
155
195
|
static fromBits(low, high) {
|
|
156
196
|
return new _Long(low, high);
|
|
157
197
|
}
|
|
158
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Split a `bigint` into its low/high 32-bit halves (two's complement). The
|
|
200
|
+
* `BigInt64Array` store *is* `ToBigInt64` — reduction modulo 2^64 — so an
|
|
201
|
+
* out-of-range value keeps exactly the halves the masks kept (bits64).
|
|
202
|
+
*/
|
|
159
203
|
static fromBigInt(value) {
|
|
160
|
-
|
|
204
|
+
S_I64[0] = value;
|
|
205
|
+
return new _Long(S_U32[LO], S_U32[HI]);
|
|
161
206
|
}
|
|
162
207
|
/** From an integer `number` (`|n| < 2^53`); sign handled via `bigint` once. */
|
|
163
208
|
static fromNumber(n) {
|
|
@@ -170,9 +215,7 @@ var SofaBuffers = (function (exports) {
|
|
|
170
215
|
}
|
|
171
216
|
/** Materialise as a `bigint`. `signed` reads the high bit as two's complement. */
|
|
172
217
|
toBigInt(signed = false) {
|
|
173
|
-
|
|
174
|
-
if (signed && (this.high & 2147483648) !== 0) r -= 0x10000000000000000n;
|
|
175
|
-
return r;
|
|
218
|
+
return signed ? joinI64(this.low, this.high) : joinU64(this.low, this.high);
|
|
176
219
|
}
|
|
177
220
|
/** Decimal string (`signed` interprets the high bit as two's complement). */
|
|
178
221
|
toString(signed = false) {
|
|
@@ -190,50 +233,40 @@ var SofaBuffers = (function (exports) {
|
|
|
190
233
|
var Long = _Long;
|
|
191
234
|
|
|
192
235
|
// src/varint/leb128.ts
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let n = 0;
|
|
197
|
-
while (hi !== 0) {
|
|
198
|
-
n++;
|
|
199
|
-
const next = (lo >>> 7 | hi << 25) >>> 0;
|
|
200
|
-
hi >>>= 7;
|
|
201
|
-
lo = next;
|
|
236
|
+
function varintSizeLoHi(lo, hi) {
|
|
237
|
+
if (hi === 0) {
|
|
238
|
+
return lo < 128 ? 1 : lo < 16384 ? 2 : lo < 2097152 ? 3 : lo < 268435456 ? 4 : 5;
|
|
202
239
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
lo >>>= 7;
|
|
206
|
-
}
|
|
207
|
-
return n + 1;
|
|
208
|
-
}
|
|
209
|
-
function encodeVarint(value, out, pos) {
|
|
210
|
-
let lo = Number(value & 0xffffffffn) >>> 0;
|
|
211
|
-
let hi = Number(value >> 32n & 0xffffffffn) >>> 0;
|
|
212
|
-
while (hi !== 0) {
|
|
213
|
-
out[pos++] = lo & 127 | 128;
|
|
214
|
-
lo = (lo >>> 7 | hi << 25) >>> 0;
|
|
215
|
-
hi >>>= 7;
|
|
216
|
-
}
|
|
217
|
-
while (lo > 127) {
|
|
218
|
-
out[pos++] = lo & 127 | 128;
|
|
219
|
-
lo >>>= 7;
|
|
220
|
-
}
|
|
221
|
-
out[pos++] = lo;
|
|
222
|
-
return pos;
|
|
240
|
+
const h = hi >>> 3;
|
|
241
|
+
return h === 0 ? 5 : 5 + (h < 128 ? 1 : h < 16384 ? 2 : h < 2097152 ? 3 : h < 268435456 ? 4 : 5);
|
|
223
242
|
}
|
|
224
243
|
function encodeVarintLoHi(lo, hi, out, pos) {
|
|
225
244
|
lo >>>= 0;
|
|
226
245
|
hi >>>= 0;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
246
|
+
if (hi === 0) {
|
|
247
|
+
while (lo > 127) {
|
|
248
|
+
out[pos++] = lo & 127 | 128;
|
|
249
|
+
lo >>>= 7;
|
|
250
|
+
}
|
|
251
|
+
out[pos++] = lo;
|
|
252
|
+
return pos;
|
|
253
|
+
}
|
|
254
|
+
out[pos++] = lo & 127 | 128;
|
|
255
|
+
out[pos++] = lo >>> 7 & 127 | 128;
|
|
256
|
+
out[pos++] = lo >>> 14 & 127 | 128;
|
|
257
|
+
out[pos++] = lo >>> 21 & 127 | 128;
|
|
258
|
+
const b4 = lo >>> 28 | (hi & 7) << 4;
|
|
259
|
+
let h = hi >>> 3;
|
|
260
|
+
if (h === 0) {
|
|
261
|
+
out[pos++] = b4;
|
|
262
|
+
return pos;
|
|
231
263
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
264
|
+
out[pos++] = b4 | 128;
|
|
265
|
+
while (h > 127) {
|
|
266
|
+
out[pos++] = h & 127 | 128;
|
|
267
|
+
h >>>= 7;
|
|
235
268
|
}
|
|
236
|
-
out[pos++] =
|
|
269
|
+
out[pos++] = h;
|
|
237
270
|
return pos;
|
|
238
271
|
}
|
|
239
272
|
function varintSizeNum(value) {
|
|
@@ -263,51 +296,78 @@ var SofaBuffers = (function (exports) {
|
|
|
263
296
|
}
|
|
264
297
|
|
|
265
298
|
// src/varint/num64.ts
|
|
266
|
-
var
|
|
299
|
+
var SCRATCH_BUF = new ArrayBuffer(8);
|
|
300
|
+
var SCRATCH2 = new DataView(SCRATCH_BUF);
|
|
301
|
+
var SCRATCH_BYTES = new Uint8Array(SCRATCH_BUF);
|
|
302
|
+
var SCRATCH_U32 = new Uint32Array(SCRATCH_BUF);
|
|
303
|
+
var SCRATCH_F32 = new Float32Array(SCRATCH_BUF, 0, 1);
|
|
304
|
+
var SCRATCH_F64 = new Float64Array(SCRATCH_BUF);
|
|
267
305
|
function toBigInt(value) {
|
|
268
306
|
if (typeof value === "bigint") return value;
|
|
269
307
|
if (!Number.isInteger(value)) {
|
|
270
|
-
throw
|
|
308
|
+
throw argumentError(`expected an integer, got ${value}`);
|
|
271
309
|
}
|
|
272
310
|
return BigInt(value);
|
|
273
311
|
}
|
|
274
|
-
function inU64(value) {
|
|
275
|
-
return value >= 0n && value <= U64_MAX;
|
|
276
|
-
}
|
|
277
|
-
function inI64(value) {
|
|
278
|
-
return value >= I64_MIN && value <= I64_MAX;
|
|
279
|
-
}
|
|
280
312
|
function packFp32(out, pos, value) {
|
|
281
|
-
|
|
282
|
-
out[pos] =
|
|
283
|
-
out[pos + 1] =
|
|
284
|
-
out[pos + 2] =
|
|
285
|
-
out[pos + 3] =
|
|
313
|
+
SCRATCH2.setFloat32(0, value, true);
|
|
314
|
+
out[pos] = SCRATCH_BYTES[0];
|
|
315
|
+
out[pos + 1] = SCRATCH_BYTES[1];
|
|
316
|
+
out[pos + 2] = SCRATCH_BYTES[2];
|
|
317
|
+
out[pos + 3] = SCRATCH_BYTES[3];
|
|
286
318
|
return pos + 4;
|
|
287
319
|
}
|
|
288
320
|
function packFp64(out, pos, value) {
|
|
289
|
-
|
|
290
|
-
|
|
321
|
+
SCRATCH2.setFloat64(0, value, true);
|
|
322
|
+
out[pos] = SCRATCH_BYTES[0];
|
|
323
|
+
out[pos + 1] = SCRATCH_BYTES[1];
|
|
324
|
+
out[pos + 2] = SCRATCH_BYTES[2];
|
|
325
|
+
out[pos + 3] = SCRATCH_BYTES[3];
|
|
326
|
+
out[pos + 4] = SCRATCH_BYTES[4];
|
|
327
|
+
out[pos + 5] = SCRATCH_BYTES[5];
|
|
328
|
+
out[pos + 6] = SCRATCH_BYTES[6];
|
|
329
|
+
out[pos + 7] = SCRATCH_BYTES[7];
|
|
291
330
|
return pos + 8;
|
|
292
331
|
}
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
332
|
+
function fp32Bits(value) {
|
|
333
|
+
SCRATCH2.setFloat32(0, value, true);
|
|
334
|
+
return SCRATCH2.getUint32(0, true);
|
|
335
|
+
}
|
|
336
|
+
function fp64BitsLo(value) {
|
|
337
|
+
SCRATCH2.setFloat64(0, value, true);
|
|
338
|
+
return SCRATCH2.getUint32(0, true);
|
|
339
|
+
}
|
|
340
|
+
function fp64BitsHi(value) {
|
|
341
|
+
SCRATCH2.setFloat64(0, value, true);
|
|
342
|
+
return SCRATCH2.getUint32(4, true);
|
|
299
343
|
}
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
return
|
|
344
|
+
function fp32FromBits(bits) {
|
|
345
|
+
SCRATCH_U32[0] = bits;
|
|
346
|
+
return SCRATCH_F32[0];
|
|
347
|
+
}
|
|
348
|
+
function fp32RawBytes(bits) {
|
|
349
|
+
const out = new Uint8Array(4);
|
|
350
|
+
out[0] = bits & 255;
|
|
351
|
+
out[1] = bits >>> 8 & 255;
|
|
352
|
+
out[2] = bits >>> 16 & 255;
|
|
353
|
+
out[3] = bits >>> 24 & 255;
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
function fp64FromBits(lo, hi) {
|
|
357
|
+
SCRATCH_U32[LO] = lo;
|
|
358
|
+
SCRATCH_U32[HI] = hi;
|
|
359
|
+
return SCRATCH_F64[0];
|
|
303
360
|
}
|
|
304
361
|
|
|
305
362
|
// src/varint/zigzag.ts
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
363
|
+
function encodeZigzagVarintLoHi(lo, hi, out, pos) {
|
|
364
|
+
const sgn = -(hi >>> 31) >>> 0;
|
|
365
|
+
return encodeVarintLoHi(
|
|
366
|
+
(lo << 1 >>> 0 ^ sgn) >>> 0,
|
|
367
|
+
((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0,
|
|
368
|
+
out,
|
|
369
|
+
pos
|
|
370
|
+
);
|
|
311
371
|
}
|
|
312
372
|
|
|
313
373
|
// src/backend/js.ts
|
|
@@ -315,36 +375,161 @@ var SofaBuffers = (function (exports) {
|
|
|
315
375
|
var jsKernel = {
|
|
316
376
|
name: "js",
|
|
317
377
|
encodeUnsignedVarints(values, out, pos) {
|
|
318
|
-
|
|
378
|
+
const n = values.length;
|
|
379
|
+
const ctor = values.constructor;
|
|
380
|
+
if (ctor === BigUint64Array) {
|
|
381
|
+
const a = values;
|
|
382
|
+
const h = new Uint32Array(a.buffer, a.byteOffset, a.length * 2);
|
|
383
|
+
for (let i = 0; i < n; i++) {
|
|
384
|
+
pos = encodeVarintLoHi(h[i * 2 + LO], h[i * 2 + HI], out, pos);
|
|
385
|
+
}
|
|
386
|
+
return pos;
|
|
387
|
+
}
|
|
388
|
+
if (ctor === Uint8Array || ctor === Uint16Array || ctor === Uint32Array) {
|
|
389
|
+
const a = values;
|
|
390
|
+
for (let i = 0; i < n; i++) {
|
|
391
|
+
let v = a[i];
|
|
392
|
+
while (v > 127) {
|
|
393
|
+
out[pos++] = v & 127 | 128;
|
|
394
|
+
v >>>= 7;
|
|
395
|
+
}
|
|
396
|
+
out[pos++] = v;
|
|
397
|
+
}
|
|
398
|
+
return pos;
|
|
399
|
+
}
|
|
400
|
+
for (let i = 0; i < n; i++) {
|
|
319
401
|
const v = values[i];
|
|
320
402
|
if (typeof v === "number" && v >= 0 && v <= Number.MAX_SAFE_INTEGER && Number.isInteger(v)) {
|
|
321
403
|
pos = encodeVarintNum(v, out, pos);
|
|
322
|
-
|
|
323
|
-
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const b = toBigInt(v);
|
|
407
|
+
if (!splitU64(b)) throw argumentError(`unsigned value ${b} out of range`);
|
|
408
|
+
let lo = S_U32[LO];
|
|
409
|
+
const hi = S_U32[HI];
|
|
410
|
+
if (hi === 0) {
|
|
411
|
+
while (lo > 127) {
|
|
412
|
+
out[pos++] = lo & 127 | 128;
|
|
413
|
+
lo >>>= 7;
|
|
414
|
+
}
|
|
415
|
+
out[pos++] = lo;
|
|
416
|
+
continue;
|
|
324
417
|
}
|
|
418
|
+
out[pos++] = lo & 127 | 128;
|
|
419
|
+
out[pos++] = lo >>> 7 & 127 | 128;
|
|
420
|
+
out[pos++] = lo >>> 14 & 127 | 128;
|
|
421
|
+
out[pos++] = lo >>> 21 & 127 | 128;
|
|
422
|
+
const b4 = lo >>> 28 | (hi & 7) << 4;
|
|
423
|
+
let h = hi >>> 3;
|
|
424
|
+
if (h === 0) {
|
|
425
|
+
out[pos++] = b4;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
out[pos++] = b4 | 128;
|
|
429
|
+
while (h > 127) {
|
|
430
|
+
out[pos++] = h & 127 | 128;
|
|
431
|
+
h >>>= 7;
|
|
432
|
+
}
|
|
433
|
+
out[pos++] = h;
|
|
325
434
|
}
|
|
326
435
|
return pos;
|
|
327
436
|
},
|
|
328
437
|
encodeSignedVarints(values, out, pos) {
|
|
438
|
+
const ctorS = values.constructor;
|
|
439
|
+
if (ctorS === BigInt64Array) {
|
|
440
|
+
const a = values;
|
|
441
|
+
const h = new Uint32Array(a.buffer, a.byteOffset, a.length * 2);
|
|
442
|
+
for (let i = 0; i < a.length; i++) {
|
|
443
|
+
pos = encodeZigzagVarintLoHi(h[i * 2 + LO], h[i * 2 + HI], out, pos);
|
|
444
|
+
}
|
|
445
|
+
return pos;
|
|
446
|
+
}
|
|
447
|
+
if (ctorS === Int8Array || ctorS === Int16Array || ctorS === Int32Array) {
|
|
448
|
+
const a = values;
|
|
449
|
+
const n = a.length;
|
|
450
|
+
for (let i = 0; i < n; i++) {
|
|
451
|
+
const x = a[i];
|
|
452
|
+
let z = (x << 1 ^ x >> 31) >>> 0;
|
|
453
|
+
while (z > 127) {
|
|
454
|
+
out[pos++] = z & 127 | 128;
|
|
455
|
+
z >>>= 7;
|
|
456
|
+
}
|
|
457
|
+
out[pos++] = z;
|
|
458
|
+
}
|
|
459
|
+
return pos;
|
|
460
|
+
}
|
|
329
461
|
for (let i = 0; i < values.length; i++) {
|
|
330
462
|
const v = values[i];
|
|
331
463
|
if (typeof v === "number" && v >= -SIGNED_FAST_MAX && v <= SIGNED_FAST_MAX && Number.isInteger(v)) {
|
|
332
464
|
pos = encodeVarintNum(v >= 0 ? v * 2 : -v * 2 - 1, out, pos);
|
|
333
465
|
} else {
|
|
334
|
-
|
|
466
|
+
const b = toBigInt(v);
|
|
467
|
+
if (!splitI64(b)) throw argumentError(`signed value ${b} out of range`);
|
|
468
|
+
pos = encodeZigzagVarintLoHi(S_U32[LO], S_U32[HI], out, pos);
|
|
335
469
|
}
|
|
336
470
|
}
|
|
337
471
|
return pos;
|
|
338
472
|
},
|
|
473
|
+
// Both float packers take one `DataView` over the destination *once the run is
|
|
474
|
+
// long enough to pay for it* — the language-forced handle of CORELIB_PLAN §6.6.2,
|
|
475
|
+
// and the only way to place an IEEE-754 value at a byte offset. It carries no
|
|
476
|
+
// message bytes (the storage is the caller's) and no wire number sizes it.
|
|
477
|
+
//
|
|
478
|
+
// The threshold is arithmetic, not taste. Measured on Node 24:
|
|
479
|
+
// `new DataView(buf.buffer, off, len)` costs **129 ns**, while the handle saves
|
|
480
|
+
// 1.9 ns per `fp32` (4.11 -> 2.23) and 7.3 ns per `fp64` (10.48 -> 3.20). So it
|
|
481
|
+
// breaks even at ~68 `fp32` and ~18 `fp64` elements; below that the scratch route
|
|
482
|
+
// wins, and a two-element array through a handle is a pessimisation.
|
|
339
483
|
packFp32Array(values, out, pos) {
|
|
340
|
-
|
|
341
|
-
|
|
484
|
+
const n = values.length;
|
|
485
|
+
if (values instanceof Float32Array) {
|
|
486
|
+
if (n < FP32_HANDLE_MIN) {
|
|
487
|
+
let w2 = null;
|
|
488
|
+
for (let i = 0; i < n; i++) {
|
|
489
|
+
const v = values[i];
|
|
490
|
+
if (v === v) {
|
|
491
|
+
pos = packFp32(out, pos, v);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (w2 === null) w2 = new Uint32Array(values.buffer, values.byteOffset, n);
|
|
495
|
+
const x = w2[i];
|
|
496
|
+
out[pos] = x;
|
|
497
|
+
out[pos + 1] = x >>> 8;
|
|
498
|
+
out[pos + 2] = x >>> 16;
|
|
499
|
+
out[pos + 3] = x >>> 24;
|
|
500
|
+
pos += 4;
|
|
501
|
+
}
|
|
502
|
+
return pos;
|
|
503
|
+
}
|
|
504
|
+
const w = new Uint32Array(values.buffer, values.byteOffset, n);
|
|
505
|
+
const dv2 = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
506
|
+
for (let i = 0; i < n; i++) {
|
|
507
|
+
dv2.setUint32(pos, w[i], true);
|
|
508
|
+
pos += 4;
|
|
509
|
+
}
|
|
510
|
+
return pos;
|
|
511
|
+
}
|
|
512
|
+
if (n < FP32_HANDLE_MIN) {
|
|
513
|
+
for (let i = 0; i < n; i++) pos = packFp32(out, pos, values[i]);
|
|
514
|
+
return pos;
|
|
515
|
+
}
|
|
516
|
+
const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
517
|
+
for (let i = 0; i < n; i++) {
|
|
518
|
+
dv.setFloat32(pos, values[i], true);
|
|
519
|
+
pos += 4;
|
|
342
520
|
}
|
|
343
521
|
return pos;
|
|
344
522
|
},
|
|
345
523
|
packFp64Array(values, out, pos) {
|
|
346
|
-
|
|
347
|
-
|
|
524
|
+
const n = values.length;
|
|
525
|
+
if (n < FP64_HANDLE_MIN) {
|
|
526
|
+
for (let i = 0; i < n; i++) pos = packFp64(out, pos, values[i]);
|
|
527
|
+
return pos;
|
|
528
|
+
}
|
|
529
|
+
const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
530
|
+
for (let i = 0; i < n; i++) {
|
|
531
|
+
dv.setFloat64(pos, values[i], true);
|
|
532
|
+
pos += 8;
|
|
348
533
|
}
|
|
349
534
|
return pos;
|
|
350
535
|
}
|
|
@@ -374,19 +559,18 @@ var SofaBuffers = (function (exports) {
|
|
|
374
559
|
}
|
|
375
560
|
|
|
376
561
|
// src/encode/fixlen.ts
|
|
377
|
-
var UTF8 = new TextEncoder();
|
|
378
562
|
function unpairedSurrogate(index) {
|
|
379
563
|
return argumentError(
|
|
380
564
|
`unpaired surrogate at index ${index}: string value is not valid UTF-8`
|
|
381
565
|
);
|
|
382
566
|
}
|
|
383
|
-
function encodeUtf8(text) {
|
|
384
|
-
utf8Length(text);
|
|
385
|
-
return UTF8.encode(text);
|
|
386
|
-
}
|
|
387
567
|
function utf8Length(text) {
|
|
388
|
-
|
|
389
|
-
|
|
568
|
+
const n = text.length;
|
|
569
|
+
let a = 0;
|
|
570
|
+
while (a < n && text.charCodeAt(a) < 128) a++;
|
|
571
|
+
if (a === n) return n;
|
|
572
|
+
let len = a;
|
|
573
|
+
for (let i = a; i < n; i++) {
|
|
390
574
|
const c = text.charCodeAt(i);
|
|
391
575
|
if (c < 128) {
|
|
392
576
|
len += 1;
|
|
@@ -409,7 +593,17 @@ var SofaBuffers = (function (exports) {
|
|
|
409
593
|
return len;
|
|
410
594
|
}
|
|
411
595
|
function utf8Write(text, out, pos) {
|
|
412
|
-
|
|
596
|
+
const n = text.length;
|
|
597
|
+
let a = 0;
|
|
598
|
+
while (a < n) {
|
|
599
|
+
const c = text.charCodeAt(a);
|
|
600
|
+
if (c >= 128) break;
|
|
601
|
+
out[pos + a] = c;
|
|
602
|
+
a++;
|
|
603
|
+
}
|
|
604
|
+
pos += a;
|
|
605
|
+
if (a === n) return pos;
|
|
606
|
+
for (let i = a; i < n; i++) {
|
|
413
607
|
let c = text.charCodeAt(i);
|
|
414
608
|
if (c < 128) {
|
|
415
609
|
out[pos++] = c;
|
|
@@ -438,68 +632,139 @@ var SofaBuffers = (function (exports) {
|
|
|
438
632
|
}
|
|
439
633
|
return pos;
|
|
440
634
|
}
|
|
635
|
+
function utf8WriteSink(text, sink) {
|
|
636
|
+
const n = text.length;
|
|
637
|
+
for (let i = 0; i < n; i++) {
|
|
638
|
+
let c = text.charCodeAt(i);
|
|
639
|
+
if (c < 128) {
|
|
640
|
+
sink.putByte(c);
|
|
641
|
+
} else if (c < 2048) {
|
|
642
|
+
sink.putByte(192 | c >> 6);
|
|
643
|
+
sink.putByte(128 | c & 63);
|
|
644
|
+
} else if (c >= 55296 && c <= 56319) {
|
|
645
|
+
const c2 = i + 1 < n ? text.charCodeAt(i + 1) : 0;
|
|
646
|
+
if (c2 >= 56320 && c2 <= 57343) {
|
|
647
|
+
i++;
|
|
648
|
+
c = 65536 + (c - 55296 << 10) + (c2 - 56320);
|
|
649
|
+
sink.putByte(240 | c >> 18);
|
|
650
|
+
sink.putByte(128 | c >> 12 & 63);
|
|
651
|
+
sink.putByte(128 | c >> 6 & 63);
|
|
652
|
+
sink.putByte(128 | c & 63);
|
|
653
|
+
} else {
|
|
654
|
+
throw unpairedSurrogate(i);
|
|
655
|
+
}
|
|
656
|
+
} else if (c >= 56320 && c <= 57343) {
|
|
657
|
+
throw unpairedSurrogate(i);
|
|
658
|
+
} else {
|
|
659
|
+
sink.putByte(224 | c >> 12);
|
|
660
|
+
sink.putByte(128 | c >> 6 & 63);
|
|
661
|
+
sink.putByte(128 | c & 63);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
441
665
|
|
|
442
666
|
// src/encode/ostream.ts
|
|
443
|
-
var DEFAULT_CAPACITY = 256;
|
|
444
667
|
var SIGNED_FAST_MAX2 = 4503599627370496;
|
|
668
|
+
function checkHandover(buffer, offset, streaming, carried) {
|
|
669
|
+
if (offset < 0 || offset > buffer.length) {
|
|
670
|
+
throw argumentError(`offset ${offset} out of range`);
|
|
671
|
+
}
|
|
672
|
+
if (!Number.isInteger(carried) || carried < 0 || carried > offset) {
|
|
673
|
+
throw argumentError(`carried ${carried} out of range 0..${offset}`);
|
|
674
|
+
}
|
|
675
|
+
if (streaming && buffer.length - offset < MIN_OUTPUT_BUFFER) {
|
|
676
|
+
throw argumentError(
|
|
677
|
+
`output buffer with a flush sink has ${buffer.length - offset} usable byte(s), below MIN_OUTPUT_BUFFER (${MIN_OUTPUT_BUFFER})`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
445
681
|
var OStream = class {
|
|
682
|
+
/**
|
|
683
|
+
* Encoder over a caller buffer, optionally draining to `flush` as it fills.
|
|
684
|
+
*
|
|
685
|
+
* Those are the only two buffer models there are (CORELIB_PLAN §5.1.1): the
|
|
686
|
+
* buffer the caller hands over, and whatever a sink installs in its place
|
|
687
|
+
* (§5.1.5). There is no third parameter through which the encoder could be
|
|
688
|
+
* given something to enlarge — growing a destination from a write path is what
|
|
689
|
+
* §6.6's second violation row names, whoever owns the allocator.
|
|
690
|
+
*
|
|
691
|
+
* Constructing one is the only allocating step (§6.6): it sizes the hold-back
|
|
692
|
+
* run from `MAX_DEPTH` and reads the active kernel once. No `write*` call after
|
|
693
|
+
* that allocates anything.
|
|
694
|
+
*/
|
|
446
695
|
constructor(buffer, offset = 0, flush) {
|
|
447
696
|
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
697
|
/** Valid entries in {@link OStream.pending}. */
|
|
467
698
|
this.nPending = 0;
|
|
699
|
+
/**
|
|
700
|
+
* How many buffer installations {@link OStream.setBuffer} has made. Only ever
|
|
701
|
+
* compared for equality across the flush callback, which is how
|
|
702
|
+
* {@link OStream.flush} tells "the sink copied and returned" from "the sink
|
|
703
|
+
* took the buffer and installed a replacement" — the one distinction §5.1
|
|
704
|
+
* rests the handover contract on, and the one that decides whether the start
|
|
705
|
+
* offset was consumed or re-armed.
|
|
706
|
+
*/
|
|
707
|
+
this.installs = 0;
|
|
468
708
|
this.kernel = getKernel();
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (offset < 0 || offset > buffer.length) {
|
|
477
|
-
throw argumentError(`offset ${offset} out of range`);
|
|
478
|
-
}
|
|
479
|
-
this.buf = buffer;
|
|
480
|
-
this.start = offset;
|
|
481
|
-
this.pos = offset;
|
|
482
|
-
this.flushSink = flush;
|
|
483
|
-
this.canGrow = false;
|
|
484
|
-
}
|
|
709
|
+
this.pending = new Array(MAX_DEPTH);
|
|
710
|
+
checkHandover(buffer, offset, flush !== void 0, 0);
|
|
711
|
+
this.buf = buffer;
|
|
712
|
+
this.start = offset;
|
|
713
|
+
this.origin = offset;
|
|
714
|
+
this.pos = offset;
|
|
715
|
+
this.flushSink = flush;
|
|
485
716
|
}
|
|
486
|
-
/** Bytes currently held in the buffer (
|
|
717
|
+
/** Bytes of the message currently held in the buffer (see {@link bytes}). */
|
|
487
718
|
get bytesUsed() {
|
|
488
|
-
return this.pos - this.
|
|
719
|
+
return this.pos - this.origin;
|
|
489
720
|
}
|
|
490
721
|
/**
|
|
491
|
-
* The encoded message so far, as a view into the working buffer
|
|
492
|
-
*
|
|
493
|
-
*
|
|
722
|
+
* The encoded message so far, as a view into the working buffer: everything
|
|
723
|
+
* written since construction, the last {@link reset}, or the last flush the
|
|
724
|
+
* sink returned from without installing a buffer. With a flush sink that is
|
|
725
|
+
* normally only the not-yet-flushed tail; on a stream whose sink hands back a
|
|
726
|
+
* replacement carrying the earlier bytes — the accumulator `growingOStream`
|
|
727
|
+
* builds — it is the whole message. The view is valid until the next write.
|
|
494
728
|
*/
|
|
495
729
|
bytes() {
|
|
496
|
-
return this.buf.subarray(this.
|
|
730
|
+
return this.buf.subarray(this.origin, this.pos);
|
|
497
731
|
}
|
|
498
|
-
/**
|
|
732
|
+
/**
|
|
733
|
+
* Drain buffered bytes to the flush sink (no-op without one).
|
|
734
|
+
*
|
|
735
|
+
* A sink that returns **without** installing a buffer has copied what it was
|
|
736
|
+
* handed, so the encoder keeps writing into the same buffer — resuming at
|
|
737
|
+
* offset **0**. The start offset belongs to the *installation*, not to the
|
|
738
|
+
* buffer (CORELIB_PLAN §5.1): the buffer-set that armed it — the constructor
|
|
739
|
+
* or {@link OStream.setBuffer} — reserved room in the unit it began, and
|
|
740
|
+
* handing that unit over consumes the reservation. A sink that wants header
|
|
741
|
+
* room in *every* unit re-arms it by calling `setBuffer(buf, offset)` from
|
|
742
|
+
* inside the callback, a new installation like any other; a bare return must
|
|
743
|
+
* not do it implicitly, or the leading bytes would be capacity the rest of the
|
|
744
|
+
* stream could never use and the two shapes would be indistinguishable.
|
|
745
|
+
*/
|
|
499
746
|
flush() {
|
|
747
|
+
this.drain(0);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* {@link flush}, plus the number of contiguous bytes the caller wants at the
|
|
751
|
+
* cursor afterwards — `0` when it wants none in particular.
|
|
752
|
+
*
|
|
753
|
+
* Split out from the public `flush` so the figure cannot be invented by a
|
|
754
|
+
* caller: it is the encoder's own reserve request, and a sink that sizes a
|
|
755
|
+
* replacement from it (`growingOStream`) has to be able to trust it. Everything
|
|
756
|
+
* else is identical, including that a sink which returns without installing has
|
|
757
|
+
* *copied* and the cursor resumes at `0`.
|
|
758
|
+
*/
|
|
759
|
+
drain(needed) {
|
|
500
760
|
if (this.flushSink && this.pos > this.start) {
|
|
501
|
-
|
|
502
|
-
this.
|
|
761
|
+
const installed = this.installs;
|
|
762
|
+
this.flushSink(this.buf, this.start, this.pos, needed);
|
|
763
|
+
if (this.installs === installed) {
|
|
764
|
+
this.start = 0;
|
|
765
|
+
this.origin = 0;
|
|
766
|
+
this.pos = 0;
|
|
767
|
+
}
|
|
503
768
|
}
|
|
504
769
|
}
|
|
505
770
|
/**
|
|
@@ -509,14 +774,42 @@ var SofaBuffers = (function (exports) {
|
|
|
509
774
|
* without interruption. `offset` reserves space at the front of the new
|
|
510
775
|
* buffer. Any not-yet-flushed bytes in the old buffer are dropped, so
|
|
511
776
|
* {@link flush} first (the flush callback fires before you swap).
|
|
777
|
+
*
|
|
778
|
+
* Every call is a **new installation**, and its `offset` applies to the unit
|
|
779
|
+
* it begins and is consumed when that unit is flushed (CORELIB_PLAN §5.1).
|
|
780
|
+
* Passing the buffer the encoder already has is an installation like any
|
|
781
|
+
* other: that is how a sink gets header room in *every* flushed unit — one
|
|
782
|
+
* framing header per packet — where returning bare would resume at `0`.
|
|
783
|
+
*
|
|
784
|
+
* On a stream that has a flush sink the new buffer must leave at least
|
|
785
|
+
* {@link MIN_OUTPUT_BUFFER} usable bytes (`buffer.length - offset`); a smaller
|
|
786
|
+
* one is rejected here, with {@link SofabErrorCode.Argument}, leaving the
|
|
787
|
+
* encoder on the buffer it already had. A sink-less stream has no minimum.
|
|
788
|
+
*
|
|
789
|
+
* `carried` says how many bytes **immediately before `offset`** the
|
|
790
|
+
* replacement already holds of *this* message — normally `0`, because a fresh
|
|
791
|
+
* buffer holds none. A caller that keeps the whole message in one growing
|
|
792
|
+
* store passes the length it copied across, so {@link bytes} keeps reporting
|
|
793
|
+
* the message rather than only the piece written after the swap. It changes
|
|
794
|
+
* nothing on the wire and nothing about flushing: the next flush still begins
|
|
795
|
+
* at `offset`. `growingOStream` is the caller this exists for; it is the
|
|
796
|
+
* §5.1.5 handover, not a growth hook inside the encoder (§6.6).
|
|
797
|
+
*
|
|
798
|
+
* The accumulating stream `growingOStream` builds is an ordinary streaming
|
|
799
|
+
* stream, so this works on it too, and means exactly what it means anywhere
|
|
800
|
+
* else: the not-yet-flushed bytes in the old buffer are dropped, encoding
|
|
801
|
+
* continues into yours, and its sink takes over growing *that* one from the
|
|
802
|
+
* next flush — reserve offset included, since what it copies out is the
|
|
803
|
+
* message rather than the buffer. Encode into a plain
|
|
804
|
+
* `new OStream(buffer, offset, flush?)` to keep the buffer yours instead.
|
|
512
805
|
*/
|
|
513
|
-
setBuffer(buffer, offset = 0) {
|
|
514
|
-
|
|
515
|
-
throw argumentError(`offset ${offset} out of range`);
|
|
516
|
-
}
|
|
806
|
+
setBuffer(buffer, offset = 0, carried = 0) {
|
|
807
|
+
checkHandover(buffer, offset, this.flushSink !== void 0, carried);
|
|
517
808
|
this.buf = buffer;
|
|
518
809
|
this.start = offset;
|
|
810
|
+
this.origin = offset - carried;
|
|
519
811
|
this.pos = offset;
|
|
812
|
+
this.installs++;
|
|
520
813
|
}
|
|
521
814
|
/**
|
|
522
815
|
* Rewind the encoder to empty, reusing the existing buffer. Lets a caller pool
|
|
@@ -524,7 +817,8 @@ var SofaBuffers = (function (exports) {
|
|
|
524
817
|
* encode. Any view previously returned by {@link bytes} is invalidated.
|
|
525
818
|
*/
|
|
526
819
|
reset() {
|
|
527
|
-
this.pos = this.
|
|
820
|
+
this.pos = this.origin;
|
|
821
|
+
this.start = this.origin;
|
|
528
822
|
this.depth = 0;
|
|
529
823
|
this.nPending = 0;
|
|
530
824
|
}
|
|
@@ -537,9 +831,11 @@ var SofaBuffers = (function (exports) {
|
|
|
537
831
|
return;
|
|
538
832
|
}
|
|
539
833
|
const v = toBigInt(value);
|
|
540
|
-
if (!
|
|
834
|
+
if (!splitU64(v)) throw argumentError(`unsigned value ${v} out of 64-bit range`);
|
|
835
|
+
const lo = S_U32[LO];
|
|
836
|
+
const hi = S_U32[HI];
|
|
541
837
|
this.header(id, WireType.Unsigned);
|
|
542
|
-
this.
|
|
838
|
+
this.putVarintLoHi(lo, hi);
|
|
543
839
|
}
|
|
544
840
|
/** Write a signed integer field (zig-zag encoded). */
|
|
545
841
|
writeSigned(id, value) {
|
|
@@ -549,9 +845,44 @@ var SofaBuffers = (function (exports) {
|
|
|
549
845
|
return;
|
|
550
846
|
}
|
|
551
847
|
const v = toBigInt(value);
|
|
552
|
-
if (!
|
|
848
|
+
if (!splitI64(v)) throw argumentError(`signed value ${v} out of 64-bit range`);
|
|
849
|
+
const lo = S_U32[LO];
|
|
850
|
+
const hi = S_U32[HI];
|
|
851
|
+
const sgn = -(hi >>> 31) >>> 0;
|
|
852
|
+
const zLo = (lo << 1 >>> 0 ^ sgn) >>> 0;
|
|
853
|
+
const zHi = ((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0;
|
|
854
|
+
this.header(id, WireType.Signed);
|
|
855
|
+
this.putVarintLoHi(zLo, zHi);
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Write an unsigned 64-bit scalar from a {@link Long} — the `bigint`-free twin
|
|
859
|
+
* of {@link writeUnsigned}, and the scalar counterpart of
|
|
860
|
+
* {@link writeUnsignedArrayLong}. Produces the identical wire.
|
|
861
|
+
*
|
|
862
|
+
* There is no range check and no scratch round-trip: a `Long` *is* two 32-bit
|
|
863
|
+
* halves, so it is in the `uint64` domain by construction — which is the whole
|
|
864
|
+
* of what `splitU64` decides for a `number | bigint`. The halves go straight
|
|
865
|
+
* into the varint writer, so nothing is allocated per value. Nothing needs
|
|
866
|
+
* copying out ahead of `header` either, for the same reason the array writers
|
|
867
|
+
* do not: the halves come off a caller-owned immutable `Long`, not the shared
|
|
868
|
+
* scratch a re-entrant flush sink could overwrite.
|
|
869
|
+
*/
|
|
870
|
+
writeUnsignedLong(id, value) {
|
|
871
|
+
this.header(id, WireType.Unsigned);
|
|
872
|
+
this.putVarintLoHi(value.low, value.high);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Write a signed 64-bit scalar (zig-zag) from a {@link Long} — the
|
|
876
|
+
* `bigint`-free twin of {@link writeSigned}, and the scalar counterpart of
|
|
877
|
+
* {@link writeSignedArrayLong}. Zig-zag `(n << 1) ^ (n >> 63)` is computed on
|
|
878
|
+
* the lo/hi pair, so the varint goes out at its exact size (a fixed caller
|
|
879
|
+
* buffer must not see a 10-byte demand for a 2-byte field) and no `bigint` is
|
|
880
|
+
* created. A `Long` carries exactly 64 bits, so as in {@link writeUnsignedLong}
|
|
881
|
+
* there is nothing left to range-check.
|
|
882
|
+
*/
|
|
883
|
+
writeSignedLong(id, value) {
|
|
553
884
|
this.header(id, WireType.Signed);
|
|
554
|
-
this.
|
|
885
|
+
this.putZigzagVarintLoHi(value.low, value.high);
|
|
555
886
|
}
|
|
556
887
|
/** Write a boolean field (encoded as the unsigned value 0 or 1). */
|
|
557
888
|
writeBoolean(id, value) {
|
|
@@ -561,69 +892,161 @@ var SofaBuffers = (function (exports) {
|
|
|
561
892
|
/** Write an IEEE-754 32-bit float field. */
|
|
562
893
|
writeFp32(id, value) {
|
|
563
894
|
this.fixlenHead(id, 4, FixlenSubtype.Fp32);
|
|
564
|
-
this.
|
|
565
|
-
|
|
895
|
+
this.putFp32(value);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Write an fp32 field from its raw wire bits — the 4 little-endian payload
|
|
899
|
+
* bytes as one 32-bit word, which is exactly what {@link Visitor.fp32} delivers
|
|
900
|
+
* as `bits`.
|
|
901
|
+
*
|
|
902
|
+
* This is the re-encode half of the bit-exactness rule (CORELIB_PLAN §6.5). A JS
|
|
903
|
+
* `number` is a 64-bit double, and widening an fp32 **signaling** NaN into one
|
|
904
|
+
* quiets it, so re-encoding through {@link writeFp32} cannot reproduce such a
|
|
905
|
+
* payload; the bits go out verbatim here, so decode → re-encode is byte-for-byte
|
|
906
|
+
* for every fp32 value, sNaN included. §6.5 requires this path of every
|
|
907
|
+
* double-only target, and names it: "a 32-bit bits accessor".
|
|
908
|
+
*/
|
|
909
|
+
writeFp32Bits(id, bits) {
|
|
910
|
+
this.fixlenHead(id, 4, FixlenSubtype.Fp32);
|
|
911
|
+
this.putFp32Bits(bits >>> 0);
|
|
566
912
|
}
|
|
567
913
|
/** Write an IEEE-754 64-bit double field. */
|
|
568
914
|
writeFp64(id, value) {
|
|
569
915
|
this.fixlenHead(id, 8, FixlenSubtype.Fp64);
|
|
570
|
-
this.
|
|
571
|
-
this.pos = packFp64(this.buf, this.pos, value);
|
|
916
|
+
this.putFp64(value);
|
|
572
917
|
}
|
|
573
918
|
/** Write a UTF-8 string field. */
|
|
574
919
|
writeString(id, text) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
920
|
+
const n = text.length;
|
|
921
|
+
let a = 0;
|
|
922
|
+
while (a < n && text.charCodeAt(a) < 128) a++;
|
|
923
|
+
if (a === n) {
|
|
924
|
+
if (n > FIXLEN_MAX) {
|
|
925
|
+
throw argumentError(`fixlen length ${n} exceeds ${FIXLEN_MAX}`);
|
|
926
|
+
}
|
|
927
|
+
this.fixlenHead(id, n, FixlenSubtype.String);
|
|
928
|
+
if (this.reserveBulk(n)) {
|
|
929
|
+
const buf = this.buf;
|
|
930
|
+
const p = this.pos;
|
|
931
|
+
for (let k = 0; k < n; k++) buf[p + k] = text.charCodeAt(k);
|
|
932
|
+
this.pos = p + n;
|
|
933
|
+
return;
|
|
579
934
|
}
|
|
580
|
-
|
|
581
|
-
|
|
935
|
+
utf8WriteSink(text, this);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const byteLen = utf8Length(text);
|
|
939
|
+
if (byteLen > FIXLEN_MAX) {
|
|
940
|
+
throw argumentError(`fixlen length ${byteLen} exceeds ${FIXLEN_MAX}`);
|
|
941
|
+
}
|
|
942
|
+
this.fixlenHead(id, byteLen, FixlenSubtype.String);
|
|
943
|
+
if (this.reserveBulk(byteLen)) {
|
|
582
944
|
this.pos = utf8Write(text, this.buf, this.pos);
|
|
583
945
|
return;
|
|
584
946
|
}
|
|
585
|
-
|
|
947
|
+
utf8WriteSink(text, this);
|
|
586
948
|
}
|
|
587
949
|
/** Write a blob (arbitrary bytes) field. */
|
|
588
950
|
writeBlob(id, data) {
|
|
589
951
|
this.writeFixlen(id, data, FixlenSubtype.Blob);
|
|
590
952
|
}
|
|
591
|
-
/**
|
|
953
|
+
/**
|
|
954
|
+
* Write a fixed-length field of the given subtype from raw bytes.
|
|
955
|
+
*
|
|
956
|
+
* This is the byte-level entry point — the one writer that takes the subtype
|
|
957
|
+
* from the caller rather than picking it — so the payload is checked
|
|
958
|
+
* **against that subtype** before a byte is written, and it cannot emit a
|
|
959
|
+
* `fixlen_word` a conformant decoder must reject (`ARGUMENT`, §6.3):
|
|
960
|
+
*
|
|
961
|
+
* * subtypes `0x4`–`0x7` are **reserved** — a decoder must treat a field
|
|
962
|
+
* carrying one as malformed (`INVALID`, §4.6/§5.2);
|
|
963
|
+
* * `Fp32` / `Fp64` payloads are **exactly** 4 / 8 bytes — any other declared
|
|
964
|
+
* length for those subtypes is malformed, rejected the moment the word is
|
|
965
|
+
* read (§4.6);
|
|
966
|
+
* * `String` / `Blob` take any length up to `FIXLEN_MAX`.
|
|
967
|
+
*
|
|
968
|
+
* The typed writers ({@link writeFp32}, {@link writeFp64},
|
|
969
|
+
* {@link writeString}) are correct by construction and go straight to the
|
|
970
|
+
* header; only {@link writeBlob}, whose subtype is unconstrained anyway,
|
|
971
|
+
* shares this path.
|
|
972
|
+
*/
|
|
592
973
|
writeFixlen(id, data, subtype) {
|
|
593
|
-
if (
|
|
974
|
+
if (subtype >>> 0 > FixlenSubtype.Blob || !Number.isInteger(subtype)) {
|
|
975
|
+
throw argumentError(`fixlen subtype ${subtype} is reserved (\xA74.6: 0x0..0x3)`);
|
|
976
|
+
}
|
|
977
|
+
if (subtype === FixlenSubtype.Fp32 || subtype === FixlenSubtype.Fp64) {
|
|
978
|
+
const want = subtype === FixlenSubtype.Fp32 ? 4 : 8;
|
|
979
|
+
if (data.length !== want) {
|
|
980
|
+
throw argumentError(
|
|
981
|
+
`fixlen ${subtype === FixlenSubtype.Fp32 ? "fp32" : "fp64"} payload must be exactly ${want} bytes, got ${data.length}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
} else if (data.length > FIXLEN_MAX) {
|
|
594
985
|
throw argumentError(`fixlen length ${data.length} exceeds ${FIXLEN_MAX}`);
|
|
595
986
|
}
|
|
596
987
|
this.fixlenHead(id, data.length, subtype);
|
|
597
988
|
this.writeRaw(data);
|
|
598
989
|
}
|
|
599
990
|
// --- arrays -------------------------------------------------------------
|
|
600
|
-
/**
|
|
991
|
+
/**
|
|
992
|
+
* Write an array of unsigned integers (each a varint).
|
|
993
|
+
*
|
|
994
|
+
* **The bulk kernel writes a whole array in one pass and cannot flush**, so it
|
|
995
|
+
* runs only where everything is known to fit. Three cases, and none of them
|
|
996
|
+
* asks the source how wide its elements are:
|
|
997
|
+
*
|
|
998
|
+
* * **block mode** (no sink) — the buffer is meant to hold the whole message,
|
|
999
|
+
* so the kernel always runs and the buffer's length is the bound, checked
|
|
1000
|
+
* after the fact. A message that does not fit is `BUFFER_FULL`, which is
|
|
1001
|
+
* precisely this mode's answer.
|
|
1002
|
+
* * **a stream with room** — `VARINT_MAX_BYTES` per element is the true
|
|
1003
|
+
* worst case for any 64-bit value, and a *growing* stream always satisfies
|
|
1004
|
+
* it because it grows to whatever is asked.
|
|
1005
|
+
* * **a chunk too small for that** — fill it to the last byte, hand it over,
|
|
1006
|
+
* carry on, splitting an element where it falls. That is not a fallback but
|
|
1007
|
+
* the mode's contract: `MIN_OUTPUT_BUFFER` is 1.
|
|
1008
|
+
*
|
|
1009
|
+
* What is gone is the fourth case, which asked `values.constructor` for a
|
|
1010
|
+
* narrower bound so the kernel would run on a tightly-sized chunk. `constructor`
|
|
1011
|
+
* is an ordinary property, an `ArrayLike` can claim any width, and a wrong
|
|
1012
|
+
* answer silently truncated the message — §5.1's "partial output handed back as
|
|
1013
|
+
* complete". The block mode now reaches the kernel without needing the number
|
|
1014
|
+
* at all, which is where a tightly-sized buffer actually lives.
|
|
1015
|
+
*/
|
|
601
1016
|
writeUnsignedArray(id, values) {
|
|
602
1017
|
this.arrayHead(id, WireType.ArrayUnsigned, values.length);
|
|
603
|
-
if (this.
|
|
604
|
-
this.
|
|
1018
|
+
if (this.flushSink === void 0) {
|
|
1019
|
+
this.pos = this.bulkEnd(
|
|
1020
|
+
this.kernel.encodeUnsignedVarints(values, this.buf, this.pos),
|
|
1021
|
+
values.length
|
|
1022
|
+
);
|
|
1023
|
+
} else if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
|
|
605
1024
|
this.pos = this.kernel.encodeUnsignedVarints(values, this.buf, this.pos);
|
|
606
1025
|
} else {
|
|
607
1026
|
for (let i = 0; i < values.length; i++) {
|
|
608
1027
|
const v = toBigInt(values[i]);
|
|
609
|
-
if (!
|
|
610
|
-
|
|
611
|
-
|
|
1028
|
+
if (!splitU64(v)) throw argumentError(`unsigned value ${v} out of range`);
|
|
1029
|
+
const lo = S_U32[LO];
|
|
1030
|
+
const hi = S_U32[HI];
|
|
1031
|
+
this.putVarintLoHi(lo, hi);
|
|
612
1032
|
}
|
|
613
1033
|
}
|
|
614
1034
|
}
|
|
615
|
-
/** Write an array of signed integers (each zig-zag + varint). */
|
|
1035
|
+
/** Write an array of signed integers (each zig-zag + varint). See {@link writeUnsignedArray}. */
|
|
616
1036
|
writeSignedArray(id, values) {
|
|
617
1037
|
this.arrayHead(id, WireType.ArraySigned, values.length);
|
|
618
|
-
if (this.
|
|
619
|
-
this.
|
|
1038
|
+
if (this.flushSink === void 0) {
|
|
1039
|
+
this.pos = this.bulkEnd(
|
|
1040
|
+
this.kernel.encodeSignedVarints(values, this.buf, this.pos),
|
|
1041
|
+
values.length
|
|
1042
|
+
);
|
|
1043
|
+
} else if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
|
|
620
1044
|
this.pos = this.kernel.encodeSignedVarints(values, this.buf, this.pos);
|
|
621
1045
|
} else {
|
|
622
1046
|
for (let i = 0; i < values.length; i++) {
|
|
623
1047
|
const v = toBigInt(values[i]);
|
|
624
|
-
if (!
|
|
625
|
-
this.
|
|
626
|
-
this.pos = encodeVarint(zigzagEncode(v), this.buf, this.pos);
|
|
1048
|
+
if (!splitI64(v)) throw argumentError(`signed value ${v} out of range`);
|
|
1049
|
+
this.putZigzagVarintLoHi(S_U32[LO], S_U32[HI]);
|
|
627
1050
|
}
|
|
628
1051
|
}
|
|
629
1052
|
}
|
|
@@ -634,14 +1057,20 @@ var SofaBuffers = (function (exports) {
|
|
|
634
1057
|
*/
|
|
635
1058
|
writeUnsignedArrayLong(id, values) {
|
|
636
1059
|
this.arrayHead(id, WireType.ArrayUnsigned, values.length);
|
|
637
|
-
this.
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1060
|
+
if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
|
|
1061
|
+
let pos = this.pos;
|
|
1062
|
+
const buf = this.buf;
|
|
1063
|
+
for (let i = 0; i < values.length; i++) {
|
|
1064
|
+
const v = values[i];
|
|
1065
|
+
pos = encodeVarintLoHi(v.low, v.high, buf, pos);
|
|
1066
|
+
}
|
|
1067
|
+
this.pos = pos;
|
|
1068
|
+
} else {
|
|
1069
|
+
for (let i = 0; i < values.length; i++) {
|
|
1070
|
+
const v = values[i];
|
|
1071
|
+
this.putVarintLoHi(v.low, v.high);
|
|
1072
|
+
}
|
|
643
1073
|
}
|
|
644
|
-
this.pos = pos;
|
|
645
1074
|
}
|
|
646
1075
|
/**
|
|
647
1076
|
* Write a signed 64-bit array (zig-zag) from {@link Long}[] — the `bigint`-free
|
|
@@ -649,32 +1078,33 @@ var SofaBuffers = (function (exports) {
|
|
|
649
1078
|
*/
|
|
650
1079
|
writeSignedArrayLong(id, values) {
|
|
651
1080
|
this.arrayHead(id, WireType.ArraySigned, values.length);
|
|
652
|
-
this.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1081
|
+
if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
|
|
1082
|
+
let pos = this.pos;
|
|
1083
|
+
const buf = this.buf;
|
|
1084
|
+
for (let i = 0; i < values.length; i++) {
|
|
1085
|
+
const v = values[i];
|
|
1086
|
+
pos = encodeZigzagVarintLoHi(v.low, v.high, buf, pos);
|
|
1087
|
+
}
|
|
1088
|
+
this.pos = pos;
|
|
1089
|
+
} else {
|
|
1090
|
+
for (let i = 0; i < values.length; i++) {
|
|
1091
|
+
const v = values[i];
|
|
1092
|
+
this.putZigzagVarintLoHi(v.low, v.high);
|
|
1093
|
+
}
|
|
663
1094
|
}
|
|
664
|
-
this.pos = pos;
|
|
665
1095
|
}
|
|
666
1096
|
/** Write an array of IEEE-754 32-bit floats. */
|
|
667
1097
|
writeFp32Array(id, values) {
|
|
1098
|
+
if (values instanceof Float32Array) {
|
|
1099
|
+
this.writeFp32Words(id, values);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
668
1102
|
this.arrayHead(id, WireType.ArrayFixlen, values.length);
|
|
669
1103
|
this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
|
|
670
|
-
if (this.
|
|
671
|
-
this.ensure(values.length * 4);
|
|
1104
|
+
if (this.reserveBulk(values.length * 4)) {
|
|
672
1105
|
this.pos = this.kernel.packFp32Array(values, this.buf, this.pos);
|
|
673
1106
|
} else {
|
|
674
|
-
for (let i = 0; i < values.length; i++)
|
|
675
|
-
this.ensure(4);
|
|
676
|
-
this.pos = packFp32(this.buf, this.pos, values[i]);
|
|
677
|
-
}
|
|
1107
|
+
for (let i = 0; i < values.length; i++) this.putFp32(values[i]);
|
|
678
1108
|
}
|
|
679
1109
|
}
|
|
680
1110
|
/**
|
|
@@ -698,14 +1128,10 @@ var SofaBuffers = (function (exports) {
|
|
|
698
1128
|
writeFp64Array(id, values) {
|
|
699
1129
|
this.arrayHead(id, WireType.ArrayFixlen, values.length);
|
|
700
1130
|
this.putVarintNum(8 * 8 + FixlenSubtype.Fp64);
|
|
701
|
-
if (this.
|
|
702
|
-
this.ensure(values.length * 8);
|
|
1131
|
+
if (this.reserveBulk(values.length * 8)) {
|
|
703
1132
|
this.pos = this.kernel.packFp64Array(values, this.buf, this.pos);
|
|
704
1133
|
} else {
|
|
705
|
-
for (let i = 0; i < values.length; i++)
|
|
706
|
-
this.ensure(8);
|
|
707
|
-
this.pos = packFp64(this.buf, this.pos, values[i]);
|
|
708
|
-
}
|
|
1134
|
+
for (let i = 0; i < values.length; i++) this.putFp64(values[i]);
|
|
709
1135
|
}
|
|
710
1136
|
}
|
|
711
1137
|
// --- sequences ----------------------------------------------------------
|
|
@@ -729,7 +1155,7 @@ var SofaBuffers = (function (exports) {
|
|
|
729
1155
|
if (this.depth >= MAX_DEPTH) {
|
|
730
1156
|
throw argumentError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
731
1157
|
}
|
|
732
|
-
if (id
|
|
1158
|
+
if (id >>> 0 !== id || id > ID_MAX) {
|
|
733
1159
|
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
734
1160
|
}
|
|
735
1161
|
this.pending[this.nPending++] = id;
|
|
@@ -787,15 +1213,230 @@ var SofaBuffers = (function (exports) {
|
|
|
787
1213
|
if (this.depth > 0) this.depth--;
|
|
788
1214
|
}
|
|
789
1215
|
// --- internals ----------------------------------------------------------
|
|
790
|
-
/** Ensure exactly `value`'s varint size, then write it (bigint path). */
|
|
791
|
-
putVarint(value) {
|
|
792
|
-
this.ensure(varintSize(value));
|
|
793
|
-
this.pos = encodeVarint(value, this.buf, this.pos);
|
|
794
|
-
}
|
|
795
1216
|
/** Ensure exactly `value`'s varint size, then write it (number fast path). */
|
|
796
1217
|
putVarintNum(value) {
|
|
797
|
-
this.
|
|
798
|
-
|
|
1218
|
+
const pos = this.pos;
|
|
1219
|
+
if (value < 128 && pos < this.buf.length) {
|
|
1220
|
+
this.buf[pos] = value;
|
|
1221
|
+
this.pos = pos + 1;
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
this.putVarintNumSlow(value);
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* The multi-byte / needs-room tail of {@link putVarintNum}, kept in its own
|
|
1228
|
+
* method so the single-byte test above stays small enough for the JIT to
|
|
1229
|
+
* inline into every `header` / `fixlenHead` / `arrayHead` call site.
|
|
1230
|
+
*/
|
|
1231
|
+
putVarintNumSlow(value) {
|
|
1232
|
+
if (this.tryEnsure(varintSizeNum(value))) {
|
|
1233
|
+
this.pos = encodeVarintNum(value, this.buf, this.pos);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
while (value > 127) {
|
|
1237
|
+
this.putByte(value % 128 | 128);
|
|
1238
|
+
value = Math.floor(value / 128);
|
|
1239
|
+
}
|
|
1240
|
+
this.putByte(value);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Write a 64-bit value, held as two 32-bit halves, as a varint.
|
|
1244
|
+
*
|
|
1245
|
+
* Deliberately just a bounds check and two calls: this is what the array
|
|
1246
|
+
* loops call per element, and it only pays its way while it is small enough
|
|
1247
|
+
* for the JIT to inline. Spelling the drain case out here instead — three
|
|
1248
|
+
* more lines — cost 16-19% on an array streamed through a 32/64-byte buffer,
|
|
1249
|
+
* where most elements take the fast path and want it inlined.
|
|
1250
|
+
*/
|
|
1251
|
+
putVarintLoHi(lo, hi) {
|
|
1252
|
+
if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
|
|
1253
|
+
this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
this.putVarintLoHiSlow(lo, hi);
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* A full buffer that is nonetheless wide enough to hold any varint: drain it
|
|
1260
|
+
* and retry the worst case, which is exactly what `ensure(VARINT_MAX_BYTES)`
|
|
1261
|
+
* did before §5.1 and is still the common streaming case. Sizing the value
|
|
1262
|
+
* first instead — the narrow-buffer path in {@link putVarintLoHiTight} — cost
|
|
1263
|
+
* +125 instructions on every element of an array streamed through a
|
|
1264
|
+
* one-element-wide buffer.
|
|
1265
|
+
*/
|
|
1266
|
+
putVarintLoHiSlow(lo, hi) {
|
|
1267
|
+
if (this.buf.length - this.start >= VARINT_MAX_BYTES) {
|
|
1268
|
+
this.flush();
|
|
1269
|
+
if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
|
|
1270
|
+
this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
this.putVarintLoHiTight(lo, hi);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Zig-zag {@link putVarintLoHi}: `(n << 1) ^ (n >> 63)` on the halves. Keeps
|
|
1278
|
+
* its own worst-case fast path so the signed array loop reaches the combined
|
|
1279
|
+
* zig-zag-and-encode writer directly, exactly as it did before; the drain and
|
|
1280
|
+
* narrow-buffer cases are the same for both signs, so they are shared.
|
|
1281
|
+
*/
|
|
1282
|
+
putZigzagVarintLoHi(lo, hi) {
|
|
1283
|
+
if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
|
|
1284
|
+
this.pos = encodeZigzagVarintLoHi(lo, hi, this.buf, this.pos);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const sgn = -(hi >>> 31) >>> 0;
|
|
1288
|
+
this.putVarintLoHi(
|
|
1289
|
+
(lo << 1 >>> 0 ^ sgn) >>> 0,
|
|
1290
|
+
((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* The narrow-buffer tail of {@link putVarintLoHi}: the buffer could not hold a
|
|
1295
|
+
* worst-case varint even empty. Sizing the value exactly keeps such a buffer
|
|
1296
|
+
* from flushing for room it does not need; only when it cannot hold the varint
|
|
1297
|
+
* *at all* does the value get split across flushes, seven bits at a time.
|
|
1298
|
+
*/
|
|
1299
|
+
putVarintLoHiTight(lo, hi) {
|
|
1300
|
+
lo >>>= 0;
|
|
1301
|
+
hi >>>= 0;
|
|
1302
|
+
if (this.tryEnsure(varintSizeLoHi(lo, hi))) {
|
|
1303
|
+
this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
for (; ; ) {
|
|
1307
|
+
const more = hi !== 0 || lo > 127;
|
|
1308
|
+
this.putByte(more ? lo & 127 | 128 : lo);
|
|
1309
|
+
if (!more) return;
|
|
1310
|
+
lo = (lo >>> 7 | hi << 25) >>> 0;
|
|
1311
|
+
hi >>>= 7;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Write the 4 little-endian bytes of an fp32 (§4.6).
|
|
1316
|
+
*
|
|
1317
|
+
* Through the shared scratch, not a `DataView` over the buffer: building that
|
|
1318
|
+
* handle costs ~129 ns against the ~2 ns it saves on one value (§6.6.2 allows the
|
|
1319
|
+
* handle, arithmetic forbids it here). The bulk array path amortizes one over the
|
|
1320
|
+
* whole run instead — see the kernel.
|
|
1321
|
+
*/
|
|
1322
|
+
putFp32(value) {
|
|
1323
|
+
if (this.buf.length - this.pos >= 4) {
|
|
1324
|
+
this.pos = packFp32(this.buf, this.pos, value);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
this.putFp32Slow(value);
|
|
1328
|
+
}
|
|
1329
|
+
putFp32Slow(value) {
|
|
1330
|
+
if (this.tryEnsure(4)) {
|
|
1331
|
+
this.pos = packFp32(this.buf, this.pos, value);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.putFp32Bits(fp32Bits(value));
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Write the 4 little-endian bytes of an fp32 held as a 32-bit word (§4.6) — the
|
|
1338
|
+
* path {@link OStream.writeFp32Bits} takes, and the tail of
|
|
1339
|
+
* {@link OStream.putFp32Slow}.
|
|
1340
|
+
*/
|
|
1341
|
+
putFp32Bits(bits) {
|
|
1342
|
+
if (this.buf.length - this.pos >= 4) {
|
|
1343
|
+
const buf = this.buf;
|
|
1344
|
+
let p = this.pos;
|
|
1345
|
+
buf[p++] = bits & 255;
|
|
1346
|
+
buf[p++] = bits >>> 8 & 255;
|
|
1347
|
+
buf[p++] = bits >>> 16 & 255;
|
|
1348
|
+
buf[p++] = bits >>> 24;
|
|
1349
|
+
this.pos = p;
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
this.putByte(bits & 255);
|
|
1353
|
+
this.putByte(bits >>> 8 & 255);
|
|
1354
|
+
this.putByte(bits >>> 16 & 255);
|
|
1355
|
+
this.putByte(bits >>> 24);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* {@link writeFp32Array} for a `Float32Array` source: its own 32-bit words go
|
|
1359
|
+
* out, never a `number`, which is what keeps a signaling NaN intact (§6.5) —
|
|
1360
|
+
* on the bulk path (the kernel copies words too) and on the streamed one alike,
|
|
1361
|
+
* so a small buffer produces the one-shot bytes (§5.1.4).
|
|
1362
|
+
*
|
|
1363
|
+
* Kept to the header and the bulk call, the streaming loop in its own method:
|
|
1364
|
+
* with the loop in this body an 8-element one-shot array cost +416 Ir/op
|
|
1365
|
+
* (+8.8%) over the pre-#185 encoder, split like this +1.1% (Callgrind).
|
|
1366
|
+
*/
|
|
1367
|
+
writeFp32Words(id, values) {
|
|
1368
|
+
this.arrayHead(id, WireType.ArrayFixlen, values.length);
|
|
1369
|
+
this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
|
|
1370
|
+
if (this.reserveBulk(values.length * 4)) {
|
|
1371
|
+
this.pos = this.kernel.packFp32Array(values, this.buf, this.pos);
|
|
1372
|
+
} else {
|
|
1373
|
+
this.putFp32Words(values);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* The streamed half of {@link writeFp32Words}: one `Uint32Array` over the
|
|
1378
|
+
* source per call, the same handle the bulk kernel takes (§6.6.2); reading it gives each word's value whatever the
|
|
1379
|
+
* host byte order, and the shifts below store it little-endian. The words go
|
|
1380
|
+
* out one run per stretch of free buffer, with `buf`/`pos` in locals, and the
|
|
1381
|
+
* split points are {@link putFp32}'s: drain when fewer than 4 bytes are free,
|
|
1382
|
+
* split an element byte by byte only when the buffer itself is narrower than 4.
|
|
1383
|
+
*/
|
|
1384
|
+
putFp32Words(values) {
|
|
1385
|
+
const w = new Uint32Array(values.buffer, values.byteOffset, values.length);
|
|
1386
|
+
const n = w.length;
|
|
1387
|
+
let i = 0;
|
|
1388
|
+
while (i < n) {
|
|
1389
|
+
if (this.buf.length - this.pos < 4 && !this.tryEnsure(4)) {
|
|
1390
|
+
this.putFp32Bits(w[i++]);
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
const buf = this.buf;
|
|
1394
|
+
let p = this.pos;
|
|
1395
|
+
const end = Math.min(n, i + (buf.length - p >> 2));
|
|
1396
|
+
for (; i < end; i++) {
|
|
1397
|
+
const b = w[i];
|
|
1398
|
+
buf[p] = b & 255;
|
|
1399
|
+
buf[p + 1] = b >>> 8 & 255;
|
|
1400
|
+
buf[p + 2] = b >>> 16 & 255;
|
|
1401
|
+
buf[p + 3] = b >>> 24;
|
|
1402
|
+
p += 4;
|
|
1403
|
+
}
|
|
1404
|
+
this.pos = p;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
/** Write the 8 little-endian bytes of an fp64 (§4.6) — see {@link putFp32}. */
|
|
1408
|
+
putFp64(value) {
|
|
1409
|
+
if (this.buf.length - this.pos >= 8) {
|
|
1410
|
+
this.pos = packFp64(this.buf, this.pos, value);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
this.putFp64Slow(value);
|
|
1414
|
+
}
|
|
1415
|
+
putFp64Slow(value) {
|
|
1416
|
+
if (this.tryEnsure(8)) {
|
|
1417
|
+
this.pos = packFp64(this.buf, this.pos, value);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const lo = fp64BitsLo(value);
|
|
1421
|
+
const hi = fp64BitsHi(value);
|
|
1422
|
+
this.putByte(lo & 255);
|
|
1423
|
+
this.putByte(lo >>> 8 & 255);
|
|
1424
|
+
this.putByte(lo >>> 16 & 255);
|
|
1425
|
+
this.putByte(lo >>> 24);
|
|
1426
|
+
this.putByte(hi & 255);
|
|
1427
|
+
this.putByte(hi >>> 8 & 255);
|
|
1428
|
+
this.putByte(hi >>> 16 & 255);
|
|
1429
|
+
this.putByte(hi >>> 24);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Append one byte, draining to the sink first when the buffer is full.
|
|
1433
|
+
*
|
|
1434
|
+
* @internal Public only because {@link utf8WriteSink} writes through it — the
|
|
1435
|
+
* narrow-buffer string path (§5.1.3). Not part of the field-writing API.
|
|
1436
|
+
*/
|
|
1437
|
+
putByte(b) {
|
|
1438
|
+
if (this.pos === this.buf.length) this.ensureSome(1);
|
|
1439
|
+
this.buf[this.pos++] = b;
|
|
799
1440
|
}
|
|
800
1441
|
/**
|
|
801
1442
|
* Write a field header, the `(id << 3) | wireType` tag, as a varint.
|
|
@@ -814,7 +1455,7 @@ var SofaBuffers = (function (exports) {
|
|
|
814
1455
|
* {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
|
|
815
1456
|
*/
|
|
816
1457
|
header(id, type) {
|
|
817
|
-
if (id
|
|
1458
|
+
if (id >>> 0 !== id || id > ID_MAX) {
|
|
818
1459
|
throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
|
|
819
1460
|
}
|
|
820
1461
|
if (this.nPending !== 0) this.commitPending();
|
|
@@ -831,8 +1472,9 @@ var SofaBuffers = (function (exports) {
|
|
|
831
1472
|
commitPending() {
|
|
832
1473
|
const n = this.nPending;
|
|
833
1474
|
this.nPending = 0;
|
|
1475
|
+
const pending = this.pending;
|
|
834
1476
|
for (let i = 0; i < n; i++) {
|
|
835
|
-
this.putVarintNum(
|
|
1477
|
+
this.putVarintNum(pending[i] * 8 + WireType.SequenceStart);
|
|
836
1478
|
}
|
|
837
1479
|
}
|
|
838
1480
|
fixlenHead(id, length, subtype) {
|
|
@@ -846,332 +1488,335 @@ var SofaBuffers = (function (exports) {
|
|
|
846
1488
|
this.header(id, type);
|
|
847
1489
|
this.putVarintNum(count);
|
|
848
1490
|
}
|
|
849
|
-
/**
|
|
1491
|
+
/**
|
|
1492
|
+
* Copy `data` out, flushing/growing as needed (large payloads stay chunked).
|
|
1493
|
+
*
|
|
1494
|
+
* The whole-payload case — the common one, and the only one on a buffer sized
|
|
1495
|
+
* from `MAX_SIZE` or on the accumulator — is a single `set` of the caller's
|
|
1496
|
+
* array: a `memcpy`, and no view at all.
|
|
1497
|
+
*
|
|
1498
|
+
* **A payload split across flushes takes a per-piece view: a language-forced
|
|
1499
|
+
* handle under CORELIB_PLAN §6.6.2.** `TypedArray.set` is the only `memcpy`
|
|
1500
|
+
* this language exposes and it takes a *typed array* as its source, so copying a
|
|
1501
|
+
* *range* of one needs a `subarray` — §6.6.2's "the only way to name a region of
|
|
1502
|
+
* the caller's buffer is a wrapper over it". It has the two properties that
|
|
1503
|
+
* clause requires: it carries no message bytes (the storage is the caller's, on
|
|
1504
|
+
* both ends) and no wire number sizes it (a handle over a thousand bytes costs
|
|
1505
|
+
* what a handle over ten costs). The allocation-free alternative is a byte loop,
|
|
1506
|
+
* measured at 358 MB/s against 10,963 MB/s for `set`.
|
|
1507
|
+
*
|
|
1508
|
+
* It never leaves this method: `set` consumes it and no caller can reach it, so
|
|
1509
|
+
* §6.7's ban on exposing a value that outlives its callback is untouched — and so
|
|
1510
|
+
* is §5.1.6, which is why the copy happens at all rather than the payload being
|
|
1511
|
+
* handed to the sink. §6.6.2 asks the port to make such handles visible rather
|
|
1512
|
+
* than invisible: the README itemises it (§9.6) and `heap-free-codec.test.ts`
|
|
1513
|
+
* pins its count and kind.
|
|
1514
|
+
*/
|
|
850
1515
|
writeRaw(data) {
|
|
1516
|
+
const total = data.length;
|
|
851
1517
|
let off = 0;
|
|
852
|
-
while (off <
|
|
853
|
-
const room = this.ensureSome(
|
|
854
|
-
|
|
1518
|
+
while (off < total) {
|
|
1519
|
+
const room = this.ensureSome(total - off);
|
|
1520
|
+
if (off === 0 && room === total) {
|
|
1521
|
+
this.buf.set(data, this.pos);
|
|
1522
|
+
} else {
|
|
1523
|
+
this.buf.set(data.subarray(off, off + room), this.pos);
|
|
1524
|
+
}
|
|
855
1525
|
this.pos += room;
|
|
856
1526
|
off += room;
|
|
857
1527
|
}
|
|
858
1528
|
}
|
|
859
|
-
/**
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1529
|
+
/**
|
|
1530
|
+
* Make room for `n` contiguous bytes at `pos` if the buffer can hold them at
|
|
1531
|
+
* all: `true` when it can (flushing or growing as needed), `false` when a
|
|
1532
|
+
* fixed caller buffer is simply smaller than `n` and the value must be split
|
|
1533
|
+
* across flushes instead — CORELIB_PLAN §5.1 puts the floor on the output
|
|
1534
|
+
* buffer at a single byte, so no write may demand a contiguous run.
|
|
1535
|
+
*
|
|
1536
|
+
* The one case that still fails is a buffer with no sink to drain to: there is
|
|
1537
|
+
* nowhere for a split to put the earlier bytes, so it reports BufferFull here,
|
|
1538
|
+
* before anything is written, exactly as {@link ensure} did.
|
|
1539
|
+
*/
|
|
1540
|
+
tryEnsure(n) {
|
|
1541
|
+
if (this.buf.length - this.pos >= n) return true;
|
|
1542
|
+
this.drain(n);
|
|
1543
|
+
if (this.buf.length - this.pos >= n) return true;
|
|
1544
|
+
if (this.flushSink === void 0) {
|
|
1545
|
+
throw bufferFullError(
|
|
1546
|
+
`output buffer full: need ${n} more bytes, have ${this.buf.length - this.pos}`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
return false;
|
|
871
1550
|
}
|
|
872
|
-
/**
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1551
|
+
/**
|
|
1552
|
+
* Reserve `n` contiguous bytes for a bulk write — the whole payload of an
|
|
1553
|
+
* array or a string, written in one pass into a buffer that cannot move under
|
|
1554
|
+
* it. Every caller has an element-at-a-time route to fall back on when this
|
|
1555
|
+
* says no, producing the identical bytes, so a `false` here is never an error.
|
|
1556
|
+
*
|
|
1557
|
+
* The room already at the cursor counts on **any** buffer, which is what makes
|
|
1558
|
+
* the one-shot `new OStream(buf)` case — a caller buffer sized from the
|
|
1559
|
+
* schema's `MAX_SIZE`, the shape CORELIB_PLAN §5.1 puts first — the fast one:
|
|
1560
|
+
* without it a message encoded into a caller's own buffer took `TextEncoder`
|
|
1561
|
+
* for every string and the element loop for every array, measured at 1.9 µs
|
|
1562
|
+
* against 0.16 µs for the same five-field message.
|
|
1563
|
+
*
|
|
1564
|
+
* Beyond that room only a **sink** may be asked, and only by draining. Emptying
|
|
1565
|
+
* the buffer is the one thing that can produce room without an allocation
|
|
1566
|
+
* anywhere (§6.6), and it is legal wherever this is called — every caller has
|
|
1567
|
+
* just written a complete header, so the cursor is on a boundary between atomic
|
|
1568
|
+
* units (§5.1.3). A sink that installs a larger replacement (§5.1.5) therefore
|
|
1569
|
+
* re-opens the bulk route by itself, which is how the accumulating helper keeps
|
|
1570
|
+
* it. Nothing is *demanded*: a `false` sends the caller down its
|
|
1571
|
+
* element-at-a-time route, which produces the identical bytes, so a fixed
|
|
1572
|
+
* buffer too narrow for the worst case never turns into a spurious
|
|
1573
|
+
* `BUFFER_FULL`.
|
|
1574
|
+
*/
|
|
1575
|
+
reserveBulk(n) {
|
|
1576
|
+
if (this.buf.length - this.pos >= n) return true;
|
|
1577
|
+
if (this.flushSink === void 0) return false;
|
|
1578
|
+
this.drain(n);
|
|
1579
|
+
return this.buf.length - this.pos >= n;
|
|
895
1580
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const stack = [root];
|
|
918
|
-
let top = root;
|
|
919
|
-
while (this.p < this.n) {
|
|
920
|
-
this.readVarint();
|
|
921
|
-
const type = this.lo & 7;
|
|
922
|
-
if (type === WireType.SequenceEnd) {
|
|
923
|
-
if (stack.length <= 1) throw invalidMsgError("unbalanced sequence end");
|
|
924
|
-
top.sequenceEnd?.();
|
|
925
|
-
stack.pop();
|
|
926
|
-
top = stack[stack.length - 1];
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
const id = this.upper();
|
|
930
|
-
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
931
|
-
switch (type) {
|
|
932
|
-
case WireType.Unsigned: {
|
|
933
|
-
this.readVarint();
|
|
934
|
-
top.unsigned?.(id, this.unsignedValue());
|
|
935
|
-
break;
|
|
936
|
-
}
|
|
937
|
-
case WireType.Signed: {
|
|
938
|
-
this.readVarint();
|
|
939
|
-
top.signed?.(id, this.signedValue());
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
case WireType.Fixlen: {
|
|
943
|
-
this.readVarint();
|
|
944
|
-
const sub = this.lo & 7;
|
|
945
|
-
const len = this.upper();
|
|
946
|
-
if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
947
|
-
if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
|
|
948
|
-
if (sub === FixlenSubtype.String && len > this.maxStringLen) {
|
|
949
|
-
throw limitExceededError(`string length ${len} exceeds maxStringLen ${this.maxStringLen}`);
|
|
950
|
-
}
|
|
951
|
-
if (sub === FixlenSubtype.Blob && len > this.maxBlobLen) {
|
|
952
|
-
throw limitExceededError(`blob length ${len} exceeds maxBlobLen ${this.maxBlobLen}`);
|
|
953
|
-
}
|
|
954
|
-
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
955
|
-
const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
|
|
956
|
-
if (len !== want) throw invalidMsgError("fixlen float length mismatch");
|
|
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
|
-
}
|
|
965
|
-
} else {
|
|
966
|
-
const chunk = this.take(len);
|
|
967
|
-
if (sub === FixlenSubtype.String) top.string?.(id, len, 0, chunk);
|
|
968
|
-
else top.blob?.(id, len, 0, chunk);
|
|
969
|
-
}
|
|
970
|
-
break;
|
|
971
|
-
}
|
|
972
|
-
case WireType.ArrayUnsigned: {
|
|
973
|
-
const count = this.arrayCount();
|
|
974
|
-
top.arrayBegin?.(id, ArrayKind.Unsigned, count);
|
|
975
|
-
for (let i = 0; i < count; i++) {
|
|
976
|
-
this.readVarint();
|
|
977
|
-
top.arrayUnsigned?.(id, i, this.unsignedValue());
|
|
978
|
-
}
|
|
979
|
-
top.arrayEnd?.(id);
|
|
980
|
-
break;
|
|
981
|
-
}
|
|
982
|
-
case WireType.ArraySigned: {
|
|
983
|
-
const count = this.arrayCount();
|
|
984
|
-
top.arrayBegin?.(id, ArrayKind.Signed, count);
|
|
985
|
-
for (let i = 0; i < count; i++) {
|
|
986
|
-
this.readVarint();
|
|
987
|
-
top.arraySigned?.(id, i, this.signedValue());
|
|
988
|
-
}
|
|
989
|
-
top.arrayEnd?.(id);
|
|
990
|
-
break;
|
|
991
|
-
}
|
|
992
|
-
case WireType.ArrayFixlen: {
|
|
993
|
-
const count = this.arrayCount();
|
|
994
|
-
this.readVarint();
|
|
995
|
-
const sub = this.lo & 7;
|
|
996
|
-
const size = this.upper();
|
|
997
|
-
let kind;
|
|
998
|
-
if (sub === FixlenSubtype.Fp32 && size === 4) kind = ArrayKind.Fp32;
|
|
999
|
-
else if (sub === FixlenSubtype.Fp64 && size === 8) kind = ArrayKind.Fp64;
|
|
1000
|
-
else throw invalidMsgError("invalid fixlen array element type");
|
|
1001
|
-
top.arrayBegin?.(id, kind, count);
|
|
1002
|
-
if (kind === ArrayKind.Fp32) {
|
|
1003
|
-
const wantRaw = top.fp32Raw === true;
|
|
1004
|
-
for (let i = 0; i < count; i++) {
|
|
1005
|
-
const p = this.p;
|
|
1006
|
-
const value = this.readFp32();
|
|
1007
|
-
top.arrayFp32?.(id, i, value, wantRaw ? this.buf.subarray(p, p + 4) : void 0);
|
|
1008
|
-
}
|
|
1009
|
-
} else {
|
|
1010
|
-
for (let i = 0; i < count; i++) {
|
|
1011
|
-
const value = this.readFp64();
|
|
1012
|
-
top.arrayFp64?.(id, i, value);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
top.arrayEnd?.(id);
|
|
1016
|
-
break;
|
|
1017
|
-
}
|
|
1018
|
-
case WireType.SequenceStart: {
|
|
1019
|
-
if (stack.length - 1 >= MAX_DEPTH) {
|
|
1020
|
-
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1021
|
-
}
|
|
1022
|
-
const child = top.sequenceBegin?.(id);
|
|
1023
|
-
top = child ?? top;
|
|
1024
|
-
stack.push(top);
|
|
1025
|
-
break;
|
|
1026
|
-
}
|
|
1027
|
-
default:
|
|
1028
|
-
throw invalidMsgError(`invalid wire type ${type}`);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (stack.length > 1) throw incompleteError("truncated message: unbalanced sequence");
|
|
1032
|
-
}
|
|
1033
|
-
// --- field helpers ------------------------------------------------------
|
|
1034
|
-
/** Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). */
|
|
1035
|
-
arrayCount() {
|
|
1036
|
-
this.readVarint();
|
|
1037
|
-
const count = this.num();
|
|
1038
|
-
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1039
|
-
if (count > this.maxArrayCount) {
|
|
1040
|
-
throw limitExceededError(`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`);
|
|
1041
|
-
}
|
|
1042
|
-
return count;
|
|
1043
|
-
}
|
|
1044
|
-
/** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
|
|
1045
|
-
take(len) {
|
|
1046
|
-
const start = this.p;
|
|
1047
|
-
const end = start + len;
|
|
1048
|
-
if (end > this.n) throw incompleteError("truncated fixlen payload");
|
|
1049
|
-
this.p = end;
|
|
1050
|
-
return this.buf.subarray(start, end);
|
|
1051
|
-
}
|
|
1052
|
-
readFp32() {
|
|
1053
|
-
const p = this.p;
|
|
1054
|
-
if (p + 4 > this.n) throw incompleteError("truncated fp32");
|
|
1055
|
-
this.p = p + 4;
|
|
1056
|
-
return this.view.getFloat32(p, true);
|
|
1057
|
-
}
|
|
1058
|
-
readFp64() {
|
|
1059
|
-
const p = this.p;
|
|
1060
|
-
if (p + 8 > this.n) throw incompleteError("truncated fp64");
|
|
1061
|
-
this.p = p + 8;
|
|
1062
|
-
return this.view.getFloat64(p, true);
|
|
1063
|
-
}
|
|
1064
|
-
// --- varint reading -----------------------------------------------------
|
|
1065
|
-
/** The last varint's full value as a `bigint` (64-bit fidelity). */
|
|
1066
|
-
big() {
|
|
1067
|
-
return this.hi === 0 ? BigInt(this.lo >>> 0) : BigInt(this.hi >>> 0) << 32n | BigInt(this.lo >>> 0);
|
|
1068
|
-
}
|
|
1069
|
-
/**
|
|
1070
|
-
* The last varint as an unsigned value, number-first: a `number` when it fits
|
|
1071
|
-
* exactly (`≤ 2^53-1` — all ids, u8..u32 and small u64s), a `bigint` only
|
|
1072
|
-
* beyond that. Skips the per-value bigint allocation on the common path.
|
|
1073
|
-
*/
|
|
1074
|
-
unsignedValue() {
|
|
1075
|
-
const hi = this.hi >>> 0;
|
|
1076
|
-
return hi <= 2097151 ? hi * TWO32 + (this.lo >>> 0) : this.big();
|
|
1077
|
-
}
|
|
1078
|
-
/** The last zig-zag varint as a signed value, number-first (see {@link unsignedValue}). */
|
|
1079
|
-
signedValue() {
|
|
1080
|
-
const hi = this.hi >>> 0;
|
|
1081
|
-
if (hi <= 2097151) {
|
|
1082
|
-
const r = hi * TWO32 + (this.lo >>> 0);
|
|
1083
|
-
return r % 2 === 0 ? r / 2 : -(r + 1) / 2;
|
|
1581
|
+
/**
|
|
1582
|
+
* Commit the position a bulk kernel returned, in the **block** mode.
|
|
1583
|
+
*
|
|
1584
|
+
* Nothing reserves room in front of that kernel, and nothing could: the bytes an
|
|
1585
|
+
* array takes are only known once it is encoded, and the estimate that used to
|
|
1586
|
+
* stand in — the source's own `constructor`, which any object can set — silently
|
|
1587
|
+
* truncated the message when it was wrong (§5.1). Here the bound is the buffer's
|
|
1588
|
+
* own length, applied afterwards. The varint kernels stop writing at
|
|
1589
|
+
* `out.length` and keep counting (see {@link Kernel}), so a `pos` past the end
|
|
1590
|
+
* is the exact shortfall and nothing was written outside the buffer.
|
|
1591
|
+
*
|
|
1592
|
+
* This is the block mode's error and only its: the buffer is meant to hold the
|
|
1593
|
+
* whole message, so one that does not fit is exactly what `BUFFER_FULL` means.
|
|
1594
|
+
* The streaming mode never gets here, and `BUFFER_FULL` is unreachable there by
|
|
1595
|
+
* contract.
|
|
1596
|
+
*/
|
|
1597
|
+
bulkEnd(pos, count) {
|
|
1598
|
+
if (pos > this.buf.length) {
|
|
1599
|
+
throw bufferFullError(
|
|
1600
|
+
`output buffer full: an array of ${count} elements needs ${pos - this.buf.length} more bytes`
|
|
1601
|
+
);
|
|
1084
1602
|
}
|
|
1085
|
-
return
|
|
1086
|
-
}
|
|
1087
|
-
/** The last varint's value as a JS number — exact for ids/lengths/counts. */
|
|
1088
|
-
num() {
|
|
1089
|
-
return this.hi * TWO32 + (this.lo >>> 0);
|
|
1090
|
-
}
|
|
1091
|
-
/** The last varint with its low 3 tag bits stripped (`value >> 3`). */
|
|
1092
|
-
upper() {
|
|
1093
|
-
return (this.hi >>> 0) * (TWO32 / 8) + (this.lo >>> 3);
|
|
1603
|
+
return pos;
|
|
1094
1604
|
}
|
|
1095
1605
|
/**
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1606
|
+
* Ensure `n` contiguous bytes are free at `pos`; returns `pos` for chaining.
|
|
1607
|
+
*
|
|
1608
|
+
* The only remaining caller is the one-byte sequence-end marker, which is
|
|
1609
|
+
* indivisible: there is no smaller piece to split it into, so a buffer that
|
|
1610
|
+
* cannot take it has nothing left to report but `BUFFER_FULL`.
|
|
1099
1611
|
*/
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1108
|
-
b = buf[p++];
|
|
1109
|
-
lo = b & 127;
|
|
1110
|
-
if (b < 128) return this.set(lo, 0, p);
|
|
1111
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1112
|
-
b = buf[p++];
|
|
1113
|
-
lo |= (b & 127) << 7;
|
|
1114
|
-
if (b < 128) return this.set(lo, 0, p);
|
|
1115
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1116
|
-
b = buf[p++];
|
|
1117
|
-
lo |= (b & 127) << 14;
|
|
1118
|
-
if (b < 128) return this.set(lo, 0, p);
|
|
1119
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1120
|
-
b = buf[p++];
|
|
1121
|
-
lo |= (b & 127) << 21;
|
|
1122
|
-
if (b < 128) return this.set(lo, 0, p);
|
|
1123
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1124
|
-
b = buf[p++];
|
|
1125
|
-
lo |= (b & 15) << 28;
|
|
1126
|
-
hi = b >> 4 & 7;
|
|
1127
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1128
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1129
|
-
b = buf[p++];
|
|
1130
|
-
hi |= (b & 127) << 3;
|
|
1131
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1132
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1133
|
-
b = buf[p++];
|
|
1134
|
-
hi |= (b & 127) << 10;
|
|
1135
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1136
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1137
|
-
b = buf[p++];
|
|
1138
|
-
hi |= (b & 127) << 17;
|
|
1139
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1140
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1141
|
-
b = buf[p++];
|
|
1142
|
-
hi |= (b & 127) << 24;
|
|
1143
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1144
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
1145
|
-
b = buf[p++];
|
|
1146
|
-
if ((b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
|
|
1147
|
-
hi |= (b & 127) << 31;
|
|
1148
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
1149
|
-
throw invalidMsgError("varint overflow");
|
|
1612
|
+
ensure(n) {
|
|
1613
|
+
if (!this.tryEnsure(n)) {
|
|
1614
|
+
throw bufferFullError(
|
|
1615
|
+
`output buffer full: need ${n} more bytes, have ${this.buf.length - this.pos}`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
return this.pos;
|
|
1150
1619
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
this.
|
|
1154
|
-
|
|
1620
|
+
/** Ensure *some* room (up to `want`); returns how many bytes are available. */
|
|
1621
|
+
ensureSome(want) {
|
|
1622
|
+
let room = this.buf.length - this.pos;
|
|
1623
|
+
if (room === 0) {
|
|
1624
|
+
this.flush();
|
|
1625
|
+
room = this.buf.length - this.pos;
|
|
1626
|
+
if (room === 0) throw bufferFullError("output buffer full");
|
|
1627
|
+
}
|
|
1628
|
+
return Math.min(room, want);
|
|
1155
1629
|
}
|
|
1156
1630
|
};
|
|
1157
1631
|
|
|
1632
|
+
// src/encode/accumulate.ts
|
|
1633
|
+
var DEFAULT_CAPACITY = 256;
|
|
1634
|
+
var MIN_WINDOW = 16;
|
|
1635
|
+
var SLAB_BYTES = 8192;
|
|
1636
|
+
var SLAB_MAX_TAKE = SLAB_BYTES >>> 1;
|
|
1637
|
+
var slab = null;
|
|
1638
|
+
var slabUsed = 0;
|
|
1639
|
+
function accumulatorBuffer(n) {
|
|
1640
|
+
if (n > SLAB_MAX_TAKE) return new Uint8Array(n);
|
|
1641
|
+
let s = slab;
|
|
1642
|
+
if (s === null || s.length - slabUsed < n) {
|
|
1643
|
+
s = slab = new Uint8Array(SLAB_BYTES);
|
|
1644
|
+
slabUsed = 0;
|
|
1645
|
+
}
|
|
1646
|
+
const from = slabUsed;
|
|
1647
|
+
slabUsed = from + n;
|
|
1648
|
+
return s.subarray(from, slabUsed);
|
|
1649
|
+
}
|
|
1650
|
+
var ACCUMULATE = function(buffer, _start, end, needed = 0) {
|
|
1651
|
+
const used = this.bytesUsed;
|
|
1652
|
+
const window = needed > MIN_WINDOW ? needed : MIN_WINDOW;
|
|
1653
|
+
if (buffer.length - end >= window) {
|
|
1654
|
+
this.setBuffer(buffer, end, used);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
let cap = buffer.length * 2;
|
|
1658
|
+
const want = used + window;
|
|
1659
|
+
if (cap < want) cap = want;
|
|
1660
|
+
const next = accumulatorBuffer(cap);
|
|
1661
|
+
next.set(buffer.subarray(end - used, end));
|
|
1662
|
+
this.setBuffer(next, used, used);
|
|
1663
|
+
};
|
|
1664
|
+
function growingOStream(initialCapacity = DEFAULT_CAPACITY) {
|
|
1665
|
+
if (!Number.isInteger(initialCapacity) || initialCapacity < 1) {
|
|
1666
|
+
throw argumentError(`initial capacity ${initialCapacity} must be a positive integer`);
|
|
1667
|
+
}
|
|
1668
|
+
return new OStream(accumulatorBuffer(initialCapacity), 0, ACCUMULATE);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// src/decode/skip.ts
|
|
1672
|
+
var SKIP = Object.freeze({});
|
|
1673
|
+
|
|
1158
1674
|
// src/decode/state.ts
|
|
1159
|
-
var
|
|
1675
|
+
var TWO32 = 4294967296;
|
|
1676
|
+
var TYPED_CAPACITY = /* @__PURE__ */ new Map([
|
|
1677
|
+
[Uint8Array, { min: 0, max: 255 }],
|
|
1678
|
+
[Uint16Array, { min: 0, max: 65535 }],
|
|
1679
|
+
[Uint32Array, { min: 0, max: 4294967295 }],
|
|
1680
|
+
[Int8Array, { min: -128, max: 127 }],
|
|
1681
|
+
[Int16Array, { min: -32768, max: 32767 }],
|
|
1682
|
+
[Int32Array, { min: -2147483648, max: 2147483647 }],
|
|
1683
|
+
// The 64-bit pair is the accumulator's own range on both sides, so any bound a
|
|
1684
|
+
// caller can state fits: `min`/`max` here are the sentinels the fit test needs,
|
|
1685
|
+
// not numbers anything is compared against (the fill writes raw halves).
|
|
1686
|
+
[BigUint64Array, { min: 0, max: Number.POSITIVE_INFINITY }],
|
|
1687
|
+
[BigInt64Array, { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY }]
|
|
1688
|
+
]);
|
|
1689
|
+
function typedCapacity(ctor) {
|
|
1690
|
+
if (ctor === Uint8Array) return CAP_U8;
|
|
1691
|
+
if (ctor === Int8Array) return CAP_I8;
|
|
1692
|
+
if (ctor === Uint16Array) return CAP_U16;
|
|
1693
|
+
if (ctor === Int16Array) return CAP_I16;
|
|
1694
|
+
if (ctor === Uint32Array) return CAP_U32;
|
|
1695
|
+
if (ctor === Int32Array) return CAP_I32;
|
|
1696
|
+
return TYPED_CAPACITY.get(ctor);
|
|
1697
|
+
}
|
|
1698
|
+
var CAP_U8 = TYPED_CAPACITY.get(Uint8Array);
|
|
1699
|
+
var CAP_I8 = TYPED_CAPACITY.get(Int8Array);
|
|
1700
|
+
var CAP_U16 = TYPED_CAPACITY.get(Uint16Array);
|
|
1701
|
+
var CAP_I16 = TYPED_CAPACITY.get(Int16Array);
|
|
1702
|
+
var CAP_U32 = TYPED_CAPACITY.get(Uint32Array);
|
|
1703
|
+
var CAP_I32 = TYPED_CAPACITY.get(Int32Array);
|
|
1704
|
+
function is64(d) {
|
|
1705
|
+
return d instanceof BigUint64Array || d instanceof BigInt64Array;
|
|
1706
|
+
}
|
|
1160
1707
|
var DecoderState = class {
|
|
1161
|
-
constructor(
|
|
1708
|
+
constructor(visitor = SKIP) {
|
|
1709
|
+
/**
|
|
1710
|
+
* Depth at which the current skipped subtree was opened, or `-1` when nothing
|
|
1711
|
+
* is being skipped. The scope that returned `false` from
|
|
1712
|
+
* {@link Visitor.sequenceBegin} closes at this depth, and skipping ends there.
|
|
1713
|
+
*/
|
|
1714
|
+
this.skipFrom = -1;
|
|
1715
|
+
/**
|
|
1716
|
+
* **No receiver cap lives here** (§6.2.1). This decoder used to hold
|
|
1717
|
+
* `maxArrayCount` / `maxStringLen` / `maxBlobLen`, defaulted to the format
|
|
1718
|
+
* ceilings when the caller supplied none — which is precisely the shape §6.2.1
|
|
1719
|
+
* forbids: "a codec **MUST NOT** hold a limit of its own, **MUST NOT** supply a
|
|
1720
|
+
* default for one it was not given, **MUST NOT** read an omitted argument as
|
|
1721
|
+
* *unlimited*, and **MUST NOT** clamp to one", and "a format ceiling (§6.2)
|
|
1722
|
+
* reached because no cap was stated is the **format's** bound, not a receiver
|
|
1723
|
+
* cap, and a port **MUST NOT** present it as one". Reporting `LimitExceeded`
|
|
1724
|
+
* against `ARRAY_MAX`/`FIXLEN_MAX` did exactly that.
|
|
1725
|
+
*
|
|
1726
|
+
* The caps now have **one implementation** and it is not here: generated code
|
|
1727
|
+
* compares them in its own flat visitor, at `arrayBegin` / `fixlenBegin` — both
|
|
1728
|
+
* of which this decoder raises at the count / length header, before a byte of
|
|
1729
|
+
* payload is emitted and behind the MESSAGE_SPEC §7.3 tag test — and a wrapper
|
|
1730
|
+
* array's elements, which reach no visitor callback, are capped by the
|
|
1731
|
+
* `StringSeq` / `BlobSeq` collectors from bounds passed to their
|
|
1732
|
+
* constructors. §6.2.1: "A port whose codec offers the check **MUST NOT** also
|
|
1733
|
+
* emit it into the generated layer, and a port that enforces it in generated
|
|
1734
|
+
* code **MUST NOT** ask the codec to enforce it too."
|
|
1735
|
+
*
|
|
1736
|
+
* The format ceilings below (`ARRAY_MAX`, `FIXLEN_MAX`, `MAX_DEPTH`, the varint
|
|
1737
|
+
* bound) are *not* receiver caps and stay here: they bound what the wire may
|
|
1738
|
+
* express, and exceeding one is `INVALID` (§6.2).
|
|
1739
|
+
*/
|
|
1740
|
+
/**
|
|
1741
|
+
* The id of every open sequence, indexed by the depth it was opened at, so a
|
|
1742
|
+
* scope close can name the sequence it closes. Fixed size, sized from
|
|
1743
|
+
* {@link MAX_DEPTH} at construction — the "fixed-size parse stack" §6.6.2
|
|
1744
|
+
* allows, and the reason nothing here grows a stack per message.
|
|
1745
|
+
*
|
|
1746
|
+
* A plain array rather than an `Int32Array`, and the difference is 3 µs: V8
|
|
1747
|
+
* keeps a typed array's bytes inside the JS heap only up to 64 bytes, so a
|
|
1748
|
+
* 255-slot one is an *external* allocation — measured at ~3.1 µs on Node 24,
|
|
1749
|
+
* against ~5 ns for this. That is paid per decoder, and the one-shot
|
|
1750
|
+
* {@link decode} builds a decoder per message, so it was the whole cost of
|
|
1751
|
+
* decoding a small one. Slots are written before they are read (a scope is
|
|
1752
|
+
* opened before it closes), so the array is never read holey.
|
|
1753
|
+
*/
|
|
1754
|
+
this.seqIds = new Array(MAX_DEPTH);
|
|
1755
|
+
/** Number of nested sequences currently open — 0 at the root scope. */
|
|
1756
|
+
this.depth = 0;
|
|
1162
1757
|
this.state = 0 /* Header */;
|
|
1163
|
-
|
|
1164
|
-
|
|
1758
|
+
/**
|
|
1759
|
+
* The terminal-refusal latch: the rejection this stream was stopped by, or
|
|
1760
|
+
* `null` while it is still healthy. **One latch, two codes** — §5.3.1 gives the
|
|
1761
|
+
* rule one implementation, and §6.3 makes both of these rejections terminal:
|
|
1762
|
+
*
|
|
1763
|
+
* - `INVALID_MSG` (§5.2.1: "malformed **regardless of what follows** … no —
|
|
1764
|
+
* terminal") — no later bytes can make malformed input valid;
|
|
1765
|
+
* - `LIMIT_EXCEEDED` (§6.3: "A **terminal**, receiver-local **policy**
|
|
1766
|
+
* rejection") — well-formed bytes the receiver's cap refuses.
|
|
1767
|
+
*
|
|
1768
|
+
* They are latched together and **kept apart by their code**, which is the
|
|
1769
|
+
* distinction §6.3 requires: a limit rejection "**MUST NOT** be reported as
|
|
1770
|
+
* `InvalidMessage`", so the code is re-raised as itself and each stays the
|
|
1771
|
+
* refusal it was.
|
|
1772
|
+
*
|
|
1773
|
+
* Latching is not bookkeeping, it is the terminality: both rejections are
|
|
1774
|
+
* raised *mid-field* — the UTF-8 check inside a payload piece, a cap inside the
|
|
1775
|
+
* count or length callback — so the machine is left at a position the visitor
|
|
1776
|
+
* never finished. Resume it and the refused field's own bytes are re-read as
|
|
1777
|
+
* headers, delivering fields that were never on the wire.
|
|
1778
|
+
*
|
|
1779
|
+
* The rejection is written here by {@link latch} and nowhere else: this
|
|
1780
|
+
* machine's own malformation findings arrive through {@link fail}, and the ones
|
|
1781
|
+
* raised above it — the §6.4.5 UTF-8 verdict, and the receiver caps §6.2.1
|
|
1782
|
+
* keeps out of this codec — arrive as a throw out of a visitor callback, caught
|
|
1783
|
+
* once in {@link push}.
|
|
1784
|
+
*/
|
|
1785
|
+
this.refusal = null;
|
|
1786
|
+
/**
|
|
1787
|
+
* A `DataView` over the chunk currently being fed, and the chunk it addresses —
|
|
1788
|
+
* the **language-forced handle** of CORELIB_PLAN §6.6.2, and the only way
|
|
1789
|
+
* JavaScript lets a reader take an IEEE-754 value from a byte offset. Without it
|
|
1790
|
+
* a float costs four or eight byte loads, a shift ladder and a round trip
|
|
1791
|
+
* through a scratch word; with it, one call.
|
|
1792
|
+
*
|
|
1793
|
+
* It qualifies because it carries no message bytes (it addresses the *caller's*
|
|
1794
|
+
* chunk) and no wire number sizes it (its extent is the chunk's). Built lazily
|
|
1795
|
+
* and only where it pays: a float **array** whose remaining run inside this chunk
|
|
1796
|
+
* clears {@link FP32_HANDLE_MIN} / {@link FP64_HANDLE_MIN}. A scalar float, a
|
|
1797
|
+
* short array, a message with no float at all, and a byte-at-a-time feed each
|
|
1798
|
+
* build none. Once built it is kept while the same chunk is being fed, and it
|
|
1799
|
+
* never leaves this class.
|
|
1800
|
+
*/
|
|
1801
|
+
this.view = null;
|
|
1802
|
+
this.viewOf = null;
|
|
1803
|
+
/** The current field's id. */
|
|
1165
1804
|
this.id = 0;
|
|
1166
|
-
// Resumable varint accumulator, as two unsigned 32-bit halves (vLo / vHi)
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1805
|
+
// Resumable varint accumulator, as two unsigned 32-bit halves (vLo / vHi) plus
|
|
1806
|
+
// the byte count so far. Number-only: a `bigint` is built once, at the end, and
|
|
1807
|
+
// only for full 64-bit *values* (never for ids, lengths or counts).
|
|
1169
1808
|
this.vLo = 0;
|
|
1170
1809
|
this.vHi = 0;
|
|
1171
1810
|
this.vBytes = 0;
|
|
1172
1811
|
this.vComplete = false;
|
|
1173
|
-
|
|
1174
|
-
|
|
1812
|
+
/**
|
|
1813
|
+
* Resumable fp32 / fp64 accumulator, as two little-endian 32-bit words rather
|
|
1814
|
+
* than a byte array: a typed array would be a per-decoder allocation, and two
|
|
1815
|
+
* number fields hold the same 8 bytes. Byte `k` lands in bits `8*k` of `fpLo`
|
|
1816
|
+
* (k < 4) or of `fpHi`, so the pair is already in wire (little-endian) order.
|
|
1817
|
+
*/
|
|
1818
|
+
this.fpLo = 0;
|
|
1819
|
+
this.fpHi = 0;
|
|
1175
1820
|
this.need = 0;
|
|
1176
1821
|
this.have = 0;
|
|
1177
1822
|
// fixlen string/blob streaming
|
|
@@ -1183,98 +1828,249 @@ var SofaBuffers = (function (exports) {
|
|
|
1183
1828
|
this.arrIsFixlen = false;
|
|
1184
1829
|
this.arrCount = 0;
|
|
1185
1830
|
this.arrIndex = 0;
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1831
|
+
/**
|
|
1832
|
+
* The destination the visitor handed over for the array being decoded, or
|
|
1833
|
+
* `null` while no hand-off is in force — which is every array the visitor
|
|
1834
|
+
* declined, and every array at all for a visitor that declares no
|
|
1835
|
+
* {@link Visitor.arrayBulk}.
|
|
1836
|
+
*
|
|
1837
|
+
* **The one reference this machine keeps into the caller's storage between
|
|
1838
|
+
* `feed` calls.** Everything else it holds past a callback is its own (§6.6):
|
|
1839
|
+
* a payload is reported as coordinates into the caller's chunk and never
|
|
1840
|
+
* retained. An array that straddles chunks has to remember where its elements
|
|
1841
|
+
* go, so this lives from the hand-off to `arrayEnd` — and is dropped there,
|
|
1842
|
+
* by {@link endArray}, and by {@link release} for a machine going back to the
|
|
1843
|
+
* pool.
|
|
1844
|
+
*/
|
|
1845
|
+
this.bulk = null;
|
|
1846
|
+
/** Which destination {@link bulk} named; meaningless while it is `null`. */
|
|
1847
|
+
this.bulkMode = 0 /* Values */;
|
|
1848
|
+
/**
|
|
1849
|
+
* The `Uint32Array` over a 64-bit typed destination (see {@link BM.Typed64}),
|
|
1850
|
+
* or over an `f32` destination once it has taken a NaN (see {@link f32StoreWord}).
|
|
1851
|
+
*/
|
|
1852
|
+
this.bulk64 = null;
|
|
1853
|
+
this.root = visitor;
|
|
1854
|
+
this.cur = visitor;
|
|
1855
|
+
this.begin(visitor);
|
|
1189
1856
|
}
|
|
1190
|
-
/**
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1857
|
+
/**
|
|
1858
|
+
* (Re)bind this machine to a visitor, clearing every trace of whatever it
|
|
1859
|
+
* decoded before.
|
|
1860
|
+
*
|
|
1861
|
+
* Constructing a decoder is the one allocating step (§6.6), and the one-shot
|
|
1862
|
+
* {@link decode} would otherwise pay it per message — which on a 37-byte message
|
|
1863
|
+
* is most of the cost. So {@link decode} keeps one machine and re-binds it here;
|
|
1864
|
+
* this method is what makes that indistinguishable from a fresh one.
|
|
1865
|
+
*
|
|
1866
|
+
* **Only the fields a fresh decode can *read* are cleared**, and the list is
|
|
1867
|
+
* exhaustive by construction rather than by inspection: every other field is
|
|
1868
|
+
* written before it is read, on every path that reads it — `id` at each header,
|
|
1869
|
+
* `vLo`/`vHi` by whichever varint reader ran (a fresh accumulation starts from
|
|
1870
|
+
* zero because `vBytes` is 0), `vComplete` by the `varintStep` whose result is
|
|
1871
|
+
* being tested, `fpLo`/`fpHi`/`need` by {@link fpBegin}, `fix*` by
|
|
1872
|
+
* {@link fixlenWord}, `arrKind`/`arrIsFixlen` by {@link dispatch} and
|
|
1873
|
+
* `arrCount`/`arrIndex` by the count word and `bulkMode` by the hand-off the
|
|
1874
|
+
* same word makes (`bulk` *is* cleared: it is the caller's storage, and a
|
|
1875
|
+
* machine that starts a fresh decode must not still be pointing at the last
|
|
1876
|
+
* one's), `seqIds[d]` when the scope at `d`
|
|
1877
|
+
* opens. `have` is the one exception and *is* cleared: the `S.ArrayFp` bulk loop
|
|
1878
|
+
* reads it to decide whether an element is half-arrived.
|
|
1879
|
+
*
|
|
1880
|
+
* `view`/`viewOf` are not cleared either, and deliberately: {@link dataView}
|
|
1881
|
+
* keys the cached handle on the chunk it addresses, so a stale one is rebuilt the
|
|
1882
|
+
* moment a different chunk arrives and reused — correctly — when the same buffer
|
|
1883
|
+
* is decoded again. {@link release} still drops it, which is where it matters:
|
|
1884
|
+
* a pooled machine must not keep the caller's last chunk alive between calls.
|
|
1885
|
+
*
|
|
1886
|
+
* Clearing them all anyway cost 5573 → 5324 Ir/op on `decode: typical message`
|
|
1887
|
+
* (~4.5%), which is what a 21-field wipe costs when the message is 37 bytes. The
|
|
1888
|
+
* invariant is held by `pooled-decoder-state.test.ts`, which aborts a decode in
|
|
1889
|
+
* every construct and then reuses the machine.
|
|
1890
|
+
*/
|
|
1891
|
+
begin(visitor) {
|
|
1892
|
+
this.root = visitor;
|
|
1893
|
+
this.cur = visitor;
|
|
1894
|
+
this.skipFrom = -1;
|
|
1895
|
+
this.depth = 0;
|
|
1896
|
+
this.state = 0 /* Header */;
|
|
1897
|
+
this.refusal = null;
|
|
1898
|
+
this.vBytes = 0;
|
|
1899
|
+
this.have = 0;
|
|
1900
|
+
this.bulk = null;
|
|
1901
|
+
this.bulk64 = null;
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Drop the references a pooled machine would otherwise keep alive: the caller's
|
|
1905
|
+
* visitor, and the handle onto the caller's chunk.
|
|
1906
|
+
*/
|
|
1907
|
+
release() {
|
|
1908
|
+
this.root = SKIP;
|
|
1909
|
+
this.cur = SKIP;
|
|
1910
|
+
this.view = null;
|
|
1911
|
+
this.viewOf = null;
|
|
1912
|
+
this.bulk = null;
|
|
1913
|
+
this.bulk64 = null;
|
|
1914
|
+
}
|
|
1915
|
+
/** Feed `input` to the machine, dispatching to the bound visitor. */
|
|
1916
|
+
push(input) {
|
|
1917
|
+
const latched = this.refusal;
|
|
1918
|
+
if (latched !== null) throw new SofabError(latched.code, latched.message);
|
|
1919
|
+
try {
|
|
1920
|
+
this.run(input);
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
if (this.refusal === null && e instanceof SofabError && (e.code === SofabErrorCode.InvalidMsg || e.code === SofabErrorCode.LimitExceeded || e.code === SofabErrorCode.Argument)) {
|
|
1923
|
+
this.latch(e);
|
|
1924
|
+
}
|
|
1925
|
+
throw e;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* The decode loop itself. {@link push} wraps it, so the terminal-refusal latch
|
|
1930
|
+
* is applied in exactly one place — including to a rejection the visitor raised
|
|
1931
|
+
* — and this stays the plain state machine.
|
|
1932
|
+
*/
|
|
1933
|
+
run(input) {
|
|
1194
1934
|
const n = input.length;
|
|
1935
|
+
let i = 0;
|
|
1195
1936
|
while (i < n) {
|
|
1937
|
+
if (this.state === 0 /* Header */ && this.vBytes === 0) {
|
|
1938
|
+
let h;
|
|
1939
|
+
const hb = input[i];
|
|
1940
|
+
if (hb < 128) {
|
|
1941
|
+
this.vLo = hb;
|
|
1942
|
+
this.vHi = 0;
|
|
1943
|
+
h = i + 1;
|
|
1944
|
+
} else h = this.varintQuick(input, i, n);
|
|
1945
|
+
if (h >= 0) {
|
|
1946
|
+
i = h;
|
|
1947
|
+
const type = this.vLo & 7;
|
|
1948
|
+
const id = this.vHi === 0 ? this.vLo >>> 3 : this.vUpper();
|
|
1949
|
+
if (id > ID_MAX) this.fail(`field id ${id} out of range`);
|
|
1950
|
+
if (type === WireType.SequenceEnd) {
|
|
1951
|
+
this.endSequence();
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
this.id = id;
|
|
1955
|
+
this.cur.fieldBegin?.(id, type);
|
|
1956
|
+
if (type === WireType.Unsigned) {
|
|
1957
|
+
let v;
|
|
1958
|
+
const b0 = i < n ? input[i] : 128;
|
|
1959
|
+
if (b0 < 128) {
|
|
1960
|
+
this.vLo = b0;
|
|
1961
|
+
this.vHi = 0;
|
|
1962
|
+
v = i + 1;
|
|
1963
|
+
} else v = this.varintQuick(input, i, n);
|
|
1964
|
+
if (v < 0) {
|
|
1965
|
+
this.state = 1 /* ScalarU */;
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
i = v;
|
|
1969
|
+
const lo = this.vLo >>> 0;
|
|
1970
|
+
const hi = this.vHi >>> 0;
|
|
1971
|
+
this.cur.unsigned?.(
|
|
1972
|
+
id,
|
|
1973
|
+
hi <= 2097151 ? hi * TWO32 + lo : joinU64(lo, hi),
|
|
1974
|
+
lo,
|
|
1975
|
+
hi
|
|
1976
|
+
);
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (type === WireType.Signed) {
|
|
1980
|
+
let v;
|
|
1981
|
+
const b0 = i < n ? input[i] : 128;
|
|
1982
|
+
if (b0 < 128) {
|
|
1983
|
+
this.vLo = b0;
|
|
1984
|
+
this.vHi = 0;
|
|
1985
|
+
v = i + 1;
|
|
1986
|
+
} else v = this.varintQuick(input, i, n);
|
|
1987
|
+
if (v < 0) {
|
|
1988
|
+
this.state = 2 /* ScalarS */;
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
i = v;
|
|
1992
|
+
const lo = this.vLo >>> 0;
|
|
1993
|
+
const hi = this.vHi >>> 0;
|
|
1994
|
+
const mask = -(lo & 1) >>> 0;
|
|
1995
|
+
this.cur.signed?.(
|
|
1996
|
+
id,
|
|
1997
|
+
this.vSigned(),
|
|
1998
|
+
((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
|
|
1999
|
+
(hi >>> 1 ^ mask) >>> 0
|
|
2000
|
+
);
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
if (type === WireType.Fixlen) {
|
|
2004
|
+
const w = this.varintQuick(input, i, n);
|
|
2005
|
+
if (w < 0) {
|
|
2006
|
+
this.state = 3 /* FixlenWord */;
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
i = this.fixlenWord(input, w, n);
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
this.dispatch(type);
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
1196
2016
|
switch (this.state) {
|
|
1197
2017
|
case 0 /* Header */: {
|
|
1198
2018
|
i = this.varintStep(input, i);
|
|
1199
2019
|
if (!this.vComplete) return;
|
|
1200
|
-
const type = this.
|
|
2020
|
+
const type = this.vLo & 7;
|
|
2021
|
+
const id = this.vUpper();
|
|
2022
|
+
if (id > ID_MAX) this.fail(`field id ${id} out of range`);
|
|
1201
2023
|
if (type === WireType.SequenceEnd) {
|
|
1202
|
-
this.resetVarint();
|
|
1203
2024
|
this.endSequence();
|
|
1204
2025
|
break;
|
|
1205
2026
|
}
|
|
1206
|
-
const id = this.vUpper();
|
|
1207
|
-
this.resetVarint();
|
|
1208
|
-
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
1209
2027
|
this.id = id;
|
|
2028
|
+
this.cur.fieldBegin?.(id, type);
|
|
1210
2029
|
this.dispatch(type);
|
|
1211
2030
|
break;
|
|
1212
2031
|
}
|
|
1213
2032
|
case 1 /* ScalarU */: {
|
|
1214
2033
|
i = this.varintStep(input, i);
|
|
1215
2034
|
if (!this.vComplete) return;
|
|
1216
|
-
const value = this.vUnsigned();
|
|
1217
|
-
this.resetVarint();
|
|
1218
|
-
this.top().unsigned?.(this.id, value);
|
|
1219
2035
|
this.state = 0 /* Header */;
|
|
2036
|
+
this.cur.unsigned?.(this.id, this.vUnsigned(), this.vLo >>> 0, this.vHi >>> 0);
|
|
1220
2037
|
break;
|
|
1221
2038
|
}
|
|
1222
2039
|
case 2 /* ScalarS */: {
|
|
1223
2040
|
i = this.varintStep(input, i);
|
|
1224
2041
|
if (!this.vComplete) return;
|
|
1225
|
-
const value = this.vSigned();
|
|
1226
|
-
this.resetVarint();
|
|
1227
|
-
this.top().signed?.(this.id, value);
|
|
1228
2042
|
this.state = 0 /* Header */;
|
|
2043
|
+
const lo = this.vLo >>> 0;
|
|
2044
|
+
const hi = this.vHi >>> 0;
|
|
2045
|
+
const mask = -(lo & 1) >>> 0;
|
|
2046
|
+
this.cur.signed?.(
|
|
2047
|
+
this.id,
|
|
2048
|
+
this.vSigned(),
|
|
2049
|
+
((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
|
|
2050
|
+
(hi >>> 1 ^ mask) >>> 0
|
|
2051
|
+
);
|
|
1229
2052
|
break;
|
|
1230
2053
|
}
|
|
1231
|
-
case 3 /*
|
|
2054
|
+
case 3 /* FixlenWord */: {
|
|
1232
2055
|
i = this.varintStep(input, i);
|
|
1233
2056
|
if (!this.vComplete) return;
|
|
1234
|
-
|
|
1235
|
-
const len = this.vUpper();
|
|
1236
|
-
this.resetVarint();
|
|
1237
|
-
if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1238
|
-
if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
|
|
1239
|
-
if (sub === FixlenSubtype.String && len > this.maxStringLen) {
|
|
1240
|
-
throw limitExceededError(`string length ${len} exceeds maxStringLen ${this.maxStringLen}`);
|
|
1241
|
-
}
|
|
1242
|
-
if (sub === FixlenSubtype.Blob && len > this.maxBlobLen) {
|
|
1243
|
-
throw limitExceededError(`blob length ${len} exceeds maxBlobLen ${this.maxBlobLen}`);
|
|
1244
|
-
}
|
|
1245
|
-
this.fixSub = sub;
|
|
1246
|
-
this.fixLen = len;
|
|
1247
|
-
this.fixOff = 0;
|
|
1248
|
-
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
1249
|
-
const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
|
|
1250
|
-
if (this.fixLen !== want) throw invalidMsgError("fixlen float length mismatch");
|
|
1251
|
-
this.need = want;
|
|
1252
|
-
this.have = 0;
|
|
1253
|
-
this.state = 4 /* FixlenFp */;
|
|
1254
|
-
} else {
|
|
1255
|
-
if (this.fixLen === 0) {
|
|
1256
|
-
this.emitBytes(input.subarray(0, 0));
|
|
1257
|
-
this.state = 0 /* Header */;
|
|
1258
|
-
} else {
|
|
1259
|
-
this.state = 5 /* FixlenBytes */;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
2057
|
+
i = this.fixlenWord(input, i, n);
|
|
1262
2058
|
break;
|
|
1263
2059
|
}
|
|
1264
2060
|
case 4 /* FixlenFp */: {
|
|
1265
|
-
i = this.fpStep(input, i);
|
|
2061
|
+
i = this.fpStep(input, i, n);
|
|
1266
2062
|
if (this.have < this.need) return;
|
|
1267
|
-
const value = this.fixSub === FixlenSubtype.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
|
|
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);
|
|
1272
2063
|
this.state = 0 /* Header */;
|
|
2064
|
+
if (this.fixSub === FixlenSubtype.Fp32) {
|
|
2065
|
+
this.cur.fp32?.(this.id, fp32FromBits(this.fpLo), this.fpLo >>> 0);
|
|
2066
|
+
} else {
|
|
2067
|
+
this.cur.fp64?.(this.id, fp64FromBits(this.fpLo, this.fpHi));
|
|
2068
|
+
}
|
|
1273
2069
|
break;
|
|
1274
2070
|
}
|
|
1275
2071
|
case 5 /* FixlenBytes */: {
|
|
1276
2072
|
const take = Math.min(n - i, this.fixLen - this.fixOff);
|
|
1277
|
-
this.emitBytes(input
|
|
2073
|
+
this.emitBytes(input, i, i + take);
|
|
1278
2074
|
i += take;
|
|
1279
2075
|
this.fixOff += take;
|
|
1280
2076
|
if (this.fixOff === this.fixLen) this.state = 0 /* Header */;
|
|
@@ -1284,77 +2080,95 @@ var SofaBuffers = (function (exports) {
|
|
|
1284
2080
|
i = this.varintStep(input, i);
|
|
1285
2081
|
if (!this.vComplete) return;
|
|
1286
2082
|
const count = this.vNum();
|
|
1287
|
-
this.
|
|
1288
|
-
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1289
|
-
if (count > this.maxArrayCount) {
|
|
1290
|
-
throw limitExceededError(`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`);
|
|
1291
|
-
}
|
|
2083
|
+
if (count > ARRAY_MAX) this.fail("array count out of range");
|
|
1292
2084
|
this.arrCount = count;
|
|
1293
2085
|
this.arrIndex = 0;
|
|
1294
2086
|
if (this.arrIsFixlen) {
|
|
1295
|
-
this.state =
|
|
2087
|
+
this.state = 8 /* ArrayElemWord */;
|
|
1296
2088
|
} else if (count === 0) {
|
|
1297
|
-
this.
|
|
1298
|
-
this.
|
|
1299
|
-
this.
|
|
2089
|
+
this.cur.arrayBegin?.(this.id, this.arrKind, 0);
|
|
2090
|
+
this.askBulk(this.arrKind, 0);
|
|
2091
|
+
this.endArray();
|
|
1300
2092
|
} else {
|
|
1301
|
-
this.
|
|
1302
|
-
this.
|
|
2093
|
+
this.state = 7 /* ArrayElem */;
|
|
2094
|
+
this.cur.arrayBegin?.(this.id, this.arrKind, count);
|
|
2095
|
+
this.askBulk(this.arrKind, count);
|
|
1303
2096
|
}
|
|
1304
2097
|
break;
|
|
1305
2098
|
}
|
|
1306
|
-
case 7 /*
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
2099
|
+
case 7 /* ArrayElem */: {
|
|
2100
|
+
const count = this.arrCount;
|
|
2101
|
+
const safeEnd = n - VARINT_MAX_BYTES;
|
|
2102
|
+
if (this.bulk !== null) {
|
|
2103
|
+
i = this.arrKind === ArrayKind.Unsigned ? this.uBulkArm(input, i, n, count, safeEnd) : this.sBulkArm(input, i, n, count, safeEnd);
|
|
2104
|
+
break;
|
|
2105
|
+
}
|
|
2106
|
+
let idx = this.arrIndex;
|
|
2107
|
+
if (this.vBytes === 0) {
|
|
2108
|
+
while (idx < count && i <= safeEnd) {
|
|
2109
|
+
i = this.varintFull(input, i);
|
|
2110
|
+
idx++;
|
|
2111
|
+
}
|
|
2112
|
+
this.arrIndex = idx;
|
|
2113
|
+
}
|
|
2114
|
+
if (idx === count) {
|
|
2115
|
+
this.state = 0 /* Header */;
|
|
2116
|
+
this.cur.arrayEnd?.(this.id);
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2119
|
+
if (i >= n) break;
|
|
1316
2120
|
i = this.varintStep(input, i);
|
|
1317
2121
|
if (!this.vComplete) return;
|
|
1318
|
-
const value = this.vSigned();
|
|
1319
|
-
this.resetVarint();
|
|
1320
|
-
this.top().arraySigned?.(this.id, this.arrIndex, value);
|
|
1321
2122
|
this.advanceArray();
|
|
1322
2123
|
break;
|
|
1323
2124
|
}
|
|
1324
|
-
case
|
|
2125
|
+
case 8 /* ArrayElemWord */: {
|
|
1325
2126
|
i = this.varintStep(input, i);
|
|
1326
2127
|
if (!this.vComplete) return;
|
|
1327
|
-
const sub = this.
|
|
2128
|
+
const sub = this.vLo & 7;
|
|
1328
2129
|
const size = this.vUpper();
|
|
1329
|
-
this.resetVarint();
|
|
1330
2130
|
if (sub === FixlenSubtype.Fp32 && size === 4) {
|
|
1331
2131
|
this.arrKind = ArrayKind.Fp32;
|
|
1332
|
-
this.
|
|
2132
|
+
this.fpBegin(4);
|
|
1333
2133
|
} else if (sub === FixlenSubtype.Fp64 && size === 8) {
|
|
1334
2134
|
this.arrKind = ArrayKind.Fp64;
|
|
1335
|
-
this.
|
|
2135
|
+
this.fpBegin(8);
|
|
1336
2136
|
} else {
|
|
1337
|
-
|
|
2137
|
+
this.fail("invalid fixlen array element type");
|
|
1338
2138
|
}
|
|
1339
|
-
this.top().arrayBegin?.(this.id, this.arrKind, this.arrCount);
|
|
1340
2139
|
if (this.arrCount === 0) {
|
|
1341
|
-
this.
|
|
1342
|
-
this.
|
|
2140
|
+
this.cur.arrayBegin?.(this.id, this.arrKind, 0);
|
|
2141
|
+
this.askBulk(this.arrKind, 0);
|
|
2142
|
+
this.endArray();
|
|
1343
2143
|
} else {
|
|
1344
|
-
this.
|
|
1345
|
-
this.
|
|
2144
|
+
this.state = 9 /* ArrayFp */;
|
|
2145
|
+
this.cur.arrayBegin?.(this.id, this.arrKind, this.arrCount);
|
|
2146
|
+
this.askBulk(this.arrKind, this.arrCount);
|
|
1346
2147
|
}
|
|
1347
2148
|
break;
|
|
1348
2149
|
}
|
|
1349
|
-
case
|
|
1350
|
-
|
|
2150
|
+
case 9 /* ArrayFp */: {
|
|
2151
|
+
const count = this.arrCount;
|
|
2152
|
+
const size = this.need;
|
|
2153
|
+
if (this.bulk !== null) {
|
|
2154
|
+
const is32 = this.arrKind === ArrayKind.Fp32;
|
|
2155
|
+
i = this.fpBulkArm(input, i, n, count, size, is32);
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
if (this.have === 0) {
|
|
2159
|
+
const run = Math.min(count - this.arrIndex, (n - i) / size | 0);
|
|
2160
|
+
i += run * size;
|
|
2161
|
+
this.arrIndex += run;
|
|
2162
|
+
}
|
|
2163
|
+
if (this.arrIndex === count) {
|
|
2164
|
+
this.state = 0 /* Header */;
|
|
2165
|
+
this.cur.arrayEnd?.(this.id);
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
if (i >= n) break;
|
|
2169
|
+
i = this.fpStep(input, i, n);
|
|
1351
2170
|
if (this.have < this.need) return;
|
|
1352
|
-
|
|
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);
|
|
1357
|
-
this.have = 0;
|
|
2171
|
+
this.fpBegin(size);
|
|
1358
2172
|
this.advanceArray();
|
|
1359
2173
|
break;
|
|
1360
2174
|
}
|
|
@@ -1362,20 +2176,104 @@ var SofaBuffers = (function (exports) {
|
|
|
1362
2176
|
}
|
|
1363
2177
|
}
|
|
1364
2178
|
/**
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
2179
|
+
* Where the decode stands (§5.2.1), *without* promoting it to an error:
|
|
2180
|
+
* {@link DecodeStatus.Complete} when the stream ended exactly at a field
|
|
2181
|
+
* boundary, {@link DecodeStatus.Incomplete} when it ended inside a field (a
|
|
2182
|
+
* partial varint, an unfinished payload / array, or a still-open nested
|
|
2183
|
+
* sequence). A trailing `Incomplete` is a truncation the caller decides how to
|
|
2184
|
+
* treat (§5.2.4), not an error this machine raises.
|
|
2185
|
+
*
|
|
2186
|
+
* **Only these two, and only on a healthy machine.** This is read in exactly
|
|
2187
|
+
* one place — after a {@link push} that returned — and a push that met a
|
|
2188
|
+
* terminal refusal does not return: it throws, this call is never reached, and
|
|
2189
|
+
* the verdict travels out on the error it threw. So `Invalid` is not a value
|
|
2190
|
+
* this can produce, and neither is the "refused by a cap" state the outcome
|
|
2191
|
+
* triple cannot name (§6.3). Nothing here re-reports a latched refusal, because
|
|
2192
|
+
* nothing needs to: the throw is the whole report, and a report with one
|
|
2193
|
+
* carrier has nothing to drift out of step with.
|
|
2194
|
+
*
|
|
2195
|
+
* The cursor's own answer would be wrong for a refused stream, which is why the
|
|
2196
|
+
* refusal must not fall through to it: a cap is compared in `arrayBegin` /
|
|
2197
|
+
* `fixlenBegin`, raised with the header and the count / length word consumed and
|
|
2198
|
+
* the payload not yet entered — a cursor that reads as a clean field boundary,
|
|
2199
|
+
* and so as `Complete`. {@link push}'s latch is what keeps that unreachable.
|
|
1373
2200
|
*/
|
|
1374
|
-
|
|
1375
|
-
const atBoundary = this.state === 0 /* Header */ && this.vBytes === 0 && this.
|
|
2201
|
+
outcome() {
|
|
2202
|
+
const atBoundary = this.state === 0 /* Header */ && this.vBytes === 0 && this.depth === 0;
|
|
1376
2203
|
return atBoundary ? DecodeStatus.Complete : DecodeStatus.Incomplete;
|
|
1377
2204
|
}
|
|
1378
2205
|
// --- helpers ------------------------------------------------------------
|
|
2206
|
+
/**
|
|
2207
|
+
* Reject the input as malformed: latch the terminal `INVALID` verdict and throw
|
|
2208
|
+
* `INVALID_MSG`. Every malformation *this machine* finds goes through here, so
|
|
2209
|
+
* none of them can be caught and then decoded past — §5.2.1's "no — terminal".
|
|
2210
|
+
* Declared `never` so a call ends control flow exactly like the `throw` it
|
|
2211
|
+
* replaced.
|
|
2212
|
+
*/
|
|
2213
|
+
fail(message) {
|
|
2214
|
+
throw this.latch(invalidMsgError(message));
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Record `e` as the terminal refusal that stopped this stream, and hand it back
|
|
2218
|
+
* to be thrown. The only writer of {@link refusal} — §5.3.1's one
|
|
2219
|
+
* implementation of the rule — for a rejection raised here and for one raised
|
|
2220
|
+
* above this machine and caught in {@link push} alike. The error is stored, not
|
|
2221
|
+
* its text: the code travels with it, so `INVALID_MSG` and `LIMIT_EXCEEDED`
|
|
2222
|
+
* each stay themselves (§6.3).
|
|
2223
|
+
*/
|
|
2224
|
+
latch(e) {
|
|
2225
|
+
this.refusal = e;
|
|
2226
|
+
this.bulk = null;
|
|
2227
|
+
this.bulk64 = null;
|
|
2228
|
+
return e;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Act on a complete `fixlen_word` sitting in the varint accumulator: validate
|
|
2232
|
+
* it, announce the field, and either read the float inline (when its bytes are
|
|
2233
|
+
* in this chunk) or set up the resumable payload state. Shared by the fast lane
|
|
2234
|
+
* and the `S.FixlenWord` arm, so the §4.6 rules exist once.
|
|
2235
|
+
*/
|
|
2236
|
+
fixlenWord(input, i, n) {
|
|
2237
|
+
const sub = this.vLo & 7;
|
|
2238
|
+
const len = this.vUpper();
|
|
2239
|
+
if (sub > FixlenSubtype.Blob) this.fail(`invalid fixlen subtype ${sub}`);
|
|
2240
|
+
if (len > FIXLEN_MAX) this.fail("fixlen length out of range");
|
|
2241
|
+
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
2242
|
+
const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
|
|
2243
|
+
if (len !== want) this.fail("fixlen float length mismatch");
|
|
2244
|
+
this.fixSub = sub;
|
|
2245
|
+
if (n - i >= want) {
|
|
2246
|
+
this.state = 0 /* Header */;
|
|
2247
|
+
const lo = u32le(input, i);
|
|
2248
|
+
if (want === 4) {
|
|
2249
|
+
this.cur.fp32?.(this.id, fp32FromBits(lo), lo);
|
|
2250
|
+
} else {
|
|
2251
|
+
this.cur.fp64?.(this.id, fp64FromBits(lo, u32le(input, i + 4)));
|
|
2252
|
+
}
|
|
2253
|
+
return i + want;
|
|
2254
|
+
}
|
|
2255
|
+
this.fpBegin(want);
|
|
2256
|
+
this.state = 4 /* FixlenFp */;
|
|
2257
|
+
return i;
|
|
2258
|
+
}
|
|
2259
|
+
this.fixSub = sub;
|
|
2260
|
+
this.fixLen = len;
|
|
2261
|
+
this.fixOff = 0;
|
|
2262
|
+
this.cur.fixlenBegin?.(this.id, this.fixSub, len);
|
|
2263
|
+
if (len === 0) {
|
|
2264
|
+
this.emitBytes(input, i, i);
|
|
2265
|
+
this.state = 0 /* Header */;
|
|
2266
|
+
return i;
|
|
2267
|
+
}
|
|
2268
|
+
const take = Math.min(n - i, len);
|
|
2269
|
+
if (take > 0) {
|
|
2270
|
+
this.emitBytes(input, i, i + take);
|
|
2271
|
+
this.fixOff = take;
|
|
2272
|
+
i += take;
|
|
2273
|
+
}
|
|
2274
|
+
this.state = this.fixOff === len ? 0 /* Header */ : 5 /* FixlenBytes */;
|
|
2275
|
+
return i;
|
|
2276
|
+
}
|
|
1379
2277
|
dispatch(type) {
|
|
1380
2278
|
switch (type) {
|
|
1381
2279
|
case WireType.Unsigned:
|
|
@@ -1385,7 +2283,7 @@ var SofaBuffers = (function (exports) {
|
|
|
1385
2283
|
this.state = 2 /* ScalarS */;
|
|
1386
2284
|
break;
|
|
1387
2285
|
case WireType.Fixlen:
|
|
1388
|
-
this.state = 3 /*
|
|
2286
|
+
this.state = 3 /* FixlenWord */;
|
|
1389
2287
|
break;
|
|
1390
2288
|
case WireType.ArrayUnsigned:
|
|
1391
2289
|
this.arrKind = ArrayKind.Unsigned;
|
|
@@ -1401,778 +2299,1293 @@ var SofaBuffers = (function (exports) {
|
|
|
1401
2299
|
this.arrIsFixlen = true;
|
|
1402
2300
|
this.state = 6 /* ArrayCount */;
|
|
1403
2301
|
break;
|
|
1404
|
-
case WireType.SequenceStart:
|
|
1405
|
-
|
|
1406
|
-
throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
1407
|
-
}
|
|
1408
|
-
const child = this.top().sequenceBegin?.(this.id);
|
|
1409
|
-
this.stack.push(child ?? this.top());
|
|
1410
|
-
this.state = 0 /* Header */;
|
|
2302
|
+
case WireType.SequenceStart:
|
|
2303
|
+
this.beginSequence();
|
|
1411
2304
|
break;
|
|
1412
|
-
}
|
|
1413
2305
|
default:
|
|
1414
|
-
|
|
2306
|
+
this.fail(`invalid wire type ${type}`);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Open a nested sequence: offer it to the visitor, note its id, and descend.
|
|
2311
|
+
*
|
|
2312
|
+
* A visitor that answers `false` declines the whole subtree — no callback of
|
|
2313
|
+
* any kind fires inside it, its own {@link Visitor.sequenceEnd} included, and a
|
|
2314
|
+
* scope opened within it is never offered either. The subtree is still *parsed*
|
|
2315
|
+
* (a sequence is framed by markers, not by a length, so its end has to be
|
|
2316
|
+
* found) and every format ceiling still applies; what stops is delivery.
|
|
2317
|
+
*/
|
|
2318
|
+
beginSequence() {
|
|
2319
|
+
const d = this.depth;
|
|
2320
|
+
if (d >= MAX_DEPTH) this.fail(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
|
|
2321
|
+
if (this.cur !== SKIP && this.root.sequenceBegin?.(this.id, d + 1) === false) {
|
|
2322
|
+
this.skipFrom = d;
|
|
2323
|
+
this.cur = SKIP;
|
|
1415
2324
|
}
|
|
2325
|
+
this.seqIds[d] = this.id;
|
|
2326
|
+
this.depth = d + 1;
|
|
2327
|
+
this.state = 0 /* Header */;
|
|
1416
2328
|
}
|
|
1417
2329
|
endSequence() {
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
this.stack.pop();
|
|
2330
|
+
const d = this.depth;
|
|
2331
|
+
if (d === 0) this.fail("unbalanced sequence end");
|
|
1421
2332
|
this.state = 0 /* Header */;
|
|
2333
|
+
const closed = d - 1;
|
|
2334
|
+
this.depth = closed;
|
|
2335
|
+
if (this.skipFrom >= 0) {
|
|
2336
|
+
if (this.skipFrom === closed) {
|
|
2337
|
+
this.skipFrom = -1;
|
|
2338
|
+
this.cur = this.root;
|
|
2339
|
+
}
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
this.root.sequenceEnd?.(this.seqIds[closed], d);
|
|
1422
2343
|
}
|
|
1423
2344
|
advanceArray() {
|
|
1424
|
-
this.arrIndex
|
|
1425
|
-
if (this.arrIndex === this.arrCount) {
|
|
1426
|
-
this.top().arrayEnd?.(this.id);
|
|
2345
|
+
if (++this.arrIndex === this.arrCount) {
|
|
1427
2346
|
this.state = 0 /* Header */;
|
|
2347
|
+
this.cur.arrayEnd?.(this.id);
|
|
1428
2348
|
}
|
|
1429
2349
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
2350
|
+
/**
|
|
2351
|
+
* Close an array delivered through the hand-off: back to the header state, drop
|
|
2352
|
+
* the visitor's destination, announce the end. Every bulk array ends here, so
|
|
2353
|
+
* the reference into the caller's storage cannot outlive the array it was made
|
|
2354
|
+
* for (§6.6; see {@link bulk}).
|
|
2355
|
+
*
|
|
2356
|
+
* The per-element arms keep their own two lines instead of calling this: they
|
|
2357
|
+
* can never hold a destination — the hand-off's arms have already taken over
|
|
2358
|
+
* before they run — and the call showed up on `decode: typical`.
|
|
2359
|
+
*/
|
|
2360
|
+
endArray() {
|
|
2361
|
+
this.state = 0 /* Header */;
|
|
2362
|
+
const t = this.bulk;
|
|
2363
|
+
if (t !== null) this.trimPlain(t, this.arrIndex);
|
|
2364
|
+
this.bulk = null;
|
|
2365
|
+
this.bulk64 = null;
|
|
2366
|
+
this.cur.arrayEnd?.(this.id);
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* Cut a plain-array destination to the `written` elements this array actually
|
|
2370
|
+
* put in it, so a destination reused across arrays — the shape the hand-off
|
|
2371
|
+
* exists for — can never hand back the previous array's tail, and its `length`
|
|
2372
|
+
* always means "elements of *this* array".
|
|
2373
|
+
*
|
|
2374
|
+
* At the end rather than at the hand-off, and that is measured: clearing it up
|
|
2375
|
+
* front turns every element write into a grow (`array<u16>` 181 583 -> 231 815
|
|
2376
|
+
* Ir/op, worse than the per-element path it replaces), where one length store
|
|
2377
|
+
* per array costs nothing. A pre-sized destination — the fast shape, every write
|
|
2378
|
+
* in bounds — is left exactly as it was.
|
|
2379
|
+
*
|
|
2380
|
+
* The typed destinations are not trimmed and cannot be: their length is the
|
|
2381
|
+
* caller's allocation, checked against `count` at the hand-off, and `count` is
|
|
2382
|
+
* how far this fill wrote.
|
|
2383
|
+
*/
|
|
2384
|
+
trimPlain(t, written) {
|
|
2385
|
+
const mode = this.bulkMode;
|
|
2386
|
+
if (mode !== 0 /* Values */ && mode !== 1 /* Values32 */ && mode !== 5 /* Longs */) return;
|
|
2387
|
+
const out = mode === 5 /* Longs */ ? t.longs : t.values;
|
|
2388
|
+
if (out.length > written) out.length = written;
|
|
1434
2389
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
2390
|
+
/**
|
|
2391
|
+
* Offer the bulk hand-off for the array that just began, and resolve the answer
|
|
2392
|
+
* once (CORELIB_PLAN §5.3.1's one implementation, and {@link ArrayTarget}'s
|
|
2393
|
+
* contract): after this, the element loops know which destination to write to
|
|
2394
|
+
* from {@link bulkMode} and re-decide nothing per element.
|
|
2395
|
+
*
|
|
2396
|
+
* Raised after `arrayBegin` and before the first element, so a receiver cap on
|
|
2397
|
+
* the count (§6.2.1) is still compared in `arrayBegin` and a rejected array is
|
|
2398
|
+
* never offered. A visitor that declares no `arrayBulk` pays one short-circuited
|
|
2399
|
+
* optional call per array.
|
|
2400
|
+
*
|
|
2401
|
+
* **An empty array is offered too**, with `count` of 0. There is nothing to
|
|
2402
|
+
* write, but there is something to say: a destination held across fields would
|
|
2403
|
+
* otherwise still be holding the last array's elements, and its length is the
|
|
2404
|
+
* only place this one's emptiness could show up (see {@link trimPlain}).
|
|
2405
|
+
*/
|
|
2406
|
+
askBulk(kind, count) {
|
|
2407
|
+
const t = this.cur.arrayBulk?.(this.id, kind, count) ?? null;
|
|
2408
|
+
if (t === null) {
|
|
2409
|
+
this.bulk = null;
|
|
2410
|
+
this.bulk64 = null;
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
this.bulk64 = null;
|
|
2414
|
+
this.bulkMode = this.resolveTarget(t, kind, count);
|
|
2415
|
+
this.bulk = t;
|
|
1437
2416
|
}
|
|
1438
2417
|
/**
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
2418
|
+
* Which destination `t` names — or an `Argument` refusal if it names none, more
|
|
2419
|
+
* than one, one that contradicts the array's element kind, or one too short to
|
|
2420
|
+
* hold `count` elements.
|
|
2421
|
+
*
|
|
2422
|
+
* These are caller mistakes rather than message verdicts, so they are
|
|
2423
|
+
* `InvalidArgument` (§6.3) and not `INVALID_MSG`: the same bytes decode fine for
|
|
2424
|
+
* a visitor that hands over a destination it can actually fill. Checked once per
|
|
2425
|
+
* array, which is why the fill loops need no guard of their own.
|
|
1442
2426
|
*/
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
hi |= b >> 4 & 7;
|
|
1455
|
-
} else {
|
|
1456
|
-
if (k === 9 && (b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
|
|
1457
|
-
hi |= (b & 127) << 7 * k - 32;
|
|
2427
|
+
resolveTarget(t, kind, count) {
|
|
2428
|
+
if (kind === ArrayKind.Fp32 || kind === ArrayKind.Fp64) {
|
|
2429
|
+
const f = t;
|
|
2430
|
+
const is32 = kind === ArrayKind.Fp32;
|
|
2431
|
+
const dest = is32 ? f.f32 ?? f.bits : f.f64;
|
|
2432
|
+
const named2 = (f.f32 !== void 0 ? 1 : 0) + (f.bits !== void 0 ? 1 : 0) + (f.f64 !== void 0 ? 1 : 0) + (t.bool !== void 0 ? 1 : 0);
|
|
2433
|
+
const want = is32 ? "f32 or bits" : "f64";
|
|
2434
|
+
if (dest === void 0 || named2 !== 1) {
|
|
2435
|
+
throw argumentError(
|
|
2436
|
+
`array ${this.id}: an ${is32 ? "fp32" : "fp64"} array needs exactly the ${want} destination`
|
|
2437
|
+
);
|
|
1458
2438
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
this.vBytes = k;
|
|
1464
|
-
this.vComplete = true;
|
|
1465
|
-
return i;
|
|
2439
|
+
if (dest.length < count) {
|
|
2440
|
+
throw argumentError(
|
|
2441
|
+
`array ${this.id}: ${is32 ? "fp32" : "fp64"} destination holds ${dest.length} of ${count} elements`
|
|
2442
|
+
);
|
|
1466
2443
|
}
|
|
2444
|
+
if (!is32) return 9 /* F64 */;
|
|
2445
|
+
return f.bits !== void 0 ? 8 /* F32Bits */ : 7 /* F32 */;
|
|
1467
2446
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
2447
|
+
const bt = t;
|
|
2448
|
+
if (bt.bool !== void 0) {
|
|
2449
|
+
const other = t;
|
|
2450
|
+
const float = t;
|
|
2451
|
+
if (other.values !== void 0 || other.longs !== void 0 || other.typed !== void 0 || other.lo !== void 0 || other.hi !== void 0 || float.f32 !== void 0 || float.bits !== void 0 || float.f64 !== void 0) {
|
|
2452
|
+
throw argumentError(`array ${this.id}: an array target needs exactly one destination`);
|
|
2453
|
+
}
|
|
2454
|
+
if (kind !== ArrayKind.Unsigned) {
|
|
2455
|
+
throw argumentError(`array ${this.id}: a bool destination needs an unsigned array`);
|
|
2456
|
+
}
|
|
2457
|
+
if (bt.bool.length < count) {
|
|
2458
|
+
throw argumentError(
|
|
2459
|
+
`array ${this.id}: bool destination holds ${bt.bool.length} of ${count} elements`
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
return 4 /* Bool */;
|
|
2463
|
+
}
|
|
2464
|
+
const it = t;
|
|
2465
|
+
const halves = it.lo !== void 0 || it.hi !== void 0;
|
|
2466
|
+
const named = (it.values !== void 0 ? 1 : 0) + (it.longs !== void 0 ? 1 : 0) + (it.typed !== void 0 ? 1 : 0) + (halves ? 1 : 0);
|
|
2467
|
+
if (named !== 1) {
|
|
2468
|
+
throw argumentError(
|
|
2469
|
+
`array ${this.id}: an integer array target needs exactly one destination (values, typed, longs, or lo+hi), got ${named}`
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
if (it.minLo >>> 0 !== it.minLo || it.minHi >>> 0 !== it.minHi || it.maxLo >>> 0 !== it.maxLo || it.maxHi >>> 0 !== it.maxHi) {
|
|
2473
|
+
throw argumentError(
|
|
2474
|
+
`array ${this.id}: the element bound must be four unsigned 32-bit halves`
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
const signed = kind === ArrayKind.Signed;
|
|
2478
|
+
const minHi = signed ? it.minHi | 0 : it.minHi;
|
|
2479
|
+
const maxHi = signed ? it.maxHi | 0 : it.maxHi;
|
|
2480
|
+
if (minHi > maxHi || minHi === maxHi && it.minLo > it.maxLo) {
|
|
2481
|
+
throw argumentError(
|
|
2482
|
+
`array ${this.id}: the element bound is empty (min ${it.minHi}:${it.minLo} > max ${it.maxHi}:${it.maxLo})`
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
if (it.typed !== void 0) {
|
|
2486
|
+
return kind === ArrayKind.Signed ? this.typedModeS(it, it.typed, kind, count) : this.typedModeU(it, it.typed, kind, count);
|
|
2487
|
+
}
|
|
2488
|
+
if (halves) {
|
|
2489
|
+
const lo = it.lo;
|
|
2490
|
+
const hi = it.hi;
|
|
2491
|
+
if (lo === void 0 || hi === void 0) {
|
|
2492
|
+
throw argumentError(`array ${this.id}: the halves destination needs both lo and hi`);
|
|
2493
|
+
}
|
|
2494
|
+
if (lo.length < count || hi.length < count) {
|
|
2495
|
+
throw argumentError(
|
|
2496
|
+
`array ${this.id}: halves destination holds ${Math.min(lo.length, hi.length)} of ${count} elements`
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
return 6 /* Halves */;
|
|
2500
|
+
}
|
|
2501
|
+
if (it.longs !== void 0) {
|
|
2502
|
+
if (!Array.isArray(it.longs)) {
|
|
2503
|
+
throw argumentError(`array ${this.id}: the longs destination must be an Array`);
|
|
2504
|
+
}
|
|
2505
|
+
return 5 /* Longs */;
|
|
2506
|
+
}
|
|
2507
|
+
if (!Array.isArray(it.values)) {
|
|
2508
|
+
throw argumentError(`array ${this.id}: the values destination must be an Array`);
|
|
2509
|
+
}
|
|
2510
|
+
return it.minHi === 0 && it.maxHi === 0 ? 1 /* Values32 */ : 0 /* Values */;
|
|
1473
2511
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
2512
|
+
/** The typed-destination check of {@link resolveTarget} for an unsigned array. */
|
|
2513
|
+
typedModeU(it, d, kind, count) {
|
|
2514
|
+
if (d.length < count) {
|
|
2515
|
+
throw argumentError(
|
|
2516
|
+
`array ${this.id}: typed destination holds ${d.length} of ${count} elements`
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
const cap = typedCapacity(d.constructor);
|
|
2520
|
+
const signed = kind === ArrayKind.Signed;
|
|
2521
|
+
if ((cap === void 0 || cap.max === Number.POSITIVE_INFINITY) && is64(d)) {
|
|
2522
|
+
if (signed !== d instanceof BigInt64Array) {
|
|
2523
|
+
throw argumentError(
|
|
2524
|
+
`array ${this.id}: a 64-bit typed destination must match the array's signedness`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
this.bulk64 = new Uint32Array(d.buffer, d.byteOffset, d.length * 2);
|
|
2528
|
+
return 3 /* Typed64 */;
|
|
2529
|
+
}
|
|
2530
|
+
const minV = signed ? (it.minHi | 0) * TWO32 + it.minLo : it.minHi * TWO32 + it.minLo;
|
|
2531
|
+
const maxV = signed ? (it.maxHi | 0) * TWO32 + it.maxLo : it.maxHi * TWO32 + it.maxLo;
|
|
2532
|
+
if (cap === void 0) {
|
|
2533
|
+
throw argumentError(`array ${this.id}: unsupported typed destination`);
|
|
2534
|
+
}
|
|
2535
|
+
if (minV < cap.min || maxV > cap.max) {
|
|
2536
|
+
throw argumentError(
|
|
2537
|
+
`array ${this.id}: the element bound ${minV}..${maxV} does not fit the typed destination (${cap.min}..${cap.max})`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
return 2 /* Typed */;
|
|
2541
|
+
}
|
|
2542
|
+
/** The signed twin of {@link typedModeU}: textually identical, for its own inline caches. */
|
|
2543
|
+
typedModeS(it, d, kind, count) {
|
|
2544
|
+
if (d.length < count) {
|
|
2545
|
+
throw argumentError(
|
|
2546
|
+
`array ${this.id}: typed destination holds ${d.length} of ${count} elements`
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
const cap = typedCapacity(d.constructor);
|
|
2550
|
+
const signed = kind === ArrayKind.Signed;
|
|
2551
|
+
if ((cap === void 0 || cap.max === Number.POSITIVE_INFINITY) && is64(d)) {
|
|
2552
|
+
if (signed !== d instanceof BigInt64Array) {
|
|
2553
|
+
throw argumentError(
|
|
2554
|
+
`array ${this.id}: a 64-bit typed destination must match the array's signedness`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
this.bulk64 = new Uint32Array(d.buffer, d.byteOffset, d.length * 2);
|
|
2558
|
+
return 3 /* Typed64 */;
|
|
2559
|
+
}
|
|
2560
|
+
const minV = signed ? (it.minHi | 0) * TWO32 + it.minLo : it.minHi * TWO32 + it.minLo;
|
|
2561
|
+
const maxV = signed ? (it.maxHi | 0) * TWO32 + it.maxLo : it.maxHi * TWO32 + it.maxLo;
|
|
2562
|
+
if (cap === void 0) {
|
|
2563
|
+
throw argumentError(`array ${this.id}: unsupported typed destination`);
|
|
2564
|
+
}
|
|
2565
|
+
if (minV < cap.min || maxV > cap.max) {
|
|
2566
|
+
throw argumentError(
|
|
2567
|
+
`array ${this.id}: the element bound ${minV}..${maxV} does not fit the typed destination (${cap.min}..${cap.max})`
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
return 2 /* Typed */;
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* The whole `S.ArrayUElem` arm for an array whose destination was handed over:
|
|
2574
|
+
* drain what this chunk holds, store a straddling tail element, close the array
|
|
2575
|
+
* when it ends. Returns the new read position; the caller's `break` then either
|
|
2576
|
+
* re-enters this state for the next chunk or leaves it, exactly as before.
|
|
2577
|
+
*
|
|
2578
|
+
* **Out of line, for the reason {@link fpDrain} already gives.** Everything
|
|
2579
|
+
* written into {@link push}'s switch is paid for by every decode that never
|
|
2580
|
+
* reaches it, so this arm is three lines at the call site and a method here.
|
|
2581
|
+
* Carrying its body in the switch instead cost `decode: typical` 5092 -> 5099
|
|
2582
|
+
* Ir/op and `decode: u64 array` 705.0k -> 708.5k; giving each bulk arm a state
|
|
2583
|
+
* of its own — which keeps the per-element arms untouched but adds three labels
|
|
2584
|
+
* — cost 5163 on `typical`, worse than either. What is left is what the
|
|
2585
|
+
* hand-off costs a decode that never takes it: 0.34% (5075 -> 5092, 702.5k ->
|
|
2586
|
+
* 705.0k), in the loop's own size.
|
|
2587
|
+
*
|
|
2588
|
+
* Suspension needs no signal of its own: a varint that did not complete consumed
|
|
2589
|
+
* the chunk to its end, so the returned position is `n` and the decode loop exits
|
|
2590
|
+
* on its own condition.
|
|
2591
|
+
*
|
|
2592
|
+
* **One loop per destination**, chosen once (see {@link bulkMode}) rather than
|
|
2593
|
+
* branched on per element: the shapes differ only in what a stored element *is*,
|
|
2594
|
+
* and a loop deciding that per element would give back what the hand-off saves.
|
|
2595
|
+
* `arrIndex` is likewise written once per drain instead of once per element,
|
|
2596
|
+
* which is part of why a bulk fill costs less than *discarding* the same
|
|
2597
|
+
* elements. A refusal mid-loop leaves it at the last committed value; nothing
|
|
2598
|
+
* reads it afterwards, because that verdict is terminal (§5.2.1).
|
|
2599
|
+
*/
|
|
2600
|
+
uBulkArm(input, i, n, count, safeEnd) {
|
|
2601
|
+
const t = this.bulk;
|
|
2602
|
+
const mode = this.bulkMode;
|
|
2603
|
+
const minLo = t.minLo;
|
|
2604
|
+
const minHi = t.minHi;
|
|
2605
|
+
const maxLo = t.maxLo;
|
|
2606
|
+
const maxHi = t.maxHi;
|
|
2607
|
+
for (; ; ) {
|
|
2608
|
+
if (this.vBytes === 0) {
|
|
2609
|
+
let idx = this.arrIndex;
|
|
2610
|
+
if (mode === 1 /* Values32 */) {
|
|
2611
|
+
const out = t.values;
|
|
2612
|
+
while (idx < count && i <= safeEnd) {
|
|
2613
|
+
i = this.varintFull(input, i);
|
|
2614
|
+
const lo = this.vLo >>> 0;
|
|
2615
|
+
if (this.vHi !== 0 || lo < minLo || lo > maxLo) this.outOfBound(idx);
|
|
2616
|
+
out[idx++] = lo;
|
|
2617
|
+
}
|
|
2618
|
+
} else if (mode === 2 /* Typed */) {
|
|
2619
|
+
const out = t.typed;
|
|
2620
|
+
while (idx < count && i <= safeEnd) {
|
|
2621
|
+
i = this.varintFull(input, i);
|
|
2622
|
+
const lo = this.vLo >>> 0;
|
|
2623
|
+
if (this.vHi !== 0 || lo < minLo || lo > maxLo) this.outOfBound(idx);
|
|
2624
|
+
out[idx++] = lo;
|
|
2625
|
+
}
|
|
2626
|
+
} else if (mode === 3 /* Typed64 */) {
|
|
2627
|
+
const out = this.bulk64;
|
|
2628
|
+
while (idx < count && i <= safeEnd) {
|
|
2629
|
+
i = this.varintFull(input, i);
|
|
2630
|
+
const lo = this.vLo >>> 0;
|
|
2631
|
+
const hi = this.vHi >>> 0;
|
|
2632
|
+
if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
|
|
2633
|
+
this.outOfBound(idx);
|
|
2634
|
+
}
|
|
2635
|
+
out[idx * 2 + LO] = lo;
|
|
2636
|
+
out[idx * 2 + HI] = hi;
|
|
2637
|
+
idx++;
|
|
2638
|
+
}
|
|
2639
|
+
} else if (mode === 4 /* Bool */) {
|
|
2640
|
+
const out = this.bulk.bool;
|
|
2641
|
+
while (idx < count && i <= safeEnd) {
|
|
2642
|
+
i = this.varintFull(input, i);
|
|
2643
|
+
out[idx++] = this.vLo !== 0 || this.vHi !== 0 ? 1 : 0;
|
|
2644
|
+
}
|
|
2645
|
+
} else if (mode === 0 /* Values */) {
|
|
2646
|
+
const out = t.values;
|
|
2647
|
+
while (idx < count && i <= safeEnd) {
|
|
2648
|
+
i = this.varintFull(input, i);
|
|
2649
|
+
const lo = this.vLo >>> 0;
|
|
2650
|
+
const hi = this.vHi >>> 0;
|
|
2651
|
+
if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
|
|
2652
|
+
this.outOfBound(idx);
|
|
2653
|
+
}
|
|
2654
|
+
out[idx++] = hi <= 2097151 ? hi * TWO32 + lo : joinU64(lo, hi);
|
|
2655
|
+
}
|
|
2656
|
+
} else if (mode === 5 /* Longs */) {
|
|
2657
|
+
const out = t.longs;
|
|
2658
|
+
while (idx < count && i <= safeEnd) {
|
|
2659
|
+
i = this.varintFull(input, i);
|
|
2660
|
+
const lo = this.vLo >>> 0;
|
|
2661
|
+
const hi = this.vHi >>> 0;
|
|
2662
|
+
if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
|
|
2663
|
+
this.outOfBound(idx);
|
|
2664
|
+
}
|
|
2665
|
+
out[idx++] = new Long(lo, hi);
|
|
2666
|
+
}
|
|
2667
|
+
} else {
|
|
2668
|
+
const oLo = t.lo;
|
|
2669
|
+
const oHi = t.hi;
|
|
2670
|
+
while (idx < count && i <= safeEnd) {
|
|
2671
|
+
i = this.varintFull(input, i);
|
|
2672
|
+
const lo = this.vLo >>> 0;
|
|
2673
|
+
const hi = this.vHi >>> 0;
|
|
2674
|
+
if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
|
|
2675
|
+
this.outOfBound(idx);
|
|
2676
|
+
}
|
|
2677
|
+
oLo[idx] = lo;
|
|
2678
|
+
oHi[idx] = hi;
|
|
2679
|
+
idx++;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
this.arrIndex = idx;
|
|
2683
|
+
}
|
|
2684
|
+
if (this.arrIndex === count) {
|
|
2685
|
+
this.endArray();
|
|
2686
|
+
return i;
|
|
2687
|
+
}
|
|
2688
|
+
if (i >= n) return i;
|
|
2689
|
+
i = this.varintStep(input, i);
|
|
2690
|
+
if (!this.vComplete) return i;
|
|
2691
|
+
this.bulkStoreU(this.arrIndex, this.vLo >>> 0, this.vHi >>> 0);
|
|
2692
|
+
if (++this.arrIndex === count) {
|
|
2693
|
+
this.endArray();
|
|
2694
|
+
return i;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
/** The signed twin of {@link uBulkArm}: the same shape, with the zig-zag undone. */
|
|
2699
|
+
sBulkArm(input, i, n, count, safeEnd) {
|
|
2700
|
+
const t = this.bulk;
|
|
2701
|
+
const mode = this.bulkMode;
|
|
2702
|
+
const minLo = t.minLo;
|
|
2703
|
+
const minHi = t.minHi | 0;
|
|
2704
|
+
const maxLo = t.maxLo;
|
|
2705
|
+
const maxHi = t.maxHi | 0;
|
|
2706
|
+
for (; ; ) {
|
|
2707
|
+
if (this.vBytes === 0) {
|
|
2708
|
+
let idx = this.arrIndex;
|
|
2709
|
+
while (idx < count && i <= safeEnd) {
|
|
2710
|
+
i = this.varintFull(input, i);
|
|
2711
|
+
const raw = this.vLo >>> 0;
|
|
2712
|
+
const rawHi = this.vHi >>> 0;
|
|
2713
|
+
const mask = -(raw & 1) >>> 0;
|
|
2714
|
+
const lo = ((raw >>> 1 | rawHi << 31) >>> 0 ^ mask) >>> 0;
|
|
2715
|
+
const hi = (rawHi >>> 1 ^ mask) >>> 0;
|
|
2716
|
+
const shi = hi | 0;
|
|
2717
|
+
if (shi < minHi || shi === minHi && lo < minLo || shi > maxHi || shi === maxHi && lo > maxLo) {
|
|
2718
|
+
this.outOfBound(idx);
|
|
2719
|
+
}
|
|
2720
|
+
if (mode === 2 /* Typed */) {
|
|
2721
|
+
t.typed[idx] = lo | 0;
|
|
2722
|
+
} else if (mode === 3 /* Typed64 */) {
|
|
2723
|
+
const o = this.bulk64;
|
|
2724
|
+
o[idx * 2 + LO] = lo;
|
|
2725
|
+
o[idx * 2 + HI] = hi;
|
|
2726
|
+
} else if (mode === 0 /* Values */ || mode === 1 /* Values32 */) {
|
|
2727
|
+
let v;
|
|
2728
|
+
if (rawHi <= 2097151) {
|
|
2729
|
+
const r = rawHi * TWO32 + raw;
|
|
2730
|
+
v = r % 2 === 0 ? r / 2 : -(r + 1) / 2;
|
|
2731
|
+
} else {
|
|
2732
|
+
v = joinI64(lo, hi);
|
|
2733
|
+
}
|
|
2734
|
+
t.values[idx] = v;
|
|
2735
|
+
} else if (mode === 5 /* Longs */) {
|
|
2736
|
+
t.longs[idx] = new Long(lo, hi);
|
|
2737
|
+
} else {
|
|
2738
|
+
t.lo[idx] = lo;
|
|
2739
|
+
t.hi[idx] = hi;
|
|
2740
|
+
}
|
|
2741
|
+
idx++;
|
|
2742
|
+
}
|
|
2743
|
+
this.arrIndex = idx;
|
|
2744
|
+
}
|
|
2745
|
+
if (this.arrIndex === count) {
|
|
2746
|
+
this.endArray();
|
|
2747
|
+
return i;
|
|
2748
|
+
}
|
|
2749
|
+
if (i >= n) return i;
|
|
2750
|
+
i = this.varintStep(input, i);
|
|
2751
|
+
if (!this.vComplete) return i;
|
|
2752
|
+
this.bulkStoreS(this.arrIndex, this.vLo >>> 0, this.vHi >>> 0);
|
|
2753
|
+
if (++this.arrIndex === count) {
|
|
2754
|
+
this.endArray();
|
|
2755
|
+
return i;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
/** The float twin of {@link uBulkArm}; a straddling element resumes through {@link fpStep}. */
|
|
2760
|
+
fpBulkArm(input, i, n, count, size, isFp32) {
|
|
2761
|
+
if (this.have === 0 && this.arrIndex < count && n - i >= size) {
|
|
2762
|
+
const t = this.bulk;
|
|
2763
|
+
const run = Math.min(count - this.arrIndex, (n - i) / size | 0);
|
|
2764
|
+
let idx = this.arrIndex;
|
|
2765
|
+
const bitsOut = this.bulkMode === 8 /* F32Bits */ ? t.bits : null;
|
|
2766
|
+
if (run >= (isFp32 ? FP32_HANDLE_MIN : FP64_HANDLE_MIN)) {
|
|
2767
|
+
const dv = this.dataView(input);
|
|
2768
|
+
if (bitsOut !== null) {
|
|
2769
|
+
for (let k = 0; k < run; k++) {
|
|
2770
|
+
bitsOut[idx++] = dv.getUint32(i, true);
|
|
2771
|
+
i += 4;
|
|
2772
|
+
}
|
|
2773
|
+
} else if (isFp32) {
|
|
2774
|
+
const out = t.f32;
|
|
2775
|
+
for (let k = 0; k < run; k++) {
|
|
2776
|
+
const v = dv.getFloat32(i, true);
|
|
2777
|
+
if (v === v) out[idx] = v;
|
|
2778
|
+
else this.f32StoreWord(out, idx, dv.getUint32(i, true));
|
|
2779
|
+
idx++;
|
|
2780
|
+
i += 4;
|
|
2781
|
+
}
|
|
2782
|
+
} else {
|
|
2783
|
+
const out = t.f64;
|
|
2784
|
+
for (let k = 0; k < run; k++) {
|
|
2785
|
+
out[idx++] = dv.getFloat64(i, true);
|
|
2786
|
+
i += 8;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
} else if (bitsOut !== null) {
|
|
2790
|
+
for (let k = 0; k < run; k++) {
|
|
2791
|
+
bitsOut[idx++] = u32le(input, i);
|
|
2792
|
+
i += 4;
|
|
2793
|
+
}
|
|
2794
|
+
} else if (isFp32) {
|
|
2795
|
+
const out = t.f32;
|
|
2796
|
+
for (let k = 0; k < run; k++) {
|
|
2797
|
+
const w = u32le(input, i);
|
|
2798
|
+
if ((w & 2147483647) > 2139095040) this.f32StoreWord(out, idx, w);
|
|
2799
|
+
else out[idx] = fp32FromBits(w);
|
|
2800
|
+
idx++;
|
|
2801
|
+
i += 4;
|
|
2802
|
+
}
|
|
2803
|
+
} else {
|
|
2804
|
+
const out = t.f64;
|
|
2805
|
+
for (let k = 0; k < run; k++) {
|
|
2806
|
+
out[idx++] = fp64FromBits(u32le(input, i), u32le(input, i + 4));
|
|
2807
|
+
i += 8;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
this.arrIndex = idx;
|
|
2811
|
+
}
|
|
2812
|
+
if (this.arrIndex === count) {
|
|
2813
|
+
this.endArray();
|
|
2814
|
+
return i;
|
|
2815
|
+
}
|
|
2816
|
+
if (i >= n) return i;
|
|
2817
|
+
i = this.fpStep(input, i, n);
|
|
2818
|
+
if (this.have < this.need) return i;
|
|
2819
|
+
const lo = this.fpLo;
|
|
2820
|
+
const hi = this.fpHi;
|
|
2821
|
+
this.fpBegin(size);
|
|
2822
|
+
this.bulkStoreFp(this.arrIndex, lo, hi, isFp32);
|
|
2823
|
+
if (++this.arrIndex === count) this.endArray();
|
|
2824
|
+
return i;
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Store an fp32 NaN into a `Float32Array` destination by its wire word (§4.6).
|
|
2828
|
+
* Widening a NaN to a double quiets a signaling one (and an engine may purify
|
|
2829
|
+
* the payload of any NaN it reads from a typed array), so a NaN never goes
|
|
2830
|
+
* through a value: it is written through a word view over the destination's own
|
|
2831
|
+
* storage. Every other fp32 survives the double exactly, which is why the view
|
|
2832
|
+
* is built lazily, on the array's first NaN, and never on the common path —
|
|
2833
|
+
* touching `.buffer` moves a small typed array's storage off the heap. It is
|
|
2834
|
+
* kept in {@link bulk64} for the rest of the array, so the count is one per
|
|
2835
|
+
* array however many NaNs the wire carries (§6.6.2).
|
|
2836
|
+
*/
|
|
2837
|
+
f32StoreWord(out, idx, word) {
|
|
2838
|
+
let w = this.bulk64;
|
|
2839
|
+
if (w === null) w = this.bulk64 = new Uint32Array(out.buffer, out.byteOffset, out.length);
|
|
2840
|
+
w[idx] = word;
|
|
2841
|
+
}
|
|
2842
|
+
/** Store one float element that straddled a chunk boundary (see {@link bulkStoreU}). */
|
|
2843
|
+
bulkStoreFp(idx, lo, hi, isFp32) {
|
|
2844
|
+
const t = this.bulk;
|
|
2845
|
+
if (!isFp32) t.f64[idx] = fp64FromBits(lo, hi);
|
|
2846
|
+
else if (this.bulkMode === 8 /* F32Bits */) t.bits[idx] = lo >>> 0;
|
|
2847
|
+
else if ((lo & 2147483647) > 2139095040) this.f32StoreWord(t.f32, idx, lo >>> 0);
|
|
2848
|
+
else t.f32[idx] = fp32FromBits(lo);
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Store one unsigned element that straddled a chunk boundary — the tail a drain
|
|
2852
|
+
* loop cannot take, resumed from the varint accumulator. At most one per chunk,
|
|
2853
|
+
* so it re-decides the destination rather than duplicating three loops.
|
|
2854
|
+
*/
|
|
2855
|
+
bulkStoreU(idx, lo, hi) {
|
|
2856
|
+
const t = this.bulk;
|
|
2857
|
+
if (hi < t.minHi || hi === t.minHi && lo < t.minLo || hi > t.maxHi || hi === t.maxHi && lo > t.maxLo) {
|
|
2858
|
+
this.outOfBound(idx);
|
|
2859
|
+
}
|
|
2860
|
+
if (this.bulkMode === 0 /* Values */ || this.bulkMode === 1 /* Values32 */) {
|
|
2861
|
+
t.values[idx] = this.vUnsigned();
|
|
2862
|
+
} else if (this.bulkMode === 2 /* Typed */) {
|
|
2863
|
+
t.typed[idx] = lo;
|
|
2864
|
+
} else if (this.bulkMode === 3 /* Typed64 */) {
|
|
2865
|
+
const o = this.bulk64;
|
|
2866
|
+
o[idx * 2 + LO] = lo;
|
|
2867
|
+
o[idx * 2 + HI] = hi;
|
|
2868
|
+
} else if (this.bulkMode === 4 /* Bool */) {
|
|
2869
|
+
this.bulk.bool[idx] = lo !== 0 || hi !== 0 ? 1 : 0;
|
|
2870
|
+
} else if (this.bulkMode === 5 /* Longs */) {
|
|
2871
|
+
t.longs[idx] = new Long(lo, hi);
|
|
2872
|
+
} else {
|
|
2873
|
+
t.lo[idx] = lo;
|
|
2874
|
+
t.hi[idx] = hi;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
/** The signed twin of {@link bulkStoreU}; `raw`/`rawHi` are the zig-zag halves. */
|
|
2878
|
+
bulkStoreS(idx, raw, rawHi) {
|
|
2879
|
+
const t = this.bulk;
|
|
2880
|
+
const mask = -(raw & 1) >>> 0;
|
|
2881
|
+
const lo = ((raw >>> 1 | rawHi << 31) >>> 0 ^ mask) >>> 0;
|
|
2882
|
+
const hi = (rawHi >>> 1 ^ mask) >>> 0;
|
|
2883
|
+
const shi = hi | 0;
|
|
2884
|
+
if (shi < (t.minHi | 0) || shi === (t.minHi | 0) && lo < t.minLo || shi > (t.maxHi | 0) || shi === (t.maxHi | 0) && lo > t.maxLo) {
|
|
2885
|
+
this.outOfBound(idx);
|
|
2886
|
+
}
|
|
2887
|
+
if (this.bulkMode === 0 /* Values */ || this.bulkMode === 1 /* Values32 */) {
|
|
2888
|
+
t.values[idx] = this.vSigned();
|
|
2889
|
+
} else if (this.bulkMode === 2 /* Typed */) t.typed[idx] = lo | 0;
|
|
2890
|
+
else if (this.bulkMode === 3 /* Typed64 */) {
|
|
2891
|
+
const o = this.bulk64;
|
|
2892
|
+
o[idx * 2 + LO] = lo;
|
|
2893
|
+
o[idx * 2 + HI] = hi;
|
|
2894
|
+
} else if (this.bulkMode === 5 /* Longs */) t.longs[idx] = new Long(lo, hi);
|
|
2895
|
+
else {
|
|
2896
|
+
t.lo[idx] = lo;
|
|
2897
|
+
t.hi[idx] = hi;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Refuse an element outside the bound its target declared (§7.3): the message
|
|
2902
|
+
* says a thing the schema does not allow, so it is `INVALID` and terminal, the
|
|
2903
|
+
* same verdict at the same point as a generated per-element guard raises today.
|
|
2904
|
+
* The destination keeps everything written before this element — see
|
|
2905
|
+
* {@link ArrayTarget}.
|
|
2906
|
+
*/
|
|
2907
|
+
outOfBound(index) {
|
|
2908
|
+
const t = this.bulk;
|
|
2909
|
+
if (t !== null) this.trimPlain(t, index);
|
|
2910
|
+
this.fail(`array ${this.id}: element ${index} outside the schema bound`);
|
|
2911
|
+
}
|
|
2912
|
+
/**
|
|
2913
|
+
* Report one payload piece: the caller's own fed chunk plus the coordinates of
|
|
2914
|
+
* the piece inside it (§6.6.3). No view is created — this decoder allocates
|
|
2915
|
+
* nothing (§6.6) and exposes no borrowed slice of its own (§6.7); `src` is the
|
|
2916
|
+
* very array the caller passed to `feed`, so whoever wants the bytes copies
|
|
2917
|
+
* them out of memory it already owns, during the call.
|
|
2918
|
+
*/
|
|
2919
|
+
emitBytes(src, start, end) {
|
|
2920
|
+
const v = this.cur;
|
|
2921
|
+
if (this.fixSub === FixlenSubtype.String) {
|
|
2922
|
+
v.string?.(this.id, this.fixLen, this.fixOff, src, start, end);
|
|
2923
|
+
} else {
|
|
2924
|
+
v.blob?.(this.id, this.fixLen, this.fixOff, src, start, end);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Consume varint bytes from `input` at `i` into the {@link vLo} / {@link vHi}
|
|
2929
|
+
* accumulator, resuming across chunk boundaries; sets {@link vComplete} when a
|
|
2930
|
+
* terminator byte arrives. Number-only — no per-byte `bigint`.
|
|
2931
|
+
*/
|
|
2932
|
+
varintStep(input, i) {
|
|
2933
|
+
if (this.vBytes === 0) {
|
|
2934
|
+
const n0 = input.length;
|
|
2935
|
+
const b0 = input[i];
|
|
2936
|
+
if (b0 < 128) {
|
|
2937
|
+
this.vLo = b0;
|
|
2938
|
+
this.vHi = 0;
|
|
2939
|
+
this.vComplete = true;
|
|
2940
|
+
return i + 1;
|
|
2941
|
+
}
|
|
2942
|
+
if (i + 1 < n0) {
|
|
2943
|
+
const b1 = input[i + 1];
|
|
2944
|
+
if (b1 < 128) {
|
|
2945
|
+
this.vLo = b0 & 127 | b1 << 7;
|
|
2946
|
+
this.vHi = 0;
|
|
2947
|
+
this.vComplete = true;
|
|
2948
|
+
return i + 2;
|
|
2949
|
+
}
|
|
2950
|
+
if (i + 2 < n0) {
|
|
2951
|
+
const b2 = input[i + 2];
|
|
2952
|
+
if (b2 < 128) {
|
|
2953
|
+
this.vLo = b0 & 127 | (b1 & 127) << 7 | b2 << 14;
|
|
2954
|
+
this.vHi = 0;
|
|
2955
|
+
this.vComplete = true;
|
|
2956
|
+
return i + 3;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
if (n0 - i >= VARINT_MAX_BYTES) return this.varintFull(input, i);
|
|
2961
|
+
}
|
|
2962
|
+
const k0 = this.vBytes;
|
|
2963
|
+
let lo = k0 === 0 ? 0 : this.vLo;
|
|
2964
|
+
let hi = k0 === 0 ? 0 : this.vHi;
|
|
2965
|
+
let k = k0;
|
|
2966
|
+
const n = input.length;
|
|
2967
|
+
while (i < n) {
|
|
2968
|
+
if (k >= VARINT_MAX_BYTES) this.fail("varint overflow");
|
|
2969
|
+
const b = input[i++];
|
|
2970
|
+
if (k < 4) lo |= (b & 127) << 7 * k;
|
|
2971
|
+
else if (k === 4) {
|
|
2972
|
+
lo |= (b & 15) << 28;
|
|
2973
|
+
hi |= b >> 4 & 7;
|
|
2974
|
+
} else {
|
|
2975
|
+
if (k === 9 && (b & 127) >> 1 !== 0) this.fail("varint overflow");
|
|
2976
|
+
hi |= (b & 127) << 7 * k - 32;
|
|
2977
|
+
}
|
|
2978
|
+
k++;
|
|
2979
|
+
if ((b & 128) === 0) {
|
|
2980
|
+
this.vLo = lo;
|
|
2981
|
+
this.vHi = hi;
|
|
2982
|
+
this.vBytes = 0;
|
|
2983
|
+
this.vComplete = true;
|
|
2984
|
+
return i;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
if (k >= VARINT_MAX_BYTES) this.fail("varint overflow");
|
|
2988
|
+
this.vLo = lo;
|
|
2989
|
+
this.vHi = hi;
|
|
2990
|
+
this.vBytes = k;
|
|
1478
2991
|
this.vComplete = false;
|
|
2992
|
+
return i;
|
|
1479
2993
|
}
|
|
1480
|
-
/**
|
|
1481
|
-
|
|
1482
|
-
|
|
2994
|
+
/**
|
|
2995
|
+
* Decode the varint at `i` **if that needs no resume bookkeeping**, returning
|
|
2996
|
+
* the index past it, or `-1` when it does (leaving the accumulator untouched,
|
|
2997
|
+
* so the resumable ladder starts the word from scratch).
|
|
2998
|
+
*
|
|
2999
|
+
* Three cases, in the order they are worth testing:
|
|
3000
|
+
*
|
|
3001
|
+
* * ten bytes in hand — the unrolled {@link varintFull}, no bounds checks at
|
|
3002
|
+
* all;
|
|
3003
|
+
* * a one-byte varint — every small id, length, count and scalar;
|
|
3004
|
+
* * a two-byte one — every id past 15, and mid-sized lengths and counts.
|
|
3005
|
+
*
|
|
3006
|
+
* Past that the word may straddle the chunk, and only the ladder can carry the
|
|
3007
|
+
* state across `feed` calls.
|
|
3008
|
+
*/
|
|
3009
|
+
varintQuick(input, i, n) {
|
|
3010
|
+
if (n - i >= VARINT_MAX_BYTES) return this.varintFull(input, i);
|
|
3011
|
+
if (i >= n) return -1;
|
|
3012
|
+
const b0 = input[i];
|
|
3013
|
+
if (b0 < 128) return this.setVarint(b0, 0, i + 1);
|
|
3014
|
+
if (i + 1 < n) {
|
|
3015
|
+
const b1 = input[i + 1];
|
|
3016
|
+
if (b1 < 128) return this.setVarint(b0 & 127 | b1 << 7, 0, i + 2);
|
|
3017
|
+
if (i + 2 < n) {
|
|
3018
|
+
const b2 = input[i + 2];
|
|
3019
|
+
if (b2 < 128) {
|
|
3020
|
+
return this.setVarint(b0 & 127 | (b1 & 127) << 7 | b2 << 14, 0, i + 3);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return -1;
|
|
1483
3025
|
}
|
|
1484
3026
|
/**
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
3027
|
+
* Decode one varint that is **guaranteed** to lie wholly within `input` — the
|
|
3028
|
+
* caller has checked that {@link VARINT_MAX_BYTES} bytes remain — into
|
|
3029
|
+
* {@link vLo} / {@link vHi}, returning the index past it.
|
|
3030
|
+
*
|
|
3031
|
+
* Unrolled and branch-per-byte: with the bytes known to be present there is no
|
|
3032
|
+
* bounds check, no resume counter and no `k`-dispatch per byte, which is the
|
|
3033
|
+
* whole per-byte cost of the resumable loop above. Reports the same `>64-bit`
|
|
3034
|
+
* overflow as that loop.
|
|
3035
|
+
*/
|
|
3036
|
+
varintFull(input, i) {
|
|
3037
|
+
let b = input[i];
|
|
3038
|
+
let lo = b & 127;
|
|
3039
|
+
let hi = 0;
|
|
3040
|
+
if (b < 128) return this.setVarint(lo, 0, i + 1);
|
|
3041
|
+
b = input[i + 1];
|
|
3042
|
+
lo |= (b & 127) << 7;
|
|
3043
|
+
if (b < 128) return this.setVarint(lo, 0, i + 2);
|
|
3044
|
+
b = input[i + 2];
|
|
3045
|
+
lo |= (b & 127) << 14;
|
|
3046
|
+
if (b < 128) return this.setVarint(lo, 0, i + 3);
|
|
3047
|
+
b = input[i + 3];
|
|
3048
|
+
lo |= (b & 127) << 21;
|
|
3049
|
+
if (b < 128) return this.setVarint(lo, 0, i + 4);
|
|
3050
|
+
b = input[i + 4];
|
|
3051
|
+
lo |= (b & 15) << 28;
|
|
3052
|
+
hi = b >> 4 & 7;
|
|
3053
|
+
if (b < 128) return this.setVarint(lo, hi, i + 5);
|
|
3054
|
+
b = input[i + 5];
|
|
3055
|
+
hi |= (b & 127) << 3;
|
|
3056
|
+
if (b < 128) return this.setVarint(lo, hi, i + 6);
|
|
3057
|
+
b = input[i + 6];
|
|
3058
|
+
hi |= (b & 127) << 10;
|
|
3059
|
+
if (b < 128) return this.setVarint(lo, hi, i + 7);
|
|
3060
|
+
b = input[i + 7];
|
|
3061
|
+
hi |= (b & 127) << 17;
|
|
3062
|
+
if (b < 128) return this.setVarint(lo, hi, i + 8);
|
|
3063
|
+
b = input[i + 8];
|
|
3064
|
+
hi |= (b & 127) << 24;
|
|
3065
|
+
if (b < 128) return this.setVarint(lo, hi, i + 9);
|
|
3066
|
+
b = input[i + 9];
|
|
3067
|
+
if ((b & 127) >> 1 !== 0) this.fail("varint overflow");
|
|
3068
|
+
hi |= (b & 127) << 31;
|
|
3069
|
+
if (b < 128) return this.setVarint(lo, hi, i + 10);
|
|
3070
|
+
this.fail("varint overflow");
|
|
3071
|
+
}
|
|
3072
|
+
/** Publish a fully-decoded varint and the cursor past it (see {@link varintFull}). */
|
|
3073
|
+
setVarint(lo, hi, i) {
|
|
3074
|
+
this.vLo = lo;
|
|
3075
|
+
this.vHi = hi;
|
|
3076
|
+
this.vBytes = 0;
|
|
3077
|
+
this.vComplete = true;
|
|
3078
|
+
return i;
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* The accumulated varint as an unsigned value, number-first: a `number` when it
|
|
3082
|
+
* fits exactly (`≤ 2^53-1`, which covers all ids, u8..u32 and small u64s), a
|
|
3083
|
+
* `bigint` only beyond that.
|
|
1488
3084
|
*/
|
|
1489
3085
|
vUnsigned() {
|
|
1490
3086
|
const hi = this.vHi >>> 0;
|
|
1491
|
-
return hi <= 2097151 ? hi *
|
|
3087
|
+
return hi <= 2097151 ? hi * TWO32 + (this.vLo >>> 0) : joinU64(this.vLo >>> 0, hi);
|
|
1492
3088
|
}
|
|
1493
3089
|
/** The accumulated zig-zag varint as a signed value, number-first (see {@link vUnsigned}). */
|
|
1494
3090
|
vSigned() {
|
|
1495
3091
|
const hi = this.vHi >>> 0;
|
|
1496
3092
|
if (hi <= 2097151) {
|
|
1497
|
-
const r = hi *
|
|
3093
|
+
const r = hi * TWO32 + (this.vLo >>> 0);
|
|
1498
3094
|
return r % 2 === 0 ? r / 2 : -(r + 1) / 2;
|
|
1499
3095
|
}
|
|
1500
|
-
|
|
3096
|
+
const lo = this.vLo >>> 0;
|
|
3097
|
+
const mask = -(lo & 1) >>> 0;
|
|
3098
|
+
return joinI64(
|
|
3099
|
+
((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
|
|
3100
|
+
(hi >>> 1 ^ mask) >>> 0
|
|
3101
|
+
);
|
|
1501
3102
|
}
|
|
1502
|
-
/**
|
|
3103
|
+
/**
|
|
3104
|
+
* The accumulated varint as a JS number — exact for ids/lengths/counts.
|
|
3105
|
+
*
|
|
3106
|
+
* The `>>> 0` is load-bearing: `vHi` is accumulated with 32-bit bitwise ops, so
|
|
3107
|
+
* bit 63 of the varint lands on its sign bit and the value reads back negative,
|
|
3108
|
+
* sliding past the `count > ARRAY_MAX` guard.
|
|
3109
|
+
*/
|
|
1503
3110
|
vNum() {
|
|
1504
|
-
return this.vHi *
|
|
1505
|
-
}
|
|
1506
|
-
/** The accumulated varint's low 3 tag bits (the wire type / fixlen subtype). */
|
|
1507
|
-
vTag() {
|
|
1508
|
-
return this.vLo & 7;
|
|
3111
|
+
return (this.vHi >>> 0) * TWO32 + (this.vLo >>> 0);
|
|
1509
3112
|
}
|
|
1510
3113
|
/** The accumulated varint with its low 3 tag bits stripped (`value >> 3`). */
|
|
1511
3114
|
vUpper() {
|
|
1512
|
-
return (this.vHi >>> 0) * (
|
|
3115
|
+
return (this.vHi >>> 0) * (TWO32 / 8) + (this.vLo >>> 3);
|
|
1513
3116
|
}
|
|
1514
|
-
/**
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
3117
|
+
/**
|
|
3118
|
+
* The handle over `input`, built on demand and reused while the same chunk is
|
|
3119
|
+
* being fed (see {@link view}).
|
|
3120
|
+
*/
|
|
3121
|
+
dataView(input) {
|
|
3122
|
+
if (this.viewOf !== input) {
|
|
3123
|
+
this.view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
3124
|
+
this.viewOf = input;
|
|
1518
3125
|
}
|
|
3126
|
+
return this.view;
|
|
3127
|
+
}
|
|
3128
|
+
/** Accumulate up to `need` raw float bytes into {@link fpLo} / {@link fpHi}. */
|
|
3129
|
+
fpStep(input, i, n) {
|
|
3130
|
+
let have = this.have;
|
|
3131
|
+
const need = this.need;
|
|
3132
|
+
while (have < need && i < n) {
|
|
3133
|
+
const b = input[i++];
|
|
3134
|
+
if (have < 4) this.fpLo |= b << (have << 3);
|
|
3135
|
+
else this.fpHi |= b << (have - 4 << 3);
|
|
3136
|
+
have++;
|
|
3137
|
+
}
|
|
3138
|
+
this.have = have;
|
|
1519
3139
|
return i;
|
|
1520
3140
|
}
|
|
3141
|
+
/** Start a fresh fp accumulation of `need` bytes. */
|
|
3142
|
+
fpBegin(need) {
|
|
3143
|
+
this.need = need;
|
|
3144
|
+
this.have = 0;
|
|
3145
|
+
this.fpLo = 0;
|
|
3146
|
+
this.fpHi = 0;
|
|
3147
|
+
}
|
|
1521
3148
|
};
|
|
3149
|
+
function u32le(b, i) {
|
|
3150
|
+
return (b[i] | b[i + 1] << 8 | b[i + 2] << 16 | b[i + 3] << 24) >>> 0;
|
|
3151
|
+
}
|
|
1522
3152
|
|
|
1523
3153
|
// src/decode/istream.ts
|
|
1524
3154
|
var IStream = class {
|
|
1525
3155
|
/**
|
|
1526
|
-
* @param
|
|
1527
|
-
*
|
|
1528
|
-
* (`LIMIT_EXCEEDED`) from {@link feed}, at the offending field's header and
|
|
1529
|
-
* before any of its payload is streamed to the visitor. Omit for no caps.
|
|
1530
|
-
*/
|
|
1531
|
-
constructor(limits) {
|
|
1532
|
-
this.state = new DecoderState(limits);
|
|
1533
|
-
}
|
|
1534
|
-
/**
|
|
1535
|
-
* Feed a chunk of bytes, dispatching decoded fields to `visitor`. Throws
|
|
1536
|
-
* {@link SofabError} (`INVALID_MSG`) only if the bytes are *malformed*;
|
|
1537
|
-
* running out of bytes mid-field is not an error — it simply suspends until
|
|
1538
|
-
* the next chunk (see {@link end}).
|
|
3156
|
+
* @param visitor The field handler this stream drives, for its whole life — and
|
|
3157
|
+
* the layer that holds the receiver caps, if any (§6.2.1; see the class doc).
|
|
1539
3158
|
*/
|
|
1540
|
-
|
|
1541
|
-
this.state
|
|
3159
|
+
constructor(visitor) {
|
|
3160
|
+
this.state = new DecoderState(visitor);
|
|
1542
3161
|
}
|
|
1543
3162
|
/**
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
* nested sequence).
|
|
3163
|
+
* Feed a chunk of bytes, dispatching decoded fields to the bound visitor, and
|
|
3164
|
+
* **return** where the decode stands after them (§5.2.1):
|
|
3165
|
+
* {@link DecodeStatus.Complete} when they end exactly at a field boundary,
|
|
3166
|
+
* {@link DecodeStatus.Incomplete} when they end *inside* a field (a partial
|
|
3167
|
+
* varint, an unfinished payload / array, or a still-open nested sequence).
|
|
3168
|
+
* Running out of bytes mid-field is not an error — the decode merely suspends
|
|
3169
|
+
* until the next chunk, and the caller owns end-of-input.
|
|
1549
3170
|
*
|
|
1550
|
-
*
|
|
1551
|
-
*
|
|
1552
|
-
*
|
|
1553
|
-
*
|
|
3171
|
+
* **This call is the only place the answer is.** There is no finish / finalize
|
|
3172
|
+
* step (§5.2.4) and no status accessor: what this returns, or throws, is the
|
|
3173
|
+
* whole of what the stream has to say, so a caller is never one question short
|
|
3174
|
+
* after it and never has two answers to reconcile. Feeding an empty chunk
|
|
3175
|
+
* re-reads the same value without consuming anything, for a caller that wants
|
|
3176
|
+
* the outcome again without holding on to it.
|
|
3177
|
+
*
|
|
3178
|
+
* The chunk is borrowed **only for the duration of this call** (§6.0): once it
|
|
3179
|
+
* returns, the caller may reuse, overwrite or free that memory, and the decoded
|
|
3180
|
+
* message is unaffected — the decoder retains nothing that points into it.
|
|
3181
|
+
*
|
|
3182
|
+
* `INVALID` travels on the error channel — this port's idiomatic surfacing of
|
|
3183
|
+
* it: *malformed* bytes throw {@link SofabError} (`INVALID_MSG`) instead of
|
|
3184
|
+
* returning a status, which is why the return type names only the other two.
|
|
3185
|
+
* That verdict is **terminal** (§5.2.1): the stream latches it, so a caller that
|
|
3186
|
+
* catches the throw and feeds on gets the same error again from every later
|
|
3187
|
+
* call — no further byte is consumed and no visitor method is invoked. A caller
|
|
3188
|
+
* that caught it already holds the verdict, in the code on the error it caught.
|
|
3189
|
+
*
|
|
3190
|
+
* A receiver-limit rejection (`LIMIT_EXCEEDED`, §6.2.1) travels the same
|
|
3191
|
+
* channel — thrown out of the visitor callback that compared the cap — but it
|
|
3192
|
+
* is **not** the `INVALID` outcome and never becomes one: the bytes are
|
|
3193
|
+
* well-formed and the same message decodes under a looser cap, so it is a
|
|
3194
|
+
* policy rejection (§6.2.1, §6.3). The two stay distinguishable by their code,
|
|
3195
|
+
* which is what §6.3 requires; §6.3 leaves the surfacing open between "a fourth
|
|
3196
|
+
* decode outcome" and "a terminal failure carrying the `LimitExceeded` code on
|
|
3197
|
+
* the error channel", and this port takes the second. **Terminal** is the other
|
|
3198
|
+
* half of that sentence and holds exactly as it does for `INVALID`: the stream
|
|
3199
|
+
* latches the rejection, so every later call re-throws it under the same code,
|
|
3200
|
+
* consumes no byte and drives no visitor method. It is *only* on the error
|
|
3201
|
+
* channel — the three-valued outcome has no value for "valid, but more than I am
|
|
3202
|
+
* configured to accept", so there is nothing about it to read back as a status,
|
|
3203
|
+
* and nothing that has to be kept in step with the throw.
|
|
1554
3204
|
*/
|
|
1555
|
-
|
|
1556
|
-
|
|
3205
|
+
feed(chunk) {
|
|
3206
|
+
this.state.push(chunk);
|
|
3207
|
+
return this.state.outcome();
|
|
1557
3208
|
}
|
|
1558
3209
|
};
|
|
1559
|
-
function decode(bytes, visitor
|
|
1560
|
-
|
|
3210
|
+
function decode(bytes, visitor) {
|
|
3211
|
+
const state = pooled ?? new DecoderState();
|
|
3212
|
+
pooled = null;
|
|
3213
|
+
try {
|
|
3214
|
+
state.begin(visitor);
|
|
3215
|
+
state.push(bytes);
|
|
3216
|
+
if (state.outcome() !== DecodeStatus.Complete) {
|
|
3217
|
+
throw incompleteError("truncated message: input ends inside a field");
|
|
3218
|
+
}
|
|
3219
|
+
} finally {
|
|
3220
|
+
state.release();
|
|
3221
|
+
pooled = state;
|
|
3222
|
+
}
|
|
1561
3223
|
}
|
|
3224
|
+
var pooled = null;
|
|
1562
3225
|
|
|
1563
|
-
// src/decode/
|
|
1564
|
-
var TWO323 = 4294967296;
|
|
3226
|
+
// src/decode/text.ts
|
|
1565
3227
|
var _utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
1566
|
-
var
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
* fixlen field whose subtype word is truncated away).
|
|
1577
|
-
*
|
|
1578
|
-
* The four fixlen subtypes (`fp32`, `fp64`, `string`, `blob`) all share one
|
|
1579
|
-
* {@link wire} type, so {@link wire} alone cannot separate them. This is the
|
|
1580
|
-
* companion accessor that can: a generated guard reads it right after
|
|
1581
|
-
* {@link readHeader} to skip a field whose delivered subtype contradicts the
|
|
1582
|
-
* schema (MESSAGE_SPEC §7.3), exactly as it already does on {@link wire} for
|
|
1583
|
-
* the other kinds:
|
|
1584
|
-
*
|
|
1585
|
-
* ```ts
|
|
1586
|
-
* case 9: if (c.wire !== WireType.Fixlen || c.fixSub !== FixlenSubtype.Fp64) {
|
|
1587
|
-
* c.skip(c.wire); break;
|
|
1588
|
-
* } o.somefp64 = c.readFp64(); break;
|
|
1589
|
-
* ```
|
|
1590
|
-
*
|
|
1591
|
-
* It is *peeked* — the subtype word is not consumed — so the matching typed
|
|
1592
|
-
* reader (or {@link skip}) still reads and validates it, and a malformed or
|
|
1593
|
-
* truncated word surfaces `INVALID` / `INCOMPLETE` there as before.
|
|
1594
|
-
*/
|
|
1595
|
-
this.fixSub = -1;
|
|
1596
|
-
this.p = 0;
|
|
1597
|
-
// Last varint, as two unsigned 32-bit halves (see readVarint).
|
|
1598
|
-
this.lo = 0;
|
|
1599
|
-
this.hi = 0;
|
|
1600
|
-
// Number of nested sequences currently open (0 = root). Incremented when
|
|
1601
|
-
// readHeader accepts a SequenceStart, decremented when it consumes the matching
|
|
1602
|
-
// SequenceEnd (or when skip() discards a whole nested sequence). Lets the pull
|
|
1603
|
-
// parser tell a root-level dangling sequence-end (INVALID) and an unclosed
|
|
1604
|
-
// sequence at end-of-buffer (INCOMPLETE) apart from a clean boundary.
|
|
1605
|
-
this.depth = 0;
|
|
1606
|
-
this.buf = buf;
|
|
1607
|
-
this.n = buf.length;
|
|
1608
|
-
this.view = new DataView(buf.buffer, buf.byteOffset, buf.length);
|
|
1609
|
-
this.maxArrayCount = limits?.maxArrayCount ?? Infinity;
|
|
1610
|
-
this.maxStringLen = limits?.maxStringLen ?? Infinity;
|
|
1611
|
-
this.maxBlobLen = limits?.maxBlobLen ?? Infinity;
|
|
1612
|
-
}
|
|
1613
|
-
/**
|
|
1614
|
-
* Advance to the next field header. Returns `true` and sets {@link id} /
|
|
1615
|
-
* {@link wire} when a field follows; returns `false` — consuming the marker —
|
|
1616
|
-
* at the end of the buffer *or* at the sequence-end that closes the sequence
|
|
1617
|
-
* this decoder is reading. So a generated per-type decoder loops uniformly:
|
|
1618
|
-
*
|
|
1619
|
-
* ```ts
|
|
1620
|
-
* while (c.readHeader()) {
|
|
1621
|
-
* switch (c.id) {
|
|
1622
|
-
* case 4: this.u32 = Number(c.readUnsigned()); break;
|
|
1623
|
-
* case 10: this.child = Child.decodeFrom(c); break; // nested sequence
|
|
1624
|
-
* default: c.skip(c.wire); break; // unknown field
|
|
1625
|
-
* }
|
|
1626
|
-
* }
|
|
1627
|
-
* ```
|
|
1628
|
-
*
|
|
1629
|
-
* At the root the loop ends at end-of-buffer; inside a nested sequence it ends
|
|
1630
|
-
* at the matching {@link WireType.SequenceEnd} (which is consumed). A field
|
|
1631
|
-
* whose id is out of range throws {@link SofabError} (`INVALID_MSG`).
|
|
1632
|
-
*/
|
|
1633
|
-
readHeader() {
|
|
1634
|
-
if (this.p >= this.n) {
|
|
1635
|
-
if (this.depth > 0) {
|
|
1636
|
-
throw incompleteError("truncated message: unbalanced sequence");
|
|
3228
|
+
var FLAT_MAX = 16;
|
|
3229
|
+
function decodeUtf8(buf, start = 0, end = buf.length) {
|
|
3230
|
+
const n = end - start;
|
|
3231
|
+
if (n <= 0) return "";
|
|
3232
|
+
if (n <= FLAT_MAX) {
|
|
3233
|
+
let ascii = true;
|
|
3234
|
+
for (let k = start; k < end; k++) {
|
|
3235
|
+
if (buf[k] & 128) {
|
|
3236
|
+
ascii = false;
|
|
3237
|
+
break;
|
|
1637
3238
|
}
|
|
1638
|
-
return false;
|
|
1639
3239
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
3240
|
+
if (ascii) {
|
|
3241
|
+
switch (n) {
|
|
3242
|
+
case 1:
|
|
3243
|
+
return String.fromCharCode(buf[start]);
|
|
3244
|
+
case 2:
|
|
3245
|
+
return String.fromCharCode(buf[start], buf[start + 1]);
|
|
3246
|
+
case 3:
|
|
3247
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2]);
|
|
3248
|
+
case 4:
|
|
3249
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3]);
|
|
3250
|
+
case 5:
|
|
3251
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4]);
|
|
3252
|
+
case 6:
|
|
3253
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5]);
|
|
3254
|
+
case 7:
|
|
3255
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6]);
|
|
3256
|
+
case 8:
|
|
3257
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7]);
|
|
3258
|
+
case 9:
|
|
3259
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8]);
|
|
3260
|
+
case 10:
|
|
3261
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9]);
|
|
3262
|
+
case 11:
|
|
3263
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10]);
|
|
3264
|
+
case 12:
|
|
3265
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11]);
|
|
3266
|
+
case 13:
|
|
3267
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12]);
|
|
3268
|
+
case 14:
|
|
3269
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13]);
|
|
3270
|
+
case 15:
|
|
3271
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13], buf[start + 14]);
|
|
3272
|
+
case 16:
|
|
3273
|
+
return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13], buf[start + 14], buf[start + 15]);
|
|
1654
3274
|
}
|
|
1655
|
-
this.depth++;
|
|
1656
|
-
}
|
|
1657
|
-
this.id = id;
|
|
1658
|
-
this.wire = wire;
|
|
1659
|
-
this.fixSub = this.peekFixSub(wire);
|
|
1660
|
-
return true;
|
|
1661
|
-
}
|
|
1662
|
-
/** Read an unsigned scalar (wire {@link WireType.Unsigned}), number-first. */
|
|
1663
|
-
readUnsigned() {
|
|
1664
|
-
this.readVarint();
|
|
1665
|
-
return this.unsignedValue();
|
|
1666
|
-
}
|
|
1667
|
-
/** Read a signed scalar (wire {@link WireType.Signed}), zig-zag, number-first. */
|
|
1668
|
-
readSigned() {
|
|
1669
|
-
this.readVarint();
|
|
1670
|
-
return this.signedValue();
|
|
1671
|
-
}
|
|
1672
|
-
/** Read a 32-bit float scalar (wire {@link WireType.Fixlen}, subtype fp32). */
|
|
1673
|
-
readFp32() {
|
|
1674
|
-
this.fixlenHeader(FixlenSubtype.Fp32, 4);
|
|
1675
|
-
return this.rawFp32();
|
|
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
|
-
}
|
|
1697
|
-
/** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
|
|
1698
|
-
readFp64() {
|
|
1699
|
-
this.fixlenHeader(FixlenSubtype.Fp64, 8);
|
|
1700
|
-
return this.rawFp64();
|
|
1701
|
-
}
|
|
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);
|
|
1711
|
-
const bytes = this.take(len);
|
|
1712
|
-
try {
|
|
1713
|
-
return _utf8.decode(bytes);
|
|
1714
|
-
} catch {
|
|
1715
|
-
throw invalidMsgError("invalid UTF-8 in string");
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
/**
|
|
1719
|
-
* Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
|
|
1720
|
-
* zero-copy {@link Uint8Array} view into the source buffer.
|
|
1721
|
-
*/
|
|
1722
|
-
readBlob(schemaMaxlen) {
|
|
1723
|
-
const len = this.fixlenLen(FixlenSubtype.Blob, schemaMaxlen);
|
|
1724
|
-
return this.take(len);
|
|
1725
|
-
}
|
|
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);
|
|
1734
|
-
const out = new Array(count);
|
|
1735
|
-
for (let i = 0; i < count; i++) {
|
|
1736
|
-
this.readVarint();
|
|
1737
|
-
out[i] = this.unsignedValue();
|
|
1738
|
-
}
|
|
1739
|
-
return out;
|
|
1740
|
-
}
|
|
1741
|
-
/** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
|
|
1742
|
-
readSignedArray(schemaCount) {
|
|
1743
|
-
const count = this.arrayCount(schemaCount);
|
|
1744
|
-
const out = new Array(count);
|
|
1745
|
-
for (let i = 0; i < count; i++) {
|
|
1746
|
-
this.readVarint();
|
|
1747
|
-
out[i] = this.signedValue();
|
|
1748
|
-
}
|
|
1749
|
-
return out;
|
|
1750
|
-
}
|
|
1751
|
-
/**
|
|
1752
|
-
* Read an unsigned 64-bit array into {@link Long}[] — the `bigint`-free path.
|
|
1753
|
-
* Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
|
|
1754
|
-
* materialise only the values the caller actually needs.
|
|
1755
|
-
*/
|
|
1756
|
-
readUnsignedArrayLong(schemaCount) {
|
|
1757
|
-
const count = this.arrayCount(schemaCount);
|
|
1758
|
-
const out = new Array(count);
|
|
1759
|
-
for (let i = 0; i < count; i++) {
|
|
1760
|
-
this.readVarint();
|
|
1761
|
-
out[i] = new Long(this.lo, this.hi);
|
|
1762
|
-
}
|
|
1763
|
-
return out;
|
|
1764
|
-
}
|
|
1765
|
-
/** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
|
|
1766
|
-
readSignedArrayLong(schemaCount) {
|
|
1767
|
-
const count = this.arrayCount(schemaCount);
|
|
1768
|
-
const out = new Array(count);
|
|
1769
|
-
for (let i = 0; i < count; i++) {
|
|
1770
|
-
this.readVarint();
|
|
1771
|
-
const lo = this.lo >>> 0;
|
|
1772
|
-
const hi = this.hi >>> 0;
|
|
1773
|
-
const mask = -(lo & 1) >>> 0;
|
|
1774
|
-
out[i] = new Long((lo >>> 1 | hi << 31) >>> 0 ^ mask, hi >>> 1 >>> 0 ^ mask);
|
|
1775
|
-
}
|
|
1776
|
-
return out;
|
|
1777
|
-
}
|
|
1778
|
-
/** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
|
|
1779
|
-
readFp32Array(schemaCount) {
|
|
1780
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
|
|
1781
|
-
const out = new Array(count);
|
|
1782
|
-
for (let i = 0; i < count; i++) out[i] = this.rawFp32();
|
|
1783
|
-
return out;
|
|
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
|
-
}
|
|
1799
|
-
/** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
|
|
1800
|
-
readFp64Array(schemaCount) {
|
|
1801
|
-
const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8, schemaCount);
|
|
1802
|
-
const out = new Array(count);
|
|
1803
|
-
for (let i = 0; i < count; i++) out[i] = this.rawFp64();
|
|
1804
|
-
return out;
|
|
1805
|
-
}
|
|
1806
|
-
/**
|
|
1807
|
-
* Consume the value of the field whose header {@link readHeader} just accepted,
|
|
1808
|
-
* discarding it — for a `default:` branch that keeps the cursor in sync on an
|
|
1809
|
-
* unknown id. Pass {@link wire}. A {@link WireType.SequenceStart} skips the
|
|
1810
|
-
* whole nested sequence.
|
|
1811
|
-
*/
|
|
1812
|
-
skip(wire) {
|
|
1813
|
-
if (wire === WireType.SequenceStart) {
|
|
1814
|
-
this.skipSequence();
|
|
1815
|
-
this.depth--;
|
|
1816
|
-
return;
|
|
1817
3275
|
}
|
|
1818
|
-
this.skipValue(wire);
|
|
1819
3276
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
case WireType.Signed:
|
|
1825
|
-
this.readVarint();
|
|
1826
|
-
return;
|
|
1827
|
-
case WireType.Fixlen: {
|
|
1828
|
-
this.readVarint();
|
|
1829
|
-
const sub = this.lo & 7;
|
|
1830
|
-
const len = this.upper();
|
|
1831
|
-
if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1832
|
-
if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
|
|
1833
|
-
if (len !== (sub === FixlenSubtype.Fp32 ? 4 : 8)) {
|
|
1834
|
-
throw invalidMsgError("fixlen float length mismatch");
|
|
1835
|
-
}
|
|
1836
|
-
} else if (len > FIXLEN_MAX) {
|
|
1837
|
-
throw invalidMsgError("fixlen length out of range");
|
|
1838
|
-
}
|
|
1839
|
-
this.take(len);
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
case WireType.ArrayUnsigned:
|
|
1843
|
-
case WireType.ArraySigned: {
|
|
1844
|
-
const count = this.arrayCount();
|
|
1845
|
-
for (let i = 0; i < count; i++) this.readVarint();
|
|
1846
|
-
return;
|
|
1847
|
-
}
|
|
1848
|
-
case WireType.ArrayFixlen: {
|
|
1849
|
-
this.readVarint();
|
|
1850
|
-
const count = this.num();
|
|
1851
|
-
if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
|
|
1852
|
-
if (count > this.maxArrayCount) {
|
|
1853
|
-
throw limitExceededError(
|
|
1854
|
-
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|
|
1855
|
-
);
|
|
1856
|
-
}
|
|
1857
|
-
this.readVarint();
|
|
1858
|
-
const sub = this.lo & 7;
|
|
1859
|
-
const size = this.upper();
|
|
1860
|
-
const ok = sub === FixlenSubtype.Fp32 && size === 4 || sub === FixlenSubtype.Fp64 && size === 8;
|
|
1861
|
-
if (!ok) throw invalidMsgError("invalid fixlen array element type");
|
|
1862
|
-
this.take(count * size);
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1865
|
-
default:
|
|
1866
|
-
throw invalidMsgError(`invalid wire type ${wire}`);
|
|
1867
|
-
}
|
|
3277
|
+
try {
|
|
3278
|
+
return _utf8.decode(buf.subarray(start, end));
|
|
3279
|
+
} catch {
|
|
3280
|
+
throw invalidMsgError("invalid UTF-8 in string");
|
|
1868
3281
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
depth--;
|
|
1877
|
-
continue;
|
|
1878
|
-
}
|
|
1879
|
-
const id = this.upper();
|
|
1880
|
-
if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
|
|
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
|
-
}
|
|
1889
|
-
}
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
// src/decode/acc.ts
|
|
3285
|
+
var PayloadAcc = class {
|
|
3286
|
+
constructor() {
|
|
3287
|
+
this.buf = null;
|
|
3288
|
+
this.len = 0;
|
|
1890
3289
|
}
|
|
1891
|
-
// --- field helpers ------------------------------------------------------
|
|
1892
3290
|
/**
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
3291
|
+
* Contribute one piece — the bytes `src[start..end)` at `offset` of a
|
|
3292
|
+
* `total`-byte payload — and return the **whole** payload once it is complete,
|
|
3293
|
+
* or `null` while bytes are still outstanding.
|
|
3294
|
+
*
|
|
3295
|
+
* `offset === 0` starts a payload — and *resets* the accumulator, so a decode
|
|
3296
|
+
* that was abandoned mid-payload (an `INVALID` field, a declined subtree) leaves
|
|
3297
|
+
* nothing behind to corrupt the next one.
|
|
1898
3298
|
*
|
|
1899
|
-
*
|
|
1900
|
-
*
|
|
1901
|
-
*
|
|
3299
|
+
* A late piece with no payload in progress returns `null` rather than writing
|
|
3300
|
+
* anywhere: the accumulator has no buffer to append to, and inventing one would
|
|
3301
|
+
* fabricate a payload out of a fragment.
|
|
1902
3302
|
*/
|
|
1903
|
-
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
3303
|
+
take(total, offset, src, start, end) {
|
|
3304
|
+
if (offset === 0) {
|
|
3305
|
+
this.buf = new Uint8Array(total);
|
|
3306
|
+
this.len = 0;
|
|
1906
3307
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
3308
|
+
const b = this.buf;
|
|
3309
|
+
if (b === null) return null;
|
|
3310
|
+
let n = end - start;
|
|
3311
|
+
const room = total - this.len;
|
|
3312
|
+
if (n > room) n = room;
|
|
3313
|
+
if (n > 0) {
|
|
3314
|
+
b.set(src.subarray(start, start + n), this.len);
|
|
3315
|
+
this.len += n;
|
|
1912
3316
|
}
|
|
1913
|
-
return
|
|
3317
|
+
if (this.len < total) return null;
|
|
3318
|
+
this.buf = null;
|
|
3319
|
+
return b;
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
|
|
3323
|
+
// src/decode/seq.ts
|
|
3324
|
+
var NO_BYTES = new Uint8Array(0);
|
|
3325
|
+
var UNBOUNDED = -1;
|
|
3326
|
+
function requireReceiverBound(schemaBound, receiver, what, name) {
|
|
3327
|
+
if (schemaBound >= 0) return;
|
|
3328
|
+
if (Number.isFinite(receiver) && receiver >= 0) return;
|
|
3329
|
+
throw argumentError(
|
|
3330
|
+
`${name}: ${what} is ${receiver}, which states no cap \u2014 the schema left this field unbounded, and \xA76.2.1 admits no unset state and no unlimited mode. The number is generated code's to supply.`
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
function indexBound(cap, receiverCap) {
|
|
3334
|
+
return cap >= 0 ? cap : receiverCap;
|
|
3335
|
+
}
|
|
3336
|
+
function rejectIndex(id, cap, receiverCap, name) {
|
|
3337
|
+
if (cap >= 0) {
|
|
3338
|
+
throw invalidMsgError(`${name}: array index above schema capacity ${cap}`);
|
|
3339
|
+
}
|
|
3340
|
+
throw limitExceededError(`${name}: array index ${id} exceeds the receiver cap ${receiverCap}`);
|
|
3341
|
+
}
|
|
3342
|
+
var ElementSeq = class {
|
|
3343
|
+
constructor(out, def, cap, name, receiverCap) {
|
|
3344
|
+
this.out = out;
|
|
3345
|
+
this.def = def;
|
|
3346
|
+
this.cap = cap;
|
|
3347
|
+
this.name = name;
|
|
3348
|
+
this.receiverCap = receiverCap;
|
|
3349
|
+
if (cap < 0) requireReceiverBound(cap, receiverCap, "the receiver array-index cap", name);
|
|
3350
|
+
this.bound = indexBound(cap, receiverCap);
|
|
1914
3351
|
}
|
|
1915
3352
|
/**
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
3353
|
+
* Bound-check `id` and grow `out` to `id + 1`, filling any gap — and the slot
|
|
3354
|
+
* itself — with the element default.
|
|
3355
|
+
*
|
|
3356
|
+
* The check runs **before** the growth, which is the whole of §7.2 item 8's
|
|
3357
|
+
* "after a rejected id the container is not left partially extended": a
|
|
3358
|
+
* rejection leaves `out` exactly as it was, so a lower id delivered afterwards
|
|
3359
|
+
* still lands at its own index.
|
|
1919
3360
|
*/
|
|
1920
|
-
|
|
1921
|
-
this.
|
|
1922
|
-
|
|
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
|
-
}
|
|
1927
|
-
if (count > this.maxArrayCount) {
|
|
1928
|
-
throw limitExceededError(
|
|
1929
|
-
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|
|
1930
|
-
);
|
|
1931
|
-
}
|
|
1932
|
-
if (count > this.n - this.p) throw incompleteError("truncated array");
|
|
1933
|
-
return count;
|
|
1934
|
-
}
|
|
1935
|
-
/** Read a scalar fixlen sub-header, asserting subtype and exact byte length (floats). */
|
|
1936
|
-
fixlenHeader(wantSub, wantLen) {
|
|
1937
|
-
this.readVarint();
|
|
1938
|
-
const sub = this.lo & 7;
|
|
1939
|
-
const len = this.upper();
|
|
1940
|
-
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
1941
|
-
if (len !== wantLen) throw invalidMsgError("fixlen float length mismatch");
|
|
1942
|
-
}
|
|
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) {
|
|
1949
|
-
this.readVarint();
|
|
1950
|
-
const sub = this.lo & 7;
|
|
1951
|
-
const len = this.upper();
|
|
1952
|
-
if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
|
|
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
|
-
}
|
|
1957
|
-
const limit = wantSub === FixlenSubtype.String ? this.maxStringLen : this.maxBlobLen;
|
|
1958
|
-
if (len > limit) {
|
|
1959
|
-
const what = wantSub === FixlenSubtype.String ? "string" : "blob";
|
|
1960
|
-
const name = wantSub === FixlenSubtype.String ? "maxStringLen" : "maxBlobLen";
|
|
1961
|
-
throw limitExceededError(
|
|
1962
|
-
`${what} length ${len} exceeds ${name} ${limit}`
|
|
1963
|
-
);
|
|
1964
|
-
}
|
|
1965
|
-
return len;
|
|
3361
|
+
reserve(id) {
|
|
3362
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
3363
|
+
while (this.out.length <= id) this.out.push(this.def);
|
|
1966
3364
|
}
|
|
1967
3365
|
/**
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
3366
|
+
* The two index bounds, without growing: the schema `count` as validity
|
|
3367
|
+
* (`INVALID`) or, where the schema left the array open, the receiver cap as
|
|
3368
|
+
* capacity (`LIMIT_EXCEEDED`). Never both — §6.2.1 keeps a cap off a field the
|
|
3369
|
+
* schema already bounds, which is why one `bound` can stand for both.
|
|
3370
|
+
*
|
|
3371
|
+
* Split out from {@link reserve} because a leaf element is bound-checked at its
|
|
3372
|
+
* length word, before its payload has arrived and so before there is anything to
|
|
3373
|
+
* place ({@link StringSeq.begin}).
|
|
1971
3374
|
*/
|
|
1972
|
-
|
|
1973
|
-
this.
|
|
1974
|
-
const count = this.num();
|
|
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
|
-
}
|
|
1979
|
-
if (count > this.maxArrayCount) {
|
|
1980
|
-
throw limitExceededError(
|
|
1981
|
-
`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
this.readVarint();
|
|
1985
|
-
const sub = this.lo & 7;
|
|
1986
|
-
const size = this.upper();
|
|
1987
|
-
if (sub !== wantSub || size !== wantSize) {
|
|
1988
|
-
throw invalidMsgError("invalid fixlen array element type");
|
|
1989
|
-
}
|
|
1990
|
-
if (count > (this.n - this.p) / wantSize) {
|
|
1991
|
-
throw incompleteError("truncated fixlen array");
|
|
1992
|
-
}
|
|
1993
|
-
return count;
|
|
1994
|
-
}
|
|
1995
|
-
/** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
|
|
1996
|
-
take(len) {
|
|
1997
|
-
const start = this.p;
|
|
1998
|
-
const end = start + len;
|
|
1999
|
-
if (end > this.n) throw incompleteError("truncated fixlen payload");
|
|
2000
|
-
this.p = end;
|
|
2001
|
-
return this.buf.subarray(start, end);
|
|
3375
|
+
checkIndex(id) {
|
|
3376
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
2002
3377
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
3378
|
+
/**
|
|
3379
|
+
* What {@link reserve} does, then `value` written into the slot. A repeat
|
|
3380
|
+
* replaces (§7.4).
|
|
3381
|
+
*
|
|
3382
|
+
* Written out rather than delegating to {@link reserve}: on the baseline tier a
|
|
3383
|
+
* call is not free, and this is the per-element path. The check still precedes
|
|
3384
|
+
* the growth, which is the property §7.2 item 8 asks for.
|
|
3385
|
+
*/
|
|
3386
|
+
place(id, value) {
|
|
3387
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
3388
|
+
while (this.out.length <= id) this.out.push(this.def);
|
|
3389
|
+
this.out[id] = value;
|
|
2008
3390
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
this.
|
|
2013
|
-
|
|
3391
|
+
};
|
|
3392
|
+
var FramedSeq = class {
|
|
3393
|
+
constructor(out, make, cap, name, receiverCap) {
|
|
3394
|
+
this.out = out;
|
|
3395
|
+
this.make = make;
|
|
3396
|
+
this.cap = cap;
|
|
3397
|
+
this.name = name;
|
|
3398
|
+
this.receiverCap = receiverCap;
|
|
3399
|
+
if (cap < 0) requireReceiverBound(cap, receiverCap, "the receiver array-index cap", name);
|
|
3400
|
+
this.bound = indexBound(cap, receiverCap);
|
|
2014
3401
|
}
|
|
2015
|
-
// --- varint reading (shared verbatim with ./fast) -----------------------
|
|
2016
3402
|
/**
|
|
2017
|
-
* The
|
|
2018
|
-
*
|
|
2019
|
-
*
|
|
3403
|
+
* The two index bounds, without growing — see {@link ElementSeq.checkIndex}.
|
|
3404
|
+
*
|
|
3405
|
+
* Split out for the same reason it is there: a caller may have a second bound
|
|
3406
|
+
* to take before anything is allocated. A native matrix row is that case in this
|
|
3407
|
+
* port — its element `count` is rejected at the array header, and §7.2 item 8
|
|
3408
|
+
* wants that rejection to leave the row container exactly as it was.
|
|
2020
3409
|
*/
|
|
2021
|
-
|
|
2022
|
-
|
|
3410
|
+
checkIndex(id) {
|
|
3411
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
2023
3412
|
}
|
|
2024
3413
|
/**
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
3414
|
+
* Bound-check `id`, **then** grow `out` to `id + 1`, each new slot its own
|
|
3415
|
+
* `make()`.
|
|
3416
|
+
*
|
|
3417
|
+
* The order is §7.2 item 8's "after a rejected id the container is not left
|
|
3418
|
+
* partially extended": a rejection leaves `out` exactly as it was, so a lower id
|
|
3419
|
+
* delivered afterwards still lands at its own index. A slot already present is
|
|
3420
|
+
* left alone — a re-opened `struct` / `union` element merges into the object it
|
|
3421
|
+
* already built, which is what §7.4's last-occurrence-wins means for a scope
|
|
3422
|
+
* whose value *is* the scope.
|
|
2027
3423
|
*/
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
3424
|
+
reserve(id) {
|
|
3425
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
3426
|
+
while (this.out.length <= id) this.out.push(this.make());
|
|
2031
3427
|
}
|
|
2032
|
-
/**
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
3428
|
+
/**
|
|
3429
|
+
* Bound-check `id`, fill the gap below it, then write `value` into the slot —
|
|
3430
|
+
* what a nested **row** needs, an array wrapper *replacing* whatever an earlier
|
|
3431
|
+
* opening built at that index (§7.4) rather than merging into it.
|
|
3432
|
+
*
|
|
3433
|
+
* The gap fill stops one short of `id` on purpose: the slot is about to be
|
|
3434
|
+
* overwritten, so calling `make()` for it would allocate an element default
|
|
3435
|
+
* nobody ever reads. Assigning at `out.length` extends the array by exactly one,
|
|
3436
|
+
* which is the same array a {@link reserve} would have left.
|
|
3437
|
+
*/
|
|
3438
|
+
place(id, value) {
|
|
3439
|
+
if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
|
|
3440
|
+
while (this.out.length < id) this.out.push(this.make());
|
|
3441
|
+
this.out[id] = value;
|
|
2044
3442
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
3443
|
+
};
|
|
3444
|
+
var StringSeq = class {
|
|
3445
|
+
constructor(out, acc, cap, elemMax, name, receiverCap, receiverElemMax) {
|
|
3446
|
+
this.out = out;
|
|
3447
|
+
this.acc = acc;
|
|
3448
|
+
this.cap = cap;
|
|
3449
|
+
this.elemMax = elemMax;
|
|
3450
|
+
this.name = name;
|
|
3451
|
+
this.receiverCap = receiverCap;
|
|
3452
|
+
this.receiverElemMax = receiverElemMax;
|
|
3453
|
+
requireReceiverBound(elemMax, receiverElemMax, "the receiver element-length cap", name);
|
|
3454
|
+
this.slots = new ElementSeq(out, "", cap, name, receiverCap);
|
|
2048
3455
|
}
|
|
2049
3456
|
/**
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
3457
|
+
* The element's fixlen length word ({@link Visitor.fixlenBegin}).
|
|
3458
|
+
*
|
|
3459
|
+
* The bounds are decided by this word, so they are checked here — before any
|
|
3460
|
+
* payload byte — and again in {@link element} below.
|
|
3461
|
+
*
|
|
3462
|
+
* That is not redundancy for its own sake: a message that ends *inside* an
|
|
3463
|
+
* over-long element must still be `INVALID`, and only this event runs early
|
|
3464
|
+
* enough to say so. Without it the verdict would degrade to `INCOMPLETE`, which
|
|
3465
|
+
* §5.2.3 forbids for input already known to be malformed and which §6.4 forbids
|
|
3466
|
+
* a chunk boundary from changing.
|
|
3467
|
+
*
|
|
3468
|
+
* An element of the wrong fixlen subtype is left alone: §7.3 requires it to be
|
|
3469
|
+
* *skipped*, not rejected, and it is skipped by this class simply ignoring it.
|
|
2053
3470
|
*/
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
if (
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
lo |= (b & 15) << 28;
|
|
2080
|
-
hi = b >> 4 & 7;
|
|
2081
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2082
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
2083
|
-
b = buf[p++];
|
|
2084
|
-
hi |= (b & 127) << 3;
|
|
2085
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2086
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
2087
|
-
b = buf[p++];
|
|
2088
|
-
hi |= (b & 127) << 10;
|
|
2089
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2090
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
2091
|
-
b = buf[p++];
|
|
2092
|
-
hi |= (b & 127) << 17;
|
|
2093
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2094
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
2095
|
-
b = buf[p++];
|
|
2096
|
-
hi |= (b & 127) << 24;
|
|
2097
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2098
|
-
if (p >= n) throw incompleteError("truncated varint");
|
|
2099
|
-
b = buf[p++];
|
|
2100
|
-
if ((b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
|
|
2101
|
-
hi |= (b & 127) << 31;
|
|
2102
|
-
if (b < 128) return this.set(lo, hi, p);
|
|
2103
|
-
throw invalidMsgError("varint overflow");
|
|
2104
|
-
}
|
|
2105
|
-
set(lo, hi, p) {
|
|
2106
|
-
this.lo = lo;
|
|
2107
|
-
this.hi = hi;
|
|
2108
|
-
this.p = p;
|
|
3471
|
+
begin(id, subtype, total) {
|
|
3472
|
+
if (subtype !== FixlenSubtype.String) return;
|
|
3473
|
+
this.check(id, total);
|
|
3474
|
+
}
|
|
3475
|
+
/** One payload piece of element `id` ({@link Visitor.string}). */
|
|
3476
|
+
element(id, total, offset, src, start, end) {
|
|
3477
|
+
this.check(id, total);
|
|
3478
|
+
const text = offset === 0 && end - start === total ? decodeUtf8(src, start, end) : decodeStringPiece(this.acc, total, offset, src, start, end);
|
|
3479
|
+
if (text === null) return;
|
|
3480
|
+
this.slots.place(id, text);
|
|
3481
|
+
}
|
|
3482
|
+
/** Every bound for one element. Rejects **before** the destination grows. */
|
|
3483
|
+
check(id, total) {
|
|
3484
|
+
this.slots.checkIndex(id);
|
|
3485
|
+
if (this.elemMax >= 0) {
|
|
3486
|
+
if (total > this.elemMax) {
|
|
3487
|
+
throw invalidMsgError(
|
|
3488
|
+
`${this.name} element: string byte length above schema maxlen ${this.elemMax}`
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3491
|
+
} else if (total > this.receiverElemMax) {
|
|
3492
|
+
throw limitExceededError(
|
|
3493
|
+
`${this.name} element: string byte length ${total} exceeds the receiver cap ${this.receiverElemMax}`
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
2109
3496
|
}
|
|
2110
3497
|
};
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
return
|
|
2126
|
-
|
|
2127
|
-
|
|
3498
|
+
var BlobSeq = class {
|
|
3499
|
+
constructor(out, acc, cap, elemMax, name, receiverCap, receiverElemMax) {
|
|
3500
|
+
this.out = out;
|
|
3501
|
+
this.acc = acc;
|
|
3502
|
+
this.cap = cap;
|
|
3503
|
+
this.elemMax = elemMax;
|
|
3504
|
+
this.name = name;
|
|
3505
|
+
this.receiverCap = receiverCap;
|
|
3506
|
+
this.receiverElemMax = receiverElemMax;
|
|
3507
|
+
requireReceiverBound(elemMax, receiverElemMax, "the receiver element-length cap", name);
|
|
3508
|
+
this.slots = new ElementSeq(out, NO_BYTES, cap, name, receiverCap);
|
|
3509
|
+
}
|
|
3510
|
+
/** See {@link StringSeq.begin} — the early bound check, for subtype `blob`. */
|
|
3511
|
+
begin(id, subtype, total) {
|
|
3512
|
+
if (subtype !== FixlenSubtype.Blob) return;
|
|
3513
|
+
this.check(id, total);
|
|
3514
|
+
}
|
|
3515
|
+
/** One payload piece of element `id` ({@link Visitor.blob}). */
|
|
3516
|
+
element(id, total, offset, src, start, end) {
|
|
3517
|
+
this.check(id, total);
|
|
3518
|
+
const payload = this.acc.take(total, offset, src, start, end);
|
|
3519
|
+
if (payload === null) return;
|
|
3520
|
+
this.slots.place(id, payload);
|
|
3521
|
+
}
|
|
3522
|
+
/** Every bound for one element. Rejects **before** the destination grows. */
|
|
3523
|
+
check(id, total) {
|
|
3524
|
+
this.slots.checkIndex(id);
|
|
3525
|
+
if (this.elemMax >= 0) {
|
|
3526
|
+
if (total > this.elemMax) {
|
|
3527
|
+
throw invalidMsgError(
|
|
3528
|
+
`${this.name} element: blob byte length above schema maxlen ${this.elemMax}`
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
} else if (total > this.receiverElemMax) {
|
|
3532
|
+
throw limitExceededError(
|
|
3533
|
+
`${this.name} element: blob byte length ${total} exceeds the receiver cap ${this.receiverElemMax}`
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
2128
3536
|
}
|
|
3537
|
+
};
|
|
3538
|
+
function decodeStringPiece(acc, total, offset, src, start, end) {
|
|
3539
|
+
const payload = acc.take(total, offset, src, start, end);
|
|
3540
|
+
return payload === null ? null : decodeUtf8(payload);
|
|
2129
3541
|
}
|
|
2130
3542
|
|
|
2131
|
-
// src/
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
instance = await WebAssembly.instantiate(source, imports);
|
|
2136
|
-
} else if (typeof Response !== "undefined" && (source instanceof Response || isThenable(source))) {
|
|
2137
|
-
const result = await WebAssembly.instantiateStreaming(
|
|
2138
|
-
source,
|
|
2139
|
-
imports
|
|
2140
|
-
);
|
|
2141
|
-
instance = result.instance;
|
|
2142
|
-
} else {
|
|
2143
|
-
const result = await WebAssembly.instantiate(source, imports);
|
|
2144
|
-
instance = result.instance;
|
|
2145
|
-
}
|
|
2146
|
-
setKernel(factory(instance.exports));
|
|
3543
|
+
// src/encode/equal.ts
|
|
3544
|
+
function elementsEqual(a, b) {
|
|
3545
|
+
if (a.length !== b.length) return false;
|
|
3546
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2147
3547
|
return true;
|
|
2148
3548
|
}
|
|
2149
|
-
function
|
|
2150
|
-
|
|
3549
|
+
function longElementsEqual(a, b) {
|
|
3550
|
+
if (a.length !== b.length) return false;
|
|
3551
|
+
for (let i = 0; i < a.length; i++) {
|
|
3552
|
+
if (a[i].low !== b[i].low || a[i].high !== b[i].high) return false;
|
|
3553
|
+
}
|
|
3554
|
+
return true;
|
|
2151
3555
|
}
|
|
2152
3556
|
|
|
2153
3557
|
exports.API_VERSION = API_VERSION;
|
|
2154
3558
|
exports.ARRAY_MAX = ARRAY_MAX;
|
|
2155
3559
|
exports.ArrayKind = ArrayKind;
|
|
2156
|
-
exports.
|
|
3560
|
+
exports.BlobSeq = BlobSeq;
|
|
2157
3561
|
exports.DecodeStatus = DecodeStatus;
|
|
3562
|
+
exports.ElementSeq = ElementSeq;
|
|
2158
3563
|
exports.FIXLEN_MAX = FIXLEN_MAX;
|
|
2159
3564
|
exports.FixlenSubtype = FixlenSubtype;
|
|
3565
|
+
exports.FramedSeq = FramedSeq;
|
|
2160
3566
|
exports.I64_MAX = I64_MAX;
|
|
2161
3567
|
exports.I64_MIN = I64_MIN;
|
|
2162
3568
|
exports.ID_MAX = ID_MAX;
|
|
2163
3569
|
exports.IStream = IStream;
|
|
2164
3570
|
exports.Long = Long;
|
|
2165
3571
|
exports.MAX_DEPTH = MAX_DEPTH;
|
|
3572
|
+
exports.MIN_OUTPUT_BUFFER = MIN_OUTPUT_BUFFER;
|
|
2166
3573
|
exports.OStream = OStream;
|
|
3574
|
+
exports.PayloadAcc = PayloadAcc;
|
|
2167
3575
|
exports.SofabError = SofabError;
|
|
2168
3576
|
exports.SofabErrorCode = SofabErrorCode;
|
|
3577
|
+
exports.StringSeq = StringSeq;
|
|
2169
3578
|
exports.U64_MAX = U64_MAX;
|
|
3579
|
+
exports.UNBOUNDED = UNBOUNDED;
|
|
2170
3580
|
exports.WireType = WireType;
|
|
2171
3581
|
exports.decode = decode;
|
|
3582
|
+
exports.decodeUtf8 = decodeUtf8;
|
|
3583
|
+
exports.elementsEqual = elementsEqual;
|
|
3584
|
+
exports.fp32RawBytes = fp32RawBytes;
|
|
2172
3585
|
exports.getKernel = getKernel;
|
|
3586
|
+
exports.growingOStream = growingOStream;
|
|
2173
3587
|
exports.jsKernel = jsKernel;
|
|
2174
|
-
exports.
|
|
2175
|
-
exports.loadWasmKernel = loadWasmKernel;
|
|
3588
|
+
exports.longElementsEqual = longElementsEqual;
|
|
2176
3589
|
exports.setKernel = setKernel;
|
|
2177
3590
|
exports.sofab = public_exports;
|
|
2178
3591
|
|