@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/README.md +95 -0
- package/SPEC.md +388 -102
- package/dist/index.cjs +463 -54
- package/dist/index.d.cts +260 -5
- package/dist/index.d.ts +260 -5
- package/dist/index.js +462 -53
- package/package.json +2 -2
- package/src/binary.ts +469 -57
- package/src/cbor-scan.ts +185 -0
- package/src/index.ts +18 -1
- package/src/recorder.ts +10 -0
- package/src/types.ts +207 -2
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
|
-
|
|
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 =
|
|
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: "
|
|
125
|
-
seq
|
|
126
|
-
timestamp
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
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
|
|
219
|
-
if (
|
|
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
|
-
|
|
223
|
-
|
|
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
|
|
230
|
-
throw new Error(
|
|
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
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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;
|