@moqtap/trace 0.4.2 → 0.5.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 CHANGED
@@ -23,7 +23,7 @@ import { MESSAGE_ID_MAP } from '@moqtap/codec/draft21'
23
23
  import { createDraft21SessionState } from '@moqtap/codec/draft21/session'
24
24
 
25
25
  const recorder = createRecorder({
26
- protocol: 'moqt-21',
26
+ protocol: 'moq-transport-21',
27
27
  perspective: 'client',
28
28
  detail: 'control',
29
29
  messageTypeId: (name) => Number(MESSAGE_ID_MAP.get(name) ?? 0),
package/SPEC.md CHANGED
@@ -44,7 +44,9 @@ and Event 4's `"pl"` begins after the object header, so the bytes of a
44
44
  including `"full"`. What those headers carry is recorded as decoded fields
45
45
  instead — see [Event 1](#event-1-stream-opened). Closing that gap needs a new
46
46
  key and is not in this revision; the level's description no longer implies
47
- otherwise.
47
+ otherwise. The one part of an object's framing recorded as bytes is its
48
+ property block, [Event 3's `"props"`](#event-3-object-header), because the
49
+ block's encoding is the draft's and has no decoded form this format could hold.
48
50
 
49
51
  At any detail level, the recorder MAY be configured to **mask payloads** — replacing payload bytes with zeroes before writing. This preserves payload sizes for bandwidth analysis while stripping media content. Sources MUST advertise masking via `"payloadMasked": true` in the header's `"custom"` map when active.
50
52
 
@@ -261,6 +263,33 @@ Object header events and object payload events (event 4) for the same object sha
261
263
  | `"o"` | integer | `headers`+ | Object ID |
262
264
  | `"pp"` | integer | `headers`+ | Publisher priority |
263
265
  | `"os"` | integer | `headers`+ | Object status (0=normal, 1=end-of-group, 2=end-of-track, 3=does-not-exist) |
266
+ | `"props"` | byte string | `headers`+ | The object's property block as the wire carried it, past its length field. **Optional.** See below. |
267
+
268
+ #### `"props"` — the property block
269
+
270
+ Object Properties — called Extension Headers before draft-17 — are the part of
271
+ an object a consumer reads before its payload: a LOC object's timestamp, frame
272
+ marking and codec configuration, the gaps a publisher signals after a restart,
273
+ the key ID of an encrypted object. Before this key no level recorded them, and
274
+ none of them can be recovered from the payload, so a trace could show an
275
+ object's bytes but not what decided how they were decoded.
276
+
277
+ - **Bytes, not a decoded list.** Every draft encodes the block differently —
278
+ absolute or delta types, two varint encodings — and a reader already holds
279
+ the codec for the session's draft. A decoded form would freeze one draft's
280
+ rules into this format, and the block MUST be recorded byte for byte so a
281
+ reader decodes exactly what the endpoint saw. Immutable Properties, which an
282
+ encrypted object authenticates, depend on that.
283
+ - **Absent and empty differ.** A writer MUST omit the key when the object had no
284
+ properties field, and MUST write an empty byte string when the field was
285
+ present with a length of 0 — on a subgroup stream with the PROPERTIES flag,
286
+ every object without properties carries one. A reader MUST keep the
287
+ distinction.
288
+ - **`headers`+, not payload-bearing.** The block is object metadata, not media.
289
+ A property value can still carry application data — a codec configuration is
290
+ a few dozen bytes of it — so a source that must not share any object content
291
+ MAY drop the key along with the payload. Dropping it does not change the
292
+ trace's detail level.
264
293
 
265
294
  ### Event 4: Object Payload
266
295
 
package/dist/index.cjs CHANGED
@@ -404,6 +404,7 @@ function eventToCbor(event) {
404
404
  base.o = event.objectId;
405
405
  base.pp = int(event.publisherPriority);
406
406
  base.os = int(event.objectStatus);
407
+ if (event.properties != null) base.props = event.properties;
407
408
  break;
408
409
  }
409
410
  case "object-payload": {
@@ -661,7 +662,8 @@ function decodeEvent(src) {
661
662
  groupId: requireUint(src, "g"),
662
663
  objectId: requireUint(src, "o"),
663
664
  publisherPriority: Number(_nullishCoalesce(take(src, "pp"), () => ( 0))),
664
- objectStatus: Number(_nullishCoalesce(take(src, "os"), () => ( 0)))
665
+ objectStatus: Number(_nullishCoalesce(take(src, "os"), () => ( 0))),
666
+ ...optional(src, "props", "properties", asByteString)
665
667
  };
666
668
  case "object-payload":
667
669
  return {
@@ -1215,7 +1217,7 @@ function createRecorder(options) {
1215
1217
  errorCode
1216
1218
  });
1217
1219
  },
1218
- recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
1220
+ recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus, properties) {
1219
1221
  if (detailRank < DETAIL_RANK.headers) return;
1220
1222
  addEvent({
1221
1223
  type: "object-header",
@@ -1225,7 +1227,8 @@ function createRecorder(options) {
1225
1227
  groupId,
1226
1228
  objectId,
1227
1229
  publisherPriority,
1228
- objectStatus
1230
+ objectStatus,
1231
+ ...properties !== void 0 ? { properties } : {}
1229
1232
  });
1230
1233
  },
1231
1234
  recordObjectPayload(streamId, groupId, objectId, size, payload) {
package/dist/index.d.cts CHANGED
@@ -298,6 +298,23 @@ interface ObjectHeaderEvent extends BaseEvent {
298
298
  readonly objectId: bigint;
299
299
  readonly publisherPriority: number;
300
300
  readonly objectStatus: number;
301
+ /**
302
+ * The Object's property block — Object Properties, Extension Headers before
303
+ * draft-17 — exactly as the wire carried it, past its length field.
304
+ *
305
+ * Absent when the Object had no properties field, which is not the same as an
306
+ * empty block: a subgroup stream with the PROPERTIES flag carries a zero
307
+ * length on every Object that has none. Kept as bytes rather than decoded
308
+ * because the encoding is the draft's, and a reader decodes it with the codec
309
+ * that reads the rest of the session; a decoded form would freeze one draft's
310
+ * rules into the format.
311
+ *
312
+ * Recorded from `'headers'` up. It is object metadata a payload's consumer
313
+ * reads before the payload — timestamps, frame marking, a codec
314
+ * configuration — and before this key no level recorded it, so a trace could
315
+ * not show what decided how its media was decoded.
316
+ */
317
+ readonly properties?: Uint8Array;
301
318
  }
302
319
  interface ObjectPayloadEvent extends BaseEvent {
303
320
  readonly type: 'object-payload';
@@ -917,8 +934,13 @@ interface TraceRecorder {
917
934
  recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void;
918
935
  /** Record a stream-closed event. Ignored at 'control' detail level. */
919
936
  recordStreamClosed(streamId: bigint, errorCode?: number): void;
920
- /** Record an object header event. Ignored below 'headers' detail level. */
921
- recordObjectHeader(streamId: bigint, groupId: bigint, objectId: bigint, publisherPriority: number, objectStatus: number): void;
937
+ /**
938
+ * Record an object header event. Ignored below 'headers' detail level.
939
+ *
940
+ * `properties` is the Object's property block as the wire carried it; omit it
941
+ * when the Object had no properties field.
942
+ */
943
+ recordObjectHeader(streamId: bigint, groupId: bigint, objectId: bigint, publisherPriority: number, objectStatus: number, properties?: Uint8Array): void;
922
944
  /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
923
945
  recordObjectPayload(streamId: bigint, groupId: bigint, objectId: bigint, size: number, payload?: Uint8Array): void;
924
946
  /**
package/dist/index.d.ts CHANGED
@@ -298,6 +298,23 @@ interface ObjectHeaderEvent extends BaseEvent {
298
298
  readonly objectId: bigint;
299
299
  readonly publisherPriority: number;
300
300
  readonly objectStatus: number;
301
+ /**
302
+ * The Object's property block — Object Properties, Extension Headers before
303
+ * draft-17 — exactly as the wire carried it, past its length field.
304
+ *
305
+ * Absent when the Object had no properties field, which is not the same as an
306
+ * empty block: a subgroup stream with the PROPERTIES flag carries a zero
307
+ * length on every Object that has none. Kept as bytes rather than decoded
308
+ * because the encoding is the draft's, and a reader decodes it with the codec
309
+ * that reads the rest of the session; a decoded form would freeze one draft's
310
+ * rules into the format.
311
+ *
312
+ * Recorded from `'headers'` up. It is object metadata a payload's consumer
313
+ * reads before the payload — timestamps, frame marking, a codec
314
+ * configuration — and before this key no level recorded it, so a trace could
315
+ * not show what decided how its media was decoded.
316
+ */
317
+ readonly properties?: Uint8Array;
301
318
  }
302
319
  interface ObjectPayloadEvent extends BaseEvent {
303
320
  readonly type: 'object-payload';
@@ -917,8 +934,13 @@ interface TraceRecorder {
917
934
  recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void;
918
935
  /** Record a stream-closed event. Ignored at 'control' detail level. */
919
936
  recordStreamClosed(streamId: bigint, errorCode?: number): void;
920
- /** Record an object header event. Ignored below 'headers' detail level. */
921
- recordObjectHeader(streamId: bigint, groupId: bigint, objectId: bigint, publisherPriority: number, objectStatus: number): void;
937
+ /**
938
+ * Record an object header event. Ignored below 'headers' detail level.
939
+ *
940
+ * `properties` is the Object's property block as the wire carried it; omit it
941
+ * when the Object had no properties field.
942
+ */
943
+ recordObjectHeader(streamId: bigint, groupId: bigint, objectId: bigint, publisherPriority: number, objectStatus: number, properties?: Uint8Array): void;
922
944
  /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
923
945
  recordObjectPayload(streamId: bigint, groupId: bigint, objectId: bigint, size: number, payload?: Uint8Array): void;
924
946
  /**
package/dist/index.js CHANGED
@@ -404,6 +404,7 @@ function eventToCbor(event) {
404
404
  base.o = event.objectId;
405
405
  base.pp = int(event.publisherPriority);
406
406
  base.os = int(event.objectStatus);
407
+ if (event.properties != null) base.props = event.properties;
407
408
  break;
408
409
  }
409
410
  case "object-payload": {
@@ -661,7 +662,8 @@ function decodeEvent(src) {
661
662
  groupId: requireUint(src, "g"),
662
663
  objectId: requireUint(src, "o"),
663
664
  publisherPriority: Number(take(src, "pp") ?? 0),
664
- objectStatus: Number(take(src, "os") ?? 0)
665
+ objectStatus: Number(take(src, "os") ?? 0),
666
+ ...optional(src, "props", "properties", asByteString)
665
667
  };
666
668
  case "object-payload":
667
669
  return {
@@ -1215,7 +1217,7 @@ function createRecorder(options) {
1215
1217
  errorCode
1216
1218
  });
1217
1219
  },
1218
- recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
1220
+ recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus, properties) {
1219
1221
  if (detailRank < DETAIL_RANK.headers) return;
1220
1222
  addEvent({
1221
1223
  type: "object-header",
@@ -1225,7 +1227,8 @@ function createRecorder(options) {
1225
1227
  groupId,
1226
1228
  objectId,
1227
1229
  publisherPriority,
1228
- objectStatus
1230
+ objectStatus,
1231
+ ...properties !== void 0 ? { properties } : {}
1229
1232
  });
1230
1233
  },
1231
1234
  recordObjectPayload(streamId, groupId, objectId, size, payload) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moqtap/trace",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "MoQT session trace format — record, serialize, and analyze .moqtrace files",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/binary.ts CHANGED
@@ -542,6 +542,7 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
542
542
  base.o = event.objectId
543
543
  base.pp = int(event.publisherPriority)
544
544
  base.os = int(event.objectStatus)
545
+ if (event.properties != null) base.props = event.properties
545
546
  break
546
547
  }
547
548
  case 'object-payload': {
@@ -986,6 +987,7 @@ function decodeEvent(src: Decoding): TraceEvent {
986
987
  objectId: requireUint(src, 'o'),
987
988
  publisherPriority: Number(take(src, 'pp') ?? 0),
988
989
  objectStatus: Number(take(src, 'os') ?? 0),
990
+ ...optional(src, 'props', 'properties', asByteString),
989
991
  }
990
992
 
991
993
  case 'object-payload':
package/src/recorder.ts CHANGED
@@ -104,13 +104,19 @@ export interface TraceRecorder {
104
104
  /** Record a stream-closed event. Ignored at 'control' detail level. */
105
105
  recordStreamClosed(streamId: bigint, errorCode?: number): void
106
106
 
107
- /** Record an object header event. Ignored below 'headers' detail level. */
107
+ /**
108
+ * Record an object header event. Ignored below 'headers' detail level.
109
+ *
110
+ * `properties` is the Object's property block as the wire carried it; omit it
111
+ * when the Object had no properties field.
112
+ */
108
113
  recordObjectHeader(
109
114
  streamId: bigint,
110
115
  groupId: bigint,
111
116
  objectId: bigint,
112
117
  publisherPriority: number,
113
118
  objectStatus: number,
119
+ properties?: Uint8Array,
114
120
  ): void
115
121
 
116
122
  /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
@@ -342,7 +348,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
342
348
  })
343
349
  },
344
350
 
345
- recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
351
+ recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus, properties) {
346
352
  if (detailRank < DETAIL_RANK.headers) return
347
353
  addEvent({
348
354
  type: 'object-header',
@@ -353,6 +359,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
353
359
  objectId,
354
360
  publisherPriority,
355
361
  objectStatus,
362
+ ...(properties !== undefined ? { properties } : {}),
356
363
  })
357
364
  },
358
365
 
package/src/types.ts CHANGED
@@ -319,6 +319,23 @@ export interface ObjectHeaderEvent extends BaseEvent {
319
319
  readonly objectId: bigint
320
320
  readonly publisherPriority: number
321
321
  readonly objectStatus: number
322
+ /**
323
+ * The Object's property block — Object Properties, Extension Headers before
324
+ * draft-17 — exactly as the wire carried it, past its length field.
325
+ *
326
+ * Absent when the Object had no properties field, which is not the same as an
327
+ * empty block: a subgroup stream with the PROPERTIES flag carries a zero
328
+ * length on every Object that has none. Kept as bytes rather than decoded
329
+ * because the encoding is the draft's, and a reader decodes it with the codec
330
+ * that reads the rest of the session; a decoded form would freeze one draft's
331
+ * rules into the format.
332
+ *
333
+ * Recorded from `'headers'` up. It is object metadata a payload's consumer
334
+ * reads before the payload — timestamps, frame marking, a codec
335
+ * configuration — and before this key no level recorded it, so a trace could
336
+ * not show what decided how its media was decoded.
337
+ */
338
+ readonly properties?: Uint8Array
322
339
  }
323
340
 
324
341
  export interface ObjectPayloadEvent extends BaseEvent {