@moqtap/trace 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,13 +1,150 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/binary.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/binary.ts
2
2
  var _cborx = require('cbor-x');
3
- var codec = new (0, _cborx.Encoder)({ useRecords: false, mapsAsObjects: true });
3
+
4
+ // src/cbor-scan.ts
5
+ var INCOMPLETE = null;
6
+ var MalformedCborError = class extends Error {
7
+
8
+ constructor(message, offset) {
9
+ super(`${message} at byte ${offset}`);
10
+ this.name = "MalformedCborError";
11
+ this.offset = offset;
12
+ }
13
+ };
14
+ var MAJOR_UNSIGNED = 0;
15
+ var MAJOR_NEGATIVE = 1;
16
+ var MAJOR_BYTES = 2;
17
+ var MAJOR_TEXT = 3;
18
+ var MAJOR_ARRAY = 4;
19
+ var MAJOR_MAP = 5;
20
+ var MAJOR_TAG = 6;
21
+ var MAJOR_SIMPLE = 7;
22
+ var AI_ONE_BYTE = 24;
23
+ var AI_TWO_BYTES = 25;
24
+ var AI_FOUR_BYTES = 26;
25
+ var AI_EIGHT_BYTES = 27;
26
+ var AI_INDEFINITE = 31;
27
+ var BREAK = 255;
28
+ function headerExtraBytes(additionalInfo, offset) {
29
+ if (additionalInfo < AI_ONE_BYTE) return 0;
30
+ switch (additionalInfo) {
31
+ case AI_ONE_BYTE:
32
+ return 1;
33
+ case AI_TWO_BYTES:
34
+ return 2;
35
+ case AI_FOUR_BYTES:
36
+ return 4;
37
+ case AI_EIGHT_BYTES:
38
+ return 8;
39
+ case AI_INDEFINITE:
40
+ return 0;
41
+ default:
42
+ throw new MalformedCborError(`reserved additional information ${additionalInfo}`, offset);
43
+ }
44
+ }
45
+ function readArgument(bytes, offset, additionalInfo) {
46
+ if (additionalInfo < AI_ONE_BYTE) return additionalInfo;
47
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
48
+ switch (additionalInfo) {
49
+ case AI_ONE_BYTE:
50
+ return view.getUint8(offset + 1);
51
+ case AI_TWO_BYTES:
52
+ return view.getUint16(offset + 1, false);
53
+ case AI_FOUR_BYTES:
54
+ return view.getUint32(offset + 1, false);
55
+ case AI_EIGHT_BYTES: {
56
+ const value = view.getBigUint64(offset + 1, false);
57
+ return Number(value);
58
+ }
59
+ default:
60
+ throw new MalformedCborError(`reserved additional information ${additionalInfo}`, offset);
61
+ }
62
+ }
63
+ function skipItem(bytes, offset, allowBreak) {
64
+ if (offset >= bytes.length) return INCOMPLETE;
65
+ const initial = bytes[offset];
66
+ if (initial === BREAK) {
67
+ if (!allowBreak) {
68
+ throw new MalformedCborError("break outside an indefinite-length item", offset);
69
+ }
70
+ return offset + 1;
71
+ }
72
+ const major = initial >> 5;
73
+ const additionalInfo = initial & 31;
74
+ const extra = headerExtraBytes(additionalInfo, offset);
75
+ if (offset + 1 + extra > bytes.length) return INCOMPLETE;
76
+ const indefinite = additionalInfo === AI_INDEFINITE;
77
+ if (indefinite && (major === MAJOR_UNSIGNED || major === MAJOR_NEGATIVE || major === MAJOR_TAG)) {
78
+ throw new MalformedCborError(`major type ${major} cannot be indefinite-length`, offset);
79
+ }
80
+ const afterHeader = offset + 1 + extra;
81
+ switch (major) {
82
+ case MAJOR_UNSIGNED:
83
+ case MAJOR_NEGATIVE:
84
+ return afterHeader;
85
+ case MAJOR_BYTES:
86
+ case MAJOR_TEXT: {
87
+ if (!indefinite) {
88
+ const length = readArgument(bytes, offset, additionalInfo);
89
+ const end = afterHeader + length;
90
+ return end > bytes.length ? INCOMPLETE : end;
91
+ }
92
+ let cursor = afterHeader;
93
+ for (; ; ) {
94
+ if (cursor >= bytes.length) return INCOMPLETE;
95
+ if (bytes[cursor] === BREAK) return cursor + 1;
96
+ const next = skipItem(bytes, cursor, false);
97
+ if (next === INCOMPLETE) return INCOMPLETE;
98
+ cursor = next;
99
+ }
100
+ }
101
+ case MAJOR_ARRAY:
102
+ case MAJOR_MAP: {
103
+ const perEntry = major === MAJOR_MAP ? 2 : 1;
104
+ if (!indefinite) {
105
+ const count = readArgument(bytes, offset, additionalInfo) * perEntry;
106
+ let cursor2 = afterHeader;
107
+ for (let i = 0; i < count; i++) {
108
+ const next = skipItem(bytes, cursor2, false);
109
+ if (next === INCOMPLETE) return INCOMPLETE;
110
+ cursor2 = next;
111
+ }
112
+ return cursor2;
113
+ }
114
+ let cursor = afterHeader;
115
+ for (; ; ) {
116
+ if (cursor >= bytes.length) return INCOMPLETE;
117
+ if (bytes[cursor] === BREAK) return cursor + 1;
118
+ for (let i = 0; i < perEntry; i++) {
119
+ const next = skipItem(bytes, cursor, false);
120
+ if (next === INCOMPLETE) return INCOMPLETE;
121
+ cursor = next;
122
+ }
123
+ }
124
+ }
125
+ case MAJOR_TAG:
126
+ return skipItem(bytes, afterHeader, false);
127
+ case MAJOR_SIMPLE:
128
+ return afterHeader;
129
+ default:
130
+ throw new MalformedCborError(`unknown major type ${major}`, offset);
131
+ }
132
+ }
133
+ function cborItemLength(bytes, start) {
134
+ const end = skipItem(bytes, start, false);
135
+ return end === INCOMPLETE ? INCOMPLETE : end - start;
136
+ }
137
+
138
+ // src/types.ts
139
+ var TRACE_ID_LENGTH = 16;
140
+
141
+ // src/binary.ts
142
+ var codec = new (0, _cborx.Encoder)({ useRecords: false, mapsAsObjects: true, tagUint8Array: false });
4
143
  var encode = (value) => codec.encode(value);
5
144
  var decode = (bytes) => codec.decode(bytes);
6
- var decodeMultiple = (bytes, cb) => {
7
- codec.decodeMultiple(bytes, cb);
8
- };
9
145
  var MAGIC = new Uint8Array([77, 79, 81, 84, 82, 65, 67, 69]);
10
- var FORMAT_VERSION = 1;
146
+ var FORMAT_VERSION = 2;
147
+ var SUPPORTED_VERSIONS = [1, FORMAT_VERSION];
11
148
  var PREAMBLE_SIZE = 16;
12
149
  var EVENT_TYPE_TO_INT = {
13
150
  control: 0,
@@ -17,7 +154,10 @@ var EVENT_TYPE_TO_INT = {
17
154
  "object-payload": 4,
18
155
  "state-change": 5,
19
156
  error: 6,
20
- annotation: 7
157
+ annotation: 7,
158
+ "peer-connected": 8,
159
+ "peer-disconnected": 9,
160
+ "subscription-derivation": 10
21
161
  };
22
162
  var INT_TO_EVENT_TYPE = {
23
163
  0: "control",
@@ -27,20 +167,85 @@ var INT_TO_EVENT_TYPE = {
27
167
  4: "object-payload",
28
168
  5: "state-change",
29
169
  6: "error",
30
- 7: "annotation"
170
+ 7: "annotation",
171
+ 8: "peer-connected",
172
+ 9: "peer-disconnected",
173
+ 10: "subscription-derivation"
31
174
  };
175
+ var COMMON_EVENT_KEYS = /* @__PURE__ */ new Set(["n", "t", "p", "e"]);
176
+ var TruncatedTraceError = class extends Error {
177
+ /** Byte offset at which the incomplete item begins. */
178
+
179
+ /**
180
+ * Everything that decoded before the cut, one entry per segment. A
181
+ * truncated trace is still evidence, and the events before the cut are
182
+ * exactly as valid as they were.
183
+ */
184
+
185
+ constructor(offset, segments) {
186
+ super(`Trace truncated: the item starting at byte ${offset} is incomplete`);
187
+ this.name = "TruncatedTraceError";
188
+ this.offset = offset;
189
+ this.segments = segments;
190
+ }
191
+ /** What decoded, flattened as {@link readMoqtrace} would return it. */
192
+ get trace() {
193
+ return flatten(this.segments);
194
+ }
195
+ };
196
+ function segmentToCbor(segment) {
197
+ const map = { sequence: int(segment.sequence) };
198
+ if (segment.durationMs != null) map.durationMs = int(segment.durationMs);
199
+ if (segment.streamId != null) map.streamId = segment.streamId;
200
+ if (segment.continues != null) map.continues = segment.continues;
201
+ return map;
202
+ }
203
+ function cborToSegment(obj) {
204
+ return {
205
+ sequence: Number(_nullishCoalesce(obj.sequence, () => ( 0))),
206
+ ...obj.durationMs != null ? { durationMs: Number(obj.durationMs) } : {},
207
+ ...obj.streamId != null ? { streamId: obj.streamId } : {},
208
+ ...obj.continues != null ? { continues: obj.continues } : {}
209
+ };
210
+ }
211
+ function samplingToCbor(sampling) {
212
+ const map = {};
213
+ if (sampling.effectiveRate != null) map.effectiveRate = sampling.effectiveRate;
214
+ if (sampling.maxEventsPerSec != null) map.maxEventsPerSec = int(sampling.maxEventsPerSec);
215
+ if (sampling.dropPolicy != null) map.dropPolicy = sampling.dropPolicy;
216
+ if (sampling.droppedTotal != null) map.droppedTotal = int(sampling.droppedTotal);
217
+ if (sampling.droppedSegment != null) map.droppedSegment = int(sampling.droppedSegment);
218
+ if (sampling.rule != null) map.rule = sampling.rule;
219
+ if (sampling.ruleLang != null) map.ruleLang = sampling.ruleLang;
220
+ if (sampling.appliesTo != null) map.appliesTo = sampling.appliesTo.map(int);
221
+ return map;
222
+ }
223
+ function cborToSampling(obj) {
224
+ return {
225
+ ...obj.effectiveRate != null ? { effectiveRate: Number(obj.effectiveRate) } : {},
226
+ ...obj.maxEventsPerSec != null ? { maxEventsPerSec: Number(obj.maxEventsPerSec) } : {},
227
+ ...obj.dropPolicy != null ? { dropPolicy: obj.dropPolicy } : {},
228
+ ...obj.droppedTotal != null ? { droppedTotal: Number(obj.droppedTotal) } : {},
229
+ ...obj.droppedSegment != null ? { droppedSegment: Number(obj.droppedSegment) } : {},
230
+ ...obj.rule != null ? { rule: obj.rule } : {},
231
+ ...obj.ruleLang != null ? { ruleLang: obj.ruleLang } : {},
232
+ ...obj.appliesTo != null ? { appliesTo: obj.appliesTo.map(Number) } : {}
233
+ };
234
+ }
32
235
  function headerToCbor(header) {
33
236
  const map = {
34
237
  protocol: header.protocol,
35
238
  perspective: header.perspective,
36
239
  detail: header.detail,
37
- startTime: header.startTime
240
+ startTime: int(header.startTime)
38
241
  };
39
- if (header.endTime != null) map.endTime = header.endTime;
242
+ if (header.endTime != null) map.endTime = int(header.endTime);
40
243
  if (header.transport != null) map.transport = header.transport;
41
244
  if (header.source != null) map.source = header.source;
42
245
  if (header.endpoint != null) map.endpoint = header.endpoint;
43
246
  if (header.sessionId != null) map.sessionId = header.sessionId;
247
+ if (header.segment != null) map.segment = segmentToCbor(header.segment);
248
+ if (header.sampling != null) map.sampling = samplingToCbor(header.sampling);
44
249
  if (header.custom != null) map.custom = header.custom;
45
250
  return map;
46
251
  }
@@ -49,25 +254,51 @@ function cborToHeader(obj) {
49
254
  protocol: obj.protocol,
50
255
  perspective: obj.perspective,
51
256
  detail: obj.detail,
52
- startTime: obj.startTime,
53
- ...obj.endTime != null ? { endTime: obj.endTime } : {},
257
+ startTime: Number(obj.startTime),
258
+ ...obj.endTime != null ? { endTime: Number(obj.endTime) } : {},
54
259
  ...obj.transport != null ? { transport: obj.transport } : {},
55
260
  ...obj.source != null ? { source: obj.source } : {},
56
261
  ...obj.endpoint != null ? { endpoint: obj.endpoint } : {},
57
262
  ...obj.sessionId != null ? { sessionId: obj.sessionId } : {},
263
+ ...obj.segment != null ? { segment: cborToSegment(obj.segment) } : {},
264
+ ...obj.sampling != null ? { sampling: cborToSampling(obj.sampling) } : {},
58
265
  ...obj.custom != null ? { custom: obj.custom } : {}
59
266
  };
60
267
  }
268
+ function subscriptionRefToCbor(ref) {
269
+ return [ref.peer, ref.requestId];
270
+ }
271
+ function cborToSubscriptionRef(value) {
272
+ if (!Array.isArray(value) || value.length !== 2) {
273
+ throw new Error("Subscription reference must be [peer, requestId]");
274
+ }
275
+ return { peer: value[0], requestId: BigInt(value[1]) };
276
+ }
277
+ function toBytes(value) {
278
+ return value instanceof Uint8Array ? new Uint8Array(value) : new Uint8Array(value);
279
+ }
280
+ function int(value) {
281
+ const outsideInt32 = value > 4294967295 || value < -2147483648;
282
+ return Number.isInteger(value) && outsideInt32 ? BigInt(value) : value;
283
+ }
284
+ function checkTraceId(value) {
285
+ const bytes = toBytes(value);
286
+ if (bytes.length !== TRACE_ID_LENGTH) {
287
+ throw new Error(`traceId is ${bytes.length} bytes, must be ${TRACE_ID_LENGTH}`);
288
+ }
289
+ return bytes;
290
+ }
61
291
  function eventToCbor(event) {
62
292
  const base = {
63
- n: event.seq,
64
- t: event.timestamp,
65
- e: EVENT_TYPE_TO_INT[event.type]
293
+ n: int(event.seq),
294
+ t: int(event.timestamp)
66
295
  };
296
+ if (event.peer != null) base.p = event.peer;
297
+ if (event.type !== "unknown") base.e = EVENT_TYPE_TO_INT[event.type];
67
298
  switch (event.type) {
68
299
  case "control": {
69
300
  base.d = event.direction;
70
- base.mt = event.messageType;
301
+ base.mt = int(event.messageType);
71
302
  base.msg = event.message;
72
303
  if (event.streamId != null) base.sid = event.streamId;
73
304
  if (event.raw != null) base.raw = event.raw;
@@ -81,22 +312,22 @@ function eventToCbor(event) {
81
312
  }
82
313
  case "stream-closed": {
83
314
  base.sid = event.streamId;
84
- base.ec = event.errorCode;
315
+ base.ec = int(event.errorCode);
85
316
  break;
86
317
  }
87
318
  case "object-header": {
88
319
  base.sid = event.streamId;
89
320
  base.g = event.groupId;
90
321
  base.o = event.objectId;
91
- base.pp = event.publisherPriority;
92
- base.os = event.objectStatus;
322
+ base.pp = int(event.publisherPriority);
323
+ base.os = int(event.objectStatus);
93
324
  break;
94
325
  }
95
326
  case "object-payload": {
96
327
  base.sid = event.streamId;
97
328
  base.g = event.groupId;
98
329
  base.o = event.objectId;
99
- base.sz = event.size;
330
+ base.sz = int(event.size);
100
331
  if (event.payload != null) base.pl = event.payload;
101
332
  break;
102
333
  }
@@ -106,7 +337,7 @@ function eventToCbor(event) {
106
337
  break;
107
338
  }
108
339
  case "error": {
109
- base.ec = event.errorCode;
340
+ base.ec = int(event.errorCode);
110
341
  base.reason = event.reason;
111
342
  break;
112
343
  }
@@ -115,39 +346,79 @@ function eventToCbor(event) {
115
346
  if (event.data !== void 0) base.data = event.data;
116
347
  break;
117
348
  }
349
+ case "peer-connected": {
350
+ if (event.endpoint != null) base.endpoint = event.endpoint;
351
+ if (event.transport != null) base.transport = event.transport;
352
+ if (event.role != null) base.role = event.role;
353
+ if (event.side != null) base.side = event.side;
354
+ break;
355
+ }
356
+ case "peer-disconnected": {
357
+ base.ec = int(event.errorCode);
358
+ if (event.reason != null) base.reason = event.reason;
359
+ break;
360
+ }
361
+ case "subscription-derivation": {
362
+ base.u = subscriptionRefToCbor(event.upstream);
363
+ base.d = event.downstream.map(subscriptionRefToCbor);
364
+ base.kind = event.kind;
365
+ if (event.traceId != null) base.traceId = checkTraceId(event.traceId);
366
+ if (event.namespace != null) base.ns = event.namespace;
367
+ if (event.trackName != null) base.tn = event.trackName;
368
+ if (event.tDownstreamReceived != null) base.tdr = int(event.tDownstreamReceived);
369
+ if (event.tUpstreamSent != null) base.tus = int(event.tUpstreamSent);
370
+ if (event.tUpstreamOkReceived != null) base.tuo = int(event.tUpstreamOkReceived);
371
+ if (event.tDownstreamOkSent != null) base.tdo = int(event.tDownstreamOkSent);
372
+ break;
373
+ }
374
+ case "unknown": {
375
+ base.e = event.eventType;
376
+ for (const [key, value] of Object.entries(event.fields)) {
377
+ base[key] = value;
378
+ }
379
+ break;
380
+ }
118
381
  }
119
382
  return base;
120
383
  }
121
384
  function cborToEvent(obj) {
385
+ const seq = Number(_nullishCoalesce(obj.n, () => ( 0)));
386
+ const timestamp = Number(_nullishCoalesce(obj.t, () => ( 0)));
387
+ const peerFields = obj.p != null ? { peer: obj.p } : {};
122
388
  const eventType = INT_TO_EVENT_TYPE[obj.e];
123
389
  if (eventType == null) {
390
+ const fields = {};
391
+ for (const [key, value] of Object.entries(obj)) {
392
+ if (!COMMON_EVENT_KEYS.has(key)) fields[key] = value;
393
+ }
124
394
  return {
125
- type: "annotation",
126
- seq: Number(_nullishCoalesce(obj.n, () => ( 0))),
127
- timestamp: Number(_nullishCoalesce(obj.t, () => ( 0))),
128
- label: `unknown-event-${obj.e}`,
129
- data: obj
395
+ type: "unknown",
396
+ seq,
397
+ timestamp,
398
+ ...peerFields,
399
+ eventType: Number(_nullishCoalesce(obj.e, () => ( -1))),
400
+ fields
130
401
  };
131
402
  }
132
- const seq = Number(_nullishCoalesce(obj.n, () => ( 0)));
133
- const timestamp = Number(_nullishCoalesce(obj.t, () => ( 0)));
134
403
  switch (eventType) {
135
404
  case "control":
136
405
  return {
137
406
  type: "control",
138
407
  seq,
139
408
  timestamp,
409
+ ...peerFields,
140
410
  direction: obj.d,
141
411
  messageType: Number(_nullishCoalesce(obj.mt, () => ( 0))),
142
412
  message: _nullishCoalesce(obj.msg, () => ( {})),
143
413
  ...obj.sid != null ? { streamId: BigInt(obj.sid) } : {},
144
- ...obj.raw != null ? { raw: obj.raw } : {}
414
+ ...obj.raw != null ? { raw: toBytes(obj.raw) } : {}
145
415
  };
146
416
  case "stream-opened":
147
417
  return {
148
418
  type: "stream-opened",
149
419
  seq,
150
420
  timestamp,
421
+ ...peerFields,
151
422
  streamId: BigInt(obj.sid),
152
423
  direction: obj.d,
153
424
  streamType: obj.st
@@ -157,6 +428,7 @@ function cborToEvent(obj) {
157
428
  type: "stream-closed",
158
429
  seq,
159
430
  timestamp,
431
+ ...peerFields,
160
432
  streamId: BigInt(obj.sid),
161
433
  errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0)))
162
434
  };
@@ -165,6 +437,7 @@ function cborToEvent(obj) {
165
437
  type: "object-header",
166
438
  seq,
167
439
  timestamp,
440
+ ...peerFields,
168
441
  streamId: BigInt(obj.sid),
169
442
  groupId: BigInt(obj.g),
170
443
  objectId: BigInt(obj.o),
@@ -176,17 +449,19 @@ function cborToEvent(obj) {
176
449
  type: "object-payload",
177
450
  seq,
178
451
  timestamp,
452
+ ...peerFields,
179
453
  streamId: BigInt(obj.sid),
180
454
  groupId: BigInt(obj.g),
181
455
  objectId: BigInt(obj.o),
182
456
  size: Number(_nullishCoalesce(obj.sz, () => ( 0))),
183
- ...obj.pl != null ? { payload: obj.pl } : {}
457
+ ...obj.pl != null ? { payload: toBytes(obj.pl) } : {}
184
458
  };
185
459
  case "state-change":
186
460
  return {
187
461
  type: "state-change",
188
462
  seq,
189
463
  timestamp,
464
+ ...peerFields,
190
465
  from: obj.from,
191
466
  to: obj.to
192
467
  };
@@ -195,6 +470,7 @@ function cborToEvent(obj) {
195
470
  type: "error",
196
471
  seq,
197
472
  timestamp,
473
+ ...peerFields,
198
474
  errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0))),
199
475
  reason: _nullishCoalesce(obj.reason, () => ( ""))
200
476
  };
@@ -203,9 +479,47 @@ function cborToEvent(obj) {
203
479
  type: "annotation",
204
480
  seq,
205
481
  timestamp,
482
+ ...peerFields,
206
483
  label: _nullishCoalesce(obj.label, () => ( "")),
207
484
  data: obj.data
208
485
  };
486
+ case "peer-connected":
487
+ return {
488
+ type: "peer-connected",
489
+ seq,
490
+ timestamp,
491
+ ...peerFields,
492
+ ...obj.endpoint != null ? { endpoint: obj.endpoint } : {},
493
+ ...obj.transport != null ? { transport: obj.transport } : {},
494
+ ...obj.role != null ? { role: obj.role } : {},
495
+ ...obj.side != null ? { side: obj.side } : {}
496
+ };
497
+ case "peer-disconnected":
498
+ return {
499
+ type: "peer-disconnected",
500
+ seq,
501
+ timestamp,
502
+ ...peerFields,
503
+ errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0))),
504
+ ...obj.reason != null ? { reason: obj.reason } : {}
505
+ };
506
+ case "subscription-derivation":
507
+ return {
508
+ type: "subscription-derivation",
509
+ seq,
510
+ timestamp,
511
+ ...peerFields,
512
+ upstream: cborToSubscriptionRef(obj.u),
513
+ downstream: (_nullishCoalesce(obj.d, () => ( []))).map(cborToSubscriptionRef),
514
+ kind: _nullishCoalesce(obj.kind, () => ( "")),
515
+ ...obj.traceId != null ? { traceId: checkTraceId(obj.traceId) } : {},
516
+ ...obj.ns != null ? { namespace: obj.ns.map(toBytes) } : {},
517
+ ...obj.tn != null ? { trackName: toBytes(obj.tn) } : {},
518
+ ...obj.tdr != null ? { tDownstreamReceived: Number(obj.tdr) } : {},
519
+ ...obj.tus != null ? { tUpstreamSent: Number(obj.tus) } : {},
520
+ ...obj.tuo != null ? { tUpstreamOkReceived: Number(obj.tuo) } : {},
521
+ ...obj.tdo != null ? { tDownstreamOkSent: Number(obj.tdo) } : {}
522
+ };
209
523
  }
210
524
  }
211
525
  function writePreamble(headerCbor) {
@@ -217,26 +531,89 @@ function writePreamble(headerCbor) {
217
531
  buf.set(headerCbor, PREAMBLE_SIZE);
218
532
  return buf;
219
533
  }
220
- function validatePreamble(bytes) {
221
- if (bytes.length < PREAMBLE_SIZE) {
534
+ function startsWithMagic(bytes, offset) {
535
+ if (offset + MAGIC.length > bytes.length) return false;
536
+ for (let i = 0; i < MAGIC.length; i++) {
537
+ if (bytes[offset + i] !== MAGIC[i]) return false;
538
+ }
539
+ return true;
540
+ }
541
+ function validatePreamble(bytes, offset = 0) {
542
+ if (bytes.length - offset < PREAMBLE_SIZE) {
222
543
  throw new Error("File too short: expected at least 16 bytes");
223
544
  }
224
- for (let i = 0; i < 8; i++) {
225
- if (bytes[i] !== MAGIC[i]) {
226
- throw new Error("Invalid magic bytes: not a .moqtrace file");
227
- }
545
+ if (!startsWithMagic(bytes, offset)) {
546
+ throw new Error("Invalid magic bytes: not a .moqtrace file");
228
547
  }
229
548
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
230
- const version = view.getUint32(8, true);
231
- if (version !== FORMAT_VERSION) {
232
- throw new Error(`Unsupported format version: ${version} (expected ${FORMAT_VERSION})`);
549
+ const version = view.getUint32(offset + 8, true);
550
+ if (!SUPPORTED_VERSIONS.includes(version)) {
551
+ throw new Error(
552
+ `Unsupported format version: ${version} (supported: ${SUPPORTED_VERSIONS.join(", ")})`
553
+ );
233
554
  }
234
- const headerLength = view.getUint32(12, true);
235
- if (PREAMBLE_SIZE + headerLength > bytes.length) {
555
+ const headerLength = view.getUint32(offset + 12, true);
556
+ if (offset + PREAMBLE_SIZE + headerLength > bytes.length) {
236
557
  throw new Error("File truncated: header extends beyond file");
237
558
  }
238
559
  return { version, headerLength };
239
560
  }
561
+ function flatten(segments) {
562
+ const first = segments[0];
563
+ if (first == null) return void 0;
564
+ if (segments.length === 1) return first;
565
+ return { header: first.header, events: segments.flatMap((segment) => segment.events) };
566
+ }
567
+ function findNextSegment(bytes, from) {
568
+ for (let offset = from; offset + MAGIC.length <= bytes.length; offset++) {
569
+ if (startsWithMagic(bytes, offset)) return offset;
570
+ }
571
+ return -1;
572
+ }
573
+ function readSegments(bytes, options) {
574
+ const recover = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.recover]), () => ( false));
575
+ const segments = [];
576
+ let events = [];
577
+ let offset = 0;
578
+ validatePreamble(bytes, 0);
579
+ while (offset < bytes.length) {
580
+ if (startsWithMagic(bytes, offset)) {
581
+ const { headerLength } = validatePreamble(bytes, offset);
582
+ const start = offset + PREAMBLE_SIZE;
583
+ const headerBytes = bytes.subarray(start, start + headerLength);
584
+ events = [];
585
+ segments.push({
586
+ header: cborToHeader(decode(headerBytes)),
587
+ events
588
+ });
589
+ offset = start + headerLength;
590
+ continue;
591
+ }
592
+ let length;
593
+ try {
594
+ length = cborItemLength(bytes, offset);
595
+ } catch (error) {
596
+ if (!recover || !(error instanceof MalformedCborError)) throw error;
597
+ length = null;
598
+ }
599
+ if (length != null) {
600
+ try {
601
+ const item = decode(bytes.subarray(offset, offset + length));
602
+ events.push(cborToEvent(item));
603
+ offset += length;
604
+ continue;
605
+ } catch (error) {
606
+ if (!recover) throw error;
607
+ }
608
+ } else if (!recover) {
609
+ throw new TruncatedTraceError(offset, segments);
610
+ }
611
+ const next = findNextSegment(bytes, offset + 1);
612
+ if (next < 0) return segments;
613
+ offset = next;
614
+ }
615
+ return segments;
616
+ }
240
617
  function writeMoqtrace(trace) {
241
618
  const headerCbor = encode(headerToCbor(trace.header));
242
619
  const preamble = writePreamble(headerCbor);
@@ -256,22 +633,35 @@ function writeMoqtrace(trace) {
256
633
  }
257
634
  return result;
258
635
  }
259
- function readMoqtrace(bytes) {
260
- const { headerLength } = validatePreamble(bytes);
261
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
262
- const header = cborToHeader(decode(headerBytes));
263
- const eventBytes = bytes.slice(PREAMBLE_SIZE + headerLength);
264
- const events = [];
265
- if (eventBytes.length > 0) {
266
- decodeMultiple(eventBytes, (obj) => {
267
- events.push(cborToEvent(obj));
268
- });
636
+ function writeMoqtraceSegments(segments) {
637
+ const chunks = segments.map((segment, index) => {
638
+ if (segment.header.segment == null) {
639
+ throw new Error(`Segment ${index} has no 'segment' metadata in its header`);
640
+ }
641
+ return writeMoqtrace(segment);
642
+ });
643
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
644
+ const result = new Uint8Array(total);
645
+ let offset = 0;
646
+ for (const chunk of chunks) {
647
+ result.set(chunk, offset);
648
+ offset += chunk.length;
269
649
  }
270
- return { header, events };
650
+ return result;
651
+ }
652
+ function readMoqtrace(bytes, options) {
653
+ const trace = flatten(readSegments(bytes, options));
654
+ if (trace == null) {
655
+ throw new Error("Invalid magic bytes: not a .moqtrace file");
656
+ }
657
+ return trace;
658
+ }
659
+ function readMoqtraceSegments(bytes, options) {
660
+ return readSegments(bytes, options);
271
661
  }
272
662
  function readMoqtraceHeader(bytes) {
273
663
  const { headerLength } = validatePreamble(bytes);
274
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
664
+ const headerBytes = bytes.subarray(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
275
665
  return cborToHeader(decode(headerBytes));
276
666
  }
277
667
  function createMoqtraceWriter(header) {
@@ -283,6 +673,12 @@ function createMoqtraceWriter(header) {
283
673
  },
284
674
  writeEvent(event) {
285
675
  return encode(eventToCbor(event));
676
+ },
677
+ startSegment(next) {
678
+ if (next.segment == null) {
679
+ throw new Error("A segment header must carry 'segment' metadata");
680
+ }
681
+ return writePreamble(encode(headerToCbor(next)));
286
682
  }
287
683
  };
288
684
  }
@@ -315,6 +711,11 @@ var DETAIL_RANK = {
315
711
  function createRecorder(options) {
316
712
  const detail = options.detail;
317
713
  const detailRank = DETAIL_RANK[detail];
714
+ if (detailRank === void 0) {
715
+ throw new Error(
716
+ `Unknown detail level '${detail}': cannot record at a level this build does not implement`
717
+ );
718
+ }
318
719
  const maxEvents = _nullishCoalesce(options.maxEvents, () => ( 1e5));
319
720
  const clock = _nullishCoalesce(options.clock, () => ( (() => Math.round(performance.now() * 1e3))));
320
721
  const messageTypeId = _nullishCoalesce(options.messageTypeId, () => ( (() => 0)));
@@ -502,4 +903,10 @@ function createRecorder(options) {
502
903
 
503
904
 
504
905
 
505
- exports.createMoqtraceWriter = createMoqtraceWriter; exports.createRecorder = createRecorder; exports.readMoqtrace = readMoqtrace; exports.readMoqtraceHeader = readMoqtraceHeader; exports.traceToJSON = traceToJSON; exports.writeMoqtrace = writeMoqtrace;
906
+
907
+
908
+
909
+
910
+
911
+
912
+ exports.FORMAT_VERSION = FORMAT_VERSION; exports.SUPPORTED_VERSIONS = SUPPORTED_VERSIONS; exports.TRACE_ID_LENGTH = TRACE_ID_LENGTH; exports.TruncatedTraceError = TruncatedTraceError; exports.createMoqtraceWriter = createMoqtraceWriter; exports.createRecorder = createRecorder; exports.readMoqtrace = readMoqtrace; exports.readMoqtraceHeader = readMoqtraceHeader; exports.readMoqtraceSegments = readMoqtraceSegments; exports.traceToJSON = traceToJSON; exports.writeMoqtrace = writeMoqtrace; exports.writeMoqtraceSegments = writeMoqtraceSegments;