@moqtap/trace 0.1.3 → 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,26 +254,53 @@ 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;
303
+ if (event.streamId != null) base.sid = event.streamId;
72
304
  if (event.raw != null) base.raw = event.raw;
73
305
  break;
74
306
  }
@@ -80,22 +312,22 @@ function eventToCbor(event) {
80
312
  }
81
313
  case "stream-closed": {
82
314
  base.sid = event.streamId;
83
- base.ec = event.errorCode;
315
+ base.ec = int(event.errorCode);
84
316
  break;
85
317
  }
86
318
  case "object-header": {
87
319
  base.sid = event.streamId;
88
320
  base.g = event.groupId;
89
321
  base.o = event.objectId;
90
- base.pp = event.publisherPriority;
91
- base.os = event.objectStatus;
322
+ base.pp = int(event.publisherPriority);
323
+ base.os = int(event.objectStatus);
92
324
  break;
93
325
  }
94
326
  case "object-payload": {
95
327
  base.sid = event.streamId;
96
328
  base.g = event.groupId;
97
329
  base.o = event.objectId;
98
- base.sz = event.size;
330
+ base.sz = int(event.size);
99
331
  if (event.payload != null) base.pl = event.payload;
100
332
  break;
101
333
  }
@@ -105,7 +337,7 @@ function eventToCbor(event) {
105
337
  break;
106
338
  }
107
339
  case "error": {
108
- base.ec = event.errorCode;
340
+ base.ec = int(event.errorCode);
109
341
  base.reason = event.reason;
110
342
  break;
111
343
  }
@@ -114,38 +346,79 @@ function eventToCbor(event) {
114
346
  if (event.data !== void 0) base.data = event.data;
115
347
  break;
116
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
+ }
117
381
  }
118
382
  return base;
119
383
  }
120
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 } : {};
121
388
  const eventType = INT_TO_EVENT_TYPE[obj.e];
122
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
+ }
123
394
  return {
124
- type: "annotation",
125
- seq: Number(_nullishCoalesce(obj.n, () => ( 0))),
126
- timestamp: Number(_nullishCoalesce(obj.t, () => ( 0))),
127
- label: `unknown-event-${obj.e}`,
128
- data: obj
395
+ type: "unknown",
396
+ seq,
397
+ timestamp,
398
+ ...peerFields,
399
+ eventType: Number(_nullishCoalesce(obj.e, () => ( -1))),
400
+ fields
129
401
  };
130
402
  }
131
- const seq = Number(_nullishCoalesce(obj.n, () => ( 0)));
132
- const timestamp = Number(_nullishCoalesce(obj.t, () => ( 0)));
133
403
  switch (eventType) {
134
404
  case "control":
135
405
  return {
136
406
  type: "control",
137
407
  seq,
138
408
  timestamp,
409
+ ...peerFields,
139
410
  direction: obj.d,
140
411
  messageType: Number(_nullishCoalesce(obj.mt, () => ( 0))),
141
412
  message: _nullishCoalesce(obj.msg, () => ( {})),
142
- ...obj.raw != null ? { raw: obj.raw } : {}
413
+ ...obj.sid != null ? { streamId: BigInt(obj.sid) } : {},
414
+ ...obj.raw != null ? { raw: toBytes(obj.raw) } : {}
143
415
  };
144
416
  case "stream-opened":
145
417
  return {
146
418
  type: "stream-opened",
147
419
  seq,
148
420
  timestamp,
421
+ ...peerFields,
149
422
  streamId: BigInt(obj.sid),
150
423
  direction: obj.d,
151
424
  streamType: obj.st
@@ -155,6 +428,7 @@ function cborToEvent(obj) {
155
428
  type: "stream-closed",
156
429
  seq,
157
430
  timestamp,
431
+ ...peerFields,
158
432
  streamId: BigInt(obj.sid),
159
433
  errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0)))
160
434
  };
@@ -163,6 +437,7 @@ function cborToEvent(obj) {
163
437
  type: "object-header",
164
438
  seq,
165
439
  timestamp,
440
+ ...peerFields,
166
441
  streamId: BigInt(obj.sid),
167
442
  groupId: BigInt(obj.g),
168
443
  objectId: BigInt(obj.o),
@@ -174,17 +449,19 @@ function cborToEvent(obj) {
174
449
  type: "object-payload",
175
450
  seq,
176
451
  timestamp,
452
+ ...peerFields,
177
453
  streamId: BigInt(obj.sid),
178
454
  groupId: BigInt(obj.g),
179
455
  objectId: BigInt(obj.o),
180
456
  size: Number(_nullishCoalesce(obj.sz, () => ( 0))),
181
- ...obj.pl != null ? { payload: obj.pl } : {}
457
+ ...obj.pl != null ? { payload: toBytes(obj.pl) } : {}
182
458
  };
183
459
  case "state-change":
184
460
  return {
185
461
  type: "state-change",
186
462
  seq,
187
463
  timestamp,
464
+ ...peerFields,
188
465
  from: obj.from,
189
466
  to: obj.to
190
467
  };
@@ -193,6 +470,7 @@ function cborToEvent(obj) {
193
470
  type: "error",
194
471
  seq,
195
472
  timestamp,
473
+ ...peerFields,
196
474
  errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0))),
197
475
  reason: _nullishCoalesce(obj.reason, () => ( ""))
198
476
  };
@@ -201,9 +479,47 @@ function cborToEvent(obj) {
201
479
  type: "annotation",
202
480
  seq,
203
481
  timestamp,
482
+ ...peerFields,
204
483
  label: _nullishCoalesce(obj.label, () => ( "")),
205
484
  data: obj.data
206
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
+ };
207
523
  }
208
524
  }
209
525
  function writePreamble(headerCbor) {
@@ -215,26 +531,89 @@ function writePreamble(headerCbor) {
215
531
  buf.set(headerCbor, PREAMBLE_SIZE);
216
532
  return buf;
217
533
  }
218
- function validatePreamble(bytes) {
219
- 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) {
220
543
  throw new Error("File too short: expected at least 16 bytes");
221
544
  }
222
- for (let i = 0; i < 8; i++) {
223
- if (bytes[i] !== MAGIC[i]) {
224
- throw new Error("Invalid magic bytes: not a .moqtrace file");
225
- }
545
+ if (!startsWithMagic(bytes, offset)) {
546
+ throw new Error("Invalid magic bytes: not a .moqtrace file");
226
547
  }
227
548
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
228
- const version = view.getUint32(8, true);
229
- if (version !== FORMAT_VERSION) {
230
- 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
+ );
231
554
  }
232
- const headerLength = view.getUint32(12, true);
233
- if (PREAMBLE_SIZE + headerLength > bytes.length) {
555
+ const headerLength = view.getUint32(offset + 12, true);
556
+ if (offset + PREAMBLE_SIZE + headerLength > bytes.length) {
234
557
  throw new Error("File truncated: header extends beyond file");
235
558
  }
236
559
  return { version, headerLength };
237
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
+ }
238
617
  function writeMoqtrace(trace) {
239
618
  const headerCbor = encode(headerToCbor(trace.header));
240
619
  const preamble = writePreamble(headerCbor);
@@ -254,22 +633,35 @@ function writeMoqtrace(trace) {
254
633
  }
255
634
  return result;
256
635
  }
257
- function readMoqtrace(bytes) {
258
- const { headerLength } = validatePreamble(bytes);
259
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
260
- const header = cborToHeader(decode(headerBytes));
261
- const eventBytes = bytes.slice(PREAMBLE_SIZE + headerLength);
262
- const events = [];
263
- if (eventBytes.length > 0) {
264
- decodeMultiple(eventBytes, (obj) => {
265
- events.push(cborToEvent(obj));
266
- });
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;
267
649
  }
268
- 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);
269
661
  }
270
662
  function readMoqtraceHeader(bytes) {
271
663
  const { headerLength } = validatePreamble(bytes);
272
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
664
+ const headerBytes = bytes.subarray(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
273
665
  return cborToHeader(decode(headerBytes));
274
666
  }
275
667
  function createMoqtraceWriter(header) {
@@ -281,6 +673,12 @@ function createMoqtraceWriter(header) {
281
673
  },
282
674
  writeEvent(event) {
283
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)));
284
682
  }
285
683
  };
286
684
  }
@@ -313,6 +711,11 @@ var DETAIL_RANK = {
313
711
  function createRecorder(options) {
314
712
  const detail = options.detail;
315
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
+ }
316
719
  const maxEvents = _nullishCoalesce(options.maxEvents, () => ( 1e5));
317
720
  const clock = _nullishCoalesce(options.clock, () => ( (() => Math.round(performance.now() * 1e3))));
318
721
  const messageTypeId = _nullishCoalesce(options.messageTypeId, () => ( (() => 0)));
@@ -500,4 +903,10 @@ function createRecorder(options) {
500
903
 
501
904
 
502
905
 
503
- 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;