@moqtap/codec 0.8.0 → 0.8.1

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.
Files changed (51) hide show
  1. package/dist/{chunk-5XZKO2U5.js → chunk-2XH6PHXR.js} +62 -51
  2. package/dist/{chunk-APTV6F72.js → chunk-3YAR3I3S.js} +67 -51
  3. package/dist/{chunk-BQHLUT37.cjs → chunk-7ENEWAMZ.cjs} +2 -6
  4. package/dist/{chunk-MD2OA7SE.js → chunk-BV6MITLO.js} +15 -8
  5. package/dist/{chunk-B56AG5HU.cjs → chunk-EVCPBFSR.cjs} +6 -4
  6. package/dist/{chunk-KNLPX3YA.js → chunk-H6DTAIWM.js} +6 -4
  7. package/dist/{chunk-J3ASFPCX.cjs → chunk-IADVBKEJ.cjs} +15 -8
  8. package/dist/{chunk-I55J3KXD.js → chunk-NH4L6NIY.js} +48 -33
  9. package/dist/{chunk-EQAYMCXO.cjs → chunk-PU2KV7VP.cjs} +48 -33
  10. package/dist/{chunk-HEO2JG6C.js → chunk-TMXRYOBS.js} +100 -92
  11. package/dist/{chunk-IEJULBKB.cjs → chunk-UA5XFP4O.cjs} +100 -92
  12. package/dist/{chunk-KTVN4P6J.cjs → chunk-UZO5Z2XT.cjs} +62 -51
  13. package/dist/{chunk-MH7537FB.cjs → chunk-VWXUT5R2.cjs} +67 -51
  14. package/dist/{chunk-JJEN2VG5.js → chunk-XQGEOFGE.js} +2 -6
  15. package/dist/{codec-BnamSx7L.d.cts → codec-N3g1lxQQ.d.cts} +1 -1
  16. package/dist/{codec-CFhUGTu2.d.ts → codec-v6Vloc7H.d.ts} +1 -1
  17. package/dist/draft12.cjs +2 -2
  18. package/dist/draft12.js +1 -1
  19. package/dist/draft14-session.d.cts +1 -1
  20. package/dist/draft14-session.d.ts +1 -1
  21. package/dist/draft14.cjs +2 -2
  22. package/dist/draft14.d.cts +3 -3
  23. package/dist/draft14.d.ts +3 -3
  24. package/dist/draft14.js +1 -1
  25. package/dist/draft15.cjs +2 -2
  26. package/dist/draft15.js +1 -1
  27. package/dist/draft16.cjs +2 -2
  28. package/dist/draft16.js +1 -1
  29. package/dist/draft17.cjs +2 -2
  30. package/dist/draft17.js +1 -1
  31. package/dist/draft18.cjs +2 -2
  32. package/dist/draft18.js +1 -1
  33. package/dist/draft19.cjs +2 -2
  34. package/dist/draft19.js +1 -1
  35. package/dist/index.cjs +14 -14
  36. package/dist/index.d.cts +2 -2
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +7 -7
  39. package/dist/session.d.cts +1 -1
  40. package/dist/session.d.ts +1 -1
  41. package/dist/{types-CVeKIk7A.d.cts → types-HI_bbXka.d.cts} +0 -2
  42. package/dist/{types-CVeKIk7A.d.ts → types-HI_bbXka.d.ts} +0 -2
  43. package/package.json +2 -2
  44. package/src/drafts/draft12/data-streams.ts +13 -4
  45. package/src/drafts/draft14/codec.ts +2 -6
  46. package/src/drafts/draft14/types.ts +0 -2
  47. package/src/drafts/draft15/data-streams.ts +30 -16
  48. package/src/drafts/draft16/codec.ts +116 -93
  49. package/src/drafts/draft17/codec.ts +72 -35
  50. package/src/drafts/draft18/codec.ts +89 -53
  51. package/src/drafts/draft19/codec.ts +94 -53
@@ -264,6 +264,47 @@ const PARAM_GROUP_ORDER = 0x22n
264
264
  const PARAM_NEW_GROUP_REQUEST = 0x32n
265
265
  const PARAM_TRACK_NAMESPACE_PREFIX = 0x34n
266
266
 
267
+ /**
268
+ * Which messages each Message Parameter's own definition names.
269
+ *
270
+ * Section 10.2.1: "Each Message Parameter definition indicates the message types in
271
+ * which it can appear. If it appears in some other type of message, the
272
+ * receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
273
+ * Drafts up to and including 16 end that sentence "it MUST be ignored", so this
274
+ * table has no counterpart there.
275
+ *
276
+ * A parameter type absent from this table is one no definition restricts. A
277
+ * type this draft does not define at all never reaches here: it is refused as
278
+ * unknown while its scope is still undecidable.
279
+ */
280
+ const PARAMETER_SCOPE = new Map<bigint, Set<string>>([
281
+ [0x02n, new Set(['request_ok', 'subscribe', 'request_update'])],
282
+ [
283
+ 0x03n,
284
+ new Set([
285
+ 'publish',
286
+ 'subscribe',
287
+ 'request_update',
288
+ 'subscribe_namespace',
289
+ 'subscribe_tracks',
290
+ 'publish_namespace',
291
+ 'track_status',
292
+ 'fetch',
293
+ ]),
294
+ ],
295
+ [0x04n, new Set(['subscribe'])],
296
+ [0x06n, new Set(['request_ok', 'subscribe', 'request_update'])],
297
+ [0x08n, new Set(['subscribe_ok', 'publish', 'request_ok'])],
298
+ [0x09n, new Set(['subscribe_ok', 'publish', 'request_ok'])],
299
+ [0x0an, new Set(['fetch'])],
300
+ [0x10n, new Set(['subscribe', 'request_update', 'publish', 'request_ok', 'subscribe_tracks'])],
301
+ [0x20n, new Set(['subscribe', 'fetch', 'request_update', 'request_ok'])],
302
+ [0x21n, new Set(['subscribe', 'request_ok', 'request_update'])],
303
+ [0x22n, new Set(['subscribe', 'request_ok', 'fetch'])],
304
+ [0x32n, new Set(['request_ok', 'subscribe', 'request_update'])],
305
+ [0x34n, new Set(['request_update'])],
306
+ ])
307
+
267
308
  function encodeParams(params: Draft18Params, writer: BufferWriter): void {
268
309
  // Collect and sort params by type
269
310
  const entries: Array<{ type: bigint; encode: (w: BufferWriter) => void }> = []
@@ -307,13 +348,12 @@ function encodeParams(params: Draft18Params, writer: BufferWriter): void {
307
348
  if (params.largest_object !== undefined) {
308
349
  entries.push({
309
350
  type: PARAM_LARGEST_OBJECT,
351
+ // A Location is "Two consecutive varints (Group, Object)", which is a
352
+ // different value encoding from "Length-prefixed". Nothing states the
353
+ // length, so nothing writes one.
310
354
  encode: (w) => {
311
- const tmpW = new BufferWriter(16)
312
- tmpW.writeVarInt(params.largest_object!.group)
313
- tmpW.writeVarInt(params.largest_object!.object)
314
- const raw = tmpW.finish()
315
- w.writeVarInt(BigInt(raw.byteLength))
316
- w.writeBytes(raw)
355
+ w.writeVarInt(params.largest_object!.group)
356
+ w.writeVarInt(params.largest_object!.object)
317
357
  },
318
358
  })
319
359
  }
@@ -370,12 +410,11 @@ function encodeParams(params: Draft18Params, writer: BufferWriter): void {
370
410
  if (params.track_namespace_prefix !== undefined) {
371
411
  entries.push({
372
412
  type: PARAM_TRACK_NAMESPACE_PREFIX,
413
+ // The parameter "uses the Track Namespace encoding", which is a tuple
414
+ // that states its own field count. That is not the Length-prefixed
415
+ // encoding, so there is no byte length ahead of it.
373
416
  encode: (w) => {
374
- const tmpW = new BufferWriter(32)
375
- encodeNamespaceTuple(params.track_namespace_prefix!, tmpW)
376
- const raw = tmpW.finish()
377
- w.writeVarInt(BigInt(raw.byteLength))
378
- w.writeBytes(raw)
417
+ encodeNamespaceTuple(params.track_namespace_prefix!, w)
379
418
  },
380
419
  })
381
420
  }
@@ -406,10 +445,9 @@ function encodeParams(params: Draft18Params, writer: BufferWriter): void {
406
445
  }
407
446
  }
408
447
 
409
- function decodeParams(reader: BufferReader): Draft18Params {
448
+ function decodeParams(reader: BufferReader, messageType: string): Draft18Params {
410
449
  const count = Number(reader.readVarInt())
411
450
  const result: Draft18Params = {}
412
- const unknown: UnknownParam[] = []
413
451
  let prevType = 0n
414
452
 
415
453
  for (let i = 0; i < count; i++) {
@@ -417,6 +455,15 @@ function decodeParams(reader: BufferReader): Draft18Params {
417
455
  const paramType = prevType + delta
418
456
  prevType = paramType
419
457
 
458
+ const scope = PARAMETER_SCOPE.get(paramType)
459
+ if (scope !== undefined && !scope.has(messageType)) {
460
+ throw new DecodeError(
461
+ 'CONSTRAINT_VIOLATION',
462
+ `Message Parameter 0x${paramType.toString(16)} may not appear in ${messageType}`,
463
+ reader.offset,
464
+ )
465
+ }
466
+
420
467
  if (paramType === PARAM_OBJECT_DELIVERY_TIMEOUT) {
421
468
  result.object_delivery_timeout = reader.readVarInt()
422
469
  } else if (paramType === PARAM_AUTHORIZATION_TOKEN) {
@@ -430,13 +477,9 @@ function decodeParams(reader: BufferReader): Draft18Params {
430
477
  } else if (paramType === PARAM_EXPIRES) {
431
478
  result.expires = reader.readVarInt()
432
479
  } else if (paramType === PARAM_LARGEST_OBJECT) {
433
- // Length-prefixed Location: 2 varints (Group, Object)
434
- const length = Number(reader.readVarInt())
435
- const startOff = reader.offset
480
+ // Location: 2 bare varints (not length-prefixed)
436
481
  const group = reader.readVarInt()
437
482
  const object = reader.readVarInt()
438
- const consumed = reader.offset - startOff
439
- if (consumed < length) reader.readBytes(length - consumed)
440
483
  result.largest_object = { group, object }
441
484
  } else if (paramType === PARAM_FILL_TIMEOUT) {
442
485
  result.fill_timeout = reader.readVarInt()
@@ -473,34 +516,27 @@ function decodeParams(reader: BufferReader): Draft18Params {
473
516
  } else if (paramType === PARAM_NEW_GROUP_REQUEST) {
474
517
  result.new_group_request = reader.readVarInt()
475
518
  } else if (paramType === PARAM_TRACK_NAMESPACE_PREFIX) {
476
- const length = Number(reader.readVarInt())
477
- const bytes = reader.readBytes(length)
478
- result.track_namespace_prefix = decodeNamespaceTuple(new BufferReader(bytes))
519
+ // Track Namespace encoding, read in place: the tuple's own field count
520
+ // bounds it.
521
+ result.track_namespace_prefix = decodeNamespaceTuple(reader)
479
522
  } else {
480
- // Unknown parameter heuristic encoding decode
481
- if (paramType % 2n === 0n) {
482
- const value = reader.readVarInt()
483
- const tmpWriter = new BufferWriter(16)
484
- tmpWriter.writeVarInt(value)
485
- const raw = tmpWriter.finish()
486
- unknown.push({
487
- id: `0x${paramType.toString(16)}`,
488
- length: raw.byteLength,
489
- raw_hex: bytesToHex(raw),
490
- })
491
- } else {
492
- const length = Number(reader.readVarInt())
493
- const bytes = reader.readBytes(length)
494
- unknown.push({
495
- id: `0x${paramType.toString(16)}`,
496
- length,
497
- raw_hex: bytesToHex(bytes),
498
- })
499
- }
523
+ // Drafts 16 and later: "All Message Parameters MUST be defined in the
524
+ // negotiated version of MOQT or negotiated via Setup Options. An
525
+ // endpoint that receives an unknown Message Parameter MUST close the
526
+ // session with PROTOCOL_VIOLATION." (Section 10.2)
527
+ //
528
+ // There is no skipping an unknown one and carrying on: the value's
529
+ // encoding comes from its definition, so a receiver that does not know
530
+ // the Type does not know how many bytes it spans either. Drafts 11
531
+ // through 15 say the opposite and keep collecting them.
532
+ throw new DecodeError(
533
+ 'INVALID_PARAMETER',
534
+ `Unknown Message Parameter type 0x${paramType.toString(16)}`,
535
+ reader.offset,
536
+ )
500
537
  }
501
538
  }
502
539
 
503
- if (unknown.length > 0) result.unknown = unknown
504
540
  return result
505
541
  }
506
542
 
@@ -843,7 +879,7 @@ function decodeSubscribePayload(r: BufferReader): Draft18Message {
843
879
  const request_id = r.readVarInt()
844
880
  const track_namespace = r.readTuple()
845
881
  const track_name = r.readString()
846
- const parameters = decodeParams(r)
882
+ const parameters = decodeParams(r, 'subscribe')
847
883
  return {
848
884
  type: 'subscribe',
849
885
  request_id,
@@ -855,14 +891,14 @@ function decodeSubscribePayload(r: BufferReader): Draft18Message {
855
891
 
856
892
  function decodeSubscribeOkPayload(r: BufferReader, payloadEnd: number): Draft18Message {
857
893
  const track_alias = r.readVarInt()
858
- const parameters = decodeParams(r)
894
+ const parameters = decodeParams(r, 'subscribe_ok')
859
895
  const track_properties = decodeTrackProperties(r, payloadEnd)
860
896
  return { type: 'subscribe_ok', track_alias, parameters, track_properties }
861
897
  }
862
898
 
863
899
  function decodeRequestUpdatePayload(r: BufferReader): Draft18Message {
864
900
  const request_id = r.readVarInt()
865
- const parameters = decodeParams(r)
901
+ const parameters = decodeParams(r, 'request_update')
866
902
  return {
867
903
  type: 'request_update',
868
904
  request_id,
@@ -875,7 +911,7 @@ function decodePublishPayload(r: BufferReader, payloadEnd: number): Draft18Messa
875
911
  const track_namespace = r.readTuple()
876
912
  const track_name = r.readString()
877
913
  const track_alias = r.readVarInt()
878
- const parameters = decodeParams(r)
914
+ const parameters = decodeParams(r, 'publish')
879
915
  const track_properties = decodeTrackProperties(r, payloadEnd)
880
916
  return {
881
917
  type: 'publish',
@@ -898,7 +934,7 @@ function decodePublishDonePayload(r: BufferReader): Draft18Message {
898
934
  function decodePublishNamespacePayload(r: BufferReader): Draft18Message {
899
935
  const request_id = r.readVarInt()
900
936
  const track_namespace = r.readTuple()
901
- const parameters = decodeParams(r)
937
+ const parameters = decodeParams(r, 'publish_namespace')
902
938
  return {
903
939
  type: 'publish_namespace',
904
940
  request_id,
@@ -920,7 +956,7 @@ function decodeNamespaceDonePayload(r: BufferReader): Draft18Message {
920
956
  function decodeSubscribeNamespacePayload(r: BufferReader): Draft18Message {
921
957
  const request_id = r.readVarInt()
922
958
  const namespace_prefix = r.readTuple()
923
- const parameters = decodeParams(r)
959
+ const parameters = decodeParams(r, 'subscribe_namespace')
924
960
  return {
925
961
  type: 'subscribe_namespace',
926
962
  request_id,
@@ -932,7 +968,7 @@ function decodeSubscribeNamespacePayload(r: BufferReader): Draft18Message {
932
968
  function decodeSubscribeTracksPayload(r: BufferReader): Draft18Message {
933
969
  const request_id = r.readVarInt()
934
970
  const namespace_prefix = r.readTuple()
935
- const parameters = decodeParams(r)
971
+ const parameters = decodeParams(r, 'subscribe_tracks')
936
972
  return {
937
973
  type: 'subscribe_tracks',
938
974
  request_id,
@@ -980,7 +1016,7 @@ function decodeFetchPayload(r: BufferReader): Draft18Message {
980
1016
  joining = { joining_request_id, joining_start }
981
1017
  }
982
1018
 
983
- const parameters = decodeParams(r)
1019
+ const parameters = decodeParams(r, 'fetch')
984
1020
 
985
1021
  return {
986
1022
  type: 'fetch',
@@ -996,7 +1032,7 @@ function decodeFetchOkPayload(r: BufferReader, payloadEnd: number): Draft18Messa
996
1032
  const end_of_track = r.readUint8()
997
1033
  const end_group = r.readVarInt()
998
1034
  const end_object = r.readVarInt()
999
- const parameters = decodeParams(r)
1035
+ const parameters = decodeParams(r, 'fetch_ok')
1000
1036
  const track_properties = decodeTrackProperties(r, payloadEnd)
1001
1037
  return {
1002
1038
  type: 'fetch_ok',
@@ -1012,7 +1048,7 @@ function decodeTrackStatusPayload(r: BufferReader): Draft18Message {
1012
1048
  const request_id = r.readVarInt()
1013
1049
  const track_namespace = r.readTuple()
1014
1050
  const track_name = r.readString()
1015
- const parameters = decodeParams(r)
1051
+ const parameters = decodeParams(r, 'track_status')
1016
1052
  return {
1017
1053
  type: 'track_status',
1018
1054
  request_id,
@@ -1023,7 +1059,7 @@ function decodeTrackStatusPayload(r: BufferReader): Draft18Message {
1023
1059
  }
1024
1060
 
1025
1061
  function decodeRequestOkPayload(r: BufferReader, payloadEnd: number): Draft18Message {
1026
- const parameters = decodeParams(r)
1062
+ const parameters = decodeParams(r, 'request_ok')
1027
1063
  const track_properties = decodeTrackProperties(r, payloadEnd)
1028
1064
  return { type: 'request_ok', parameters, track_properties }
1029
1065
  }
@@ -347,6 +347,52 @@ function decodeRangeFilter(reader: BufferReader, hasProperty: boolean): RangeFil
347
347
  return property_type !== undefined ? { set_id, property_type, ranges } : { set_id, ranges }
348
348
  }
349
349
 
350
+ /**
351
+ * Which messages each Message Parameter's own definition names.
352
+ *
353
+ * Section 10.2.1: "Each Message Parameter definition indicates the message types in
354
+ * which it can appear. If it appears in some other type of message, the
355
+ * receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
356
+ * Drafts up to and including 16 end that sentence "it MUST be ignored", so this
357
+ * table has no counterpart there.
358
+ *
359
+ * A parameter type absent from this table is one no definition restricts. A
360
+ * type this draft does not define at all never reaches here: it is refused as
361
+ * unknown while its scope is still undecidable.
362
+ */
363
+ const PARAMETER_SCOPE = new Map<bigint, Set<string>>([
364
+ [0x02n, new Set(['request_ok', 'subscribe', 'request_update', 'subscribe_tracks'])],
365
+ [
366
+ 0x03n,
367
+ new Set([
368
+ 'publish',
369
+ 'subscribe',
370
+ 'request_update',
371
+ 'subscribe_namespace',
372
+ 'subscribe_tracks',
373
+ 'publish_namespace',
374
+ 'track_status',
375
+ 'fetch',
376
+ ]),
377
+ ],
378
+ [0x04n, new Set(['subscribe', 'subscribe_tracks'])],
379
+ [0x06n, new Set(['request_ok', 'subscribe', 'request_update', 'subscribe_tracks'])],
380
+ [0x08n, new Set(['subscribe_ok', 'publish', 'request_ok'])],
381
+ [0x09n, new Set(['subscribe_ok', 'publish', 'request_ok'])],
382
+ [0x0an, new Set(['fetch'])],
383
+ [0x10n, new Set(['subscribe', 'request_update', 'publish', 'request_ok', 'subscribe_tracks'])],
384
+ [0x20n, new Set(['subscribe', 'fetch', 'request_update', 'request_ok', 'subscribe_tracks'])],
385
+ [0x21n, new Set(['subscribe', 'request_ok', 'request_update', 'subscribe_tracks'])],
386
+ [0x22n, new Set(['subscribe', 'subscribe_tracks', 'fetch'])],
387
+ [0x25n, new Set(['fetch', 'subscribe', 'subscribe_tracks', 'request_ok', 'request_update'])],
388
+ [0x26n, new Set(['fetch', 'subscribe', 'subscribe_tracks', 'request_ok', 'request_update'])],
389
+ [0x27n, new Set(['fetch', 'subscribe', 'subscribe_tracks', 'request_ok', 'request_update'])],
390
+ [0x28n, new Set(['fetch', 'subscribe', 'subscribe_tracks', 'request_ok', 'request_update'])],
391
+ [0x29n, new Set(['subscribe_tracks', 'request_update'])],
392
+ [0x32n, new Set(['request_ok', 'subscribe', 'request_update', 'subscribe_tracks'])],
393
+ [0x34n, new Set(['request_update'])],
394
+ ])
395
+
350
396
  function encodeParams(params: Draft19Params, writer: BufferWriter): void {
351
397
  // Collect and sort params by type
352
398
  const entries: Array<{ type: bigint; encode: (w: BufferWriter) => void }> = []
@@ -390,13 +436,12 @@ function encodeParams(params: Draft19Params, writer: BufferWriter): void {
390
436
  if (params.largest_object !== undefined) {
391
437
  entries.push({
392
438
  type: PARAM_LARGEST_OBJECT,
439
+ // A Location is "Two consecutive varints (Group, Object)", which is a
440
+ // different value encoding from "Length-prefixed". Nothing states the
441
+ // length, so nothing writes one.
393
442
  encode: (w) => {
394
- const tmpW = new BufferWriter(16)
395
- tmpW.writeVarInt(params.largest_object!.group)
396
- tmpW.writeVarInt(params.largest_object!.object)
397
- const raw = tmpW.finish()
398
- w.writeVarInt(BigInt(raw.byteLength))
399
- w.writeBytes(raw)
443
+ w.writeVarInt(params.largest_object!.group)
444
+ w.writeVarInt(params.largest_object!.object)
400
445
  },
401
446
  })
402
447
  }
@@ -483,12 +528,11 @@ function encodeParams(params: Draft19Params, writer: BufferWriter): void {
483
528
  if (params.track_namespace_prefix !== undefined) {
484
529
  entries.push({
485
530
  type: PARAM_TRACK_NAMESPACE_PREFIX,
531
+ // The parameter "uses the Track Namespace encoding", which is a tuple
532
+ // that states its own field count. That is not the Length-prefixed
533
+ // encoding, so there is no byte length ahead of it.
486
534
  encode: (w) => {
487
- const tmpW = new BufferWriter(32)
488
- encodeNamespaceTuple(params.track_namespace_prefix!, tmpW)
489
- const raw = tmpW.finish()
490
- w.writeVarInt(BigInt(raw.byteLength))
491
- w.writeBytes(raw)
535
+ encodeNamespaceTuple(params.track_namespace_prefix!, w)
492
536
  },
493
537
  })
494
538
  }
@@ -519,10 +563,9 @@ function encodeParams(params: Draft19Params, writer: BufferWriter): void {
519
563
  }
520
564
  }
521
565
 
522
- function decodeParams(reader: BufferReader): Draft19Params {
566
+ function decodeParams(reader: BufferReader, messageType: string): Draft19Params {
523
567
  const count = Number(reader.readVarInt())
524
568
  const result: Draft19Params = {}
525
- const unknown: UnknownParam[] = []
526
569
  let prevType = 0n
527
570
 
528
571
  for (let i = 0; i < count; i++) {
@@ -530,6 +573,15 @@ function decodeParams(reader: BufferReader): Draft19Params {
530
573
  const paramType = prevType + delta
531
574
  prevType = paramType
532
575
 
576
+ const scope = PARAMETER_SCOPE.get(paramType)
577
+ if (scope !== undefined && !scope.has(messageType)) {
578
+ throw new DecodeError(
579
+ 'CONSTRAINT_VIOLATION',
580
+ `Message Parameter 0x${paramType.toString(16)} may not appear in ${messageType}`,
581
+ reader.offset,
582
+ )
583
+ }
584
+
533
585
  if (paramType === PARAM_OBJECT_DELIVERY_TIMEOUT) {
534
586
  result.object_delivery_timeout = reader.readVarInt()
535
587
  } else if (paramType === PARAM_AUTHORIZATION_TOKEN) {
@@ -543,13 +595,9 @@ function decodeParams(reader: BufferReader): Draft19Params {
543
595
  } else if (paramType === PARAM_EXPIRES) {
544
596
  result.expires = reader.readVarInt()
545
597
  } else if (paramType === PARAM_LARGEST_OBJECT) {
546
- // Length-prefixed Location: 2 varints (Group, Object)
547
- const length = Number(reader.readVarInt())
548
- const startOff = reader.offset
598
+ // Location: 2 bare varints (not length-prefixed)
549
599
  const group = reader.readVarInt()
550
600
  const object = reader.readVarInt()
551
- const consumed = reader.offset - startOff
552
- if (consumed < length) reader.readBytes(length - consumed)
553
601
  result.largest_object = { group, object }
554
602
  } else if (paramType === PARAM_FILL_TIMEOUT) {
555
603
  result.fill_timeout = reader.readVarInt()
@@ -596,34 +644,27 @@ function decodeParams(reader: BufferReader): Draft19Params {
596
644
  } else if (paramType === PARAM_NEW_GROUP_REQUEST) {
597
645
  result.new_group_request = reader.readVarInt()
598
646
  } else if (paramType === PARAM_TRACK_NAMESPACE_PREFIX) {
599
- const length = Number(reader.readVarInt())
600
- const bytes = reader.readBytes(length)
601
- result.track_namespace_prefix = decodeNamespaceTuple(new BufferReader(bytes))
647
+ // Track Namespace encoding, read in place: the tuple's own field count
648
+ // bounds it.
649
+ result.track_namespace_prefix = decodeNamespaceTuple(reader)
602
650
  } else {
603
- // Unknown parameter heuristic encoding decode
604
- if (paramType % 2n === 0n) {
605
- const value = reader.readVarInt()
606
- const tmpWriter = new BufferWriter(16)
607
- tmpWriter.writeVarInt(value)
608
- const raw = tmpWriter.finish()
609
- unknown.push({
610
- id: `0x${paramType.toString(16)}`,
611
- length: raw.byteLength,
612
- raw_hex: bytesToHex(raw),
613
- })
614
- } else {
615
- const length = Number(reader.readVarInt())
616
- const bytes = reader.readBytes(length)
617
- unknown.push({
618
- id: `0x${paramType.toString(16)}`,
619
- length,
620
- raw_hex: bytesToHex(bytes),
621
- })
622
- }
651
+ // Drafts 16 and later: "All Message Parameters MUST be defined in the
652
+ // negotiated version of MOQT or negotiated via Setup Options. An
653
+ // endpoint that receives an unknown Message Parameter MUST close the
654
+ // session with PROTOCOL_VIOLATION." (Section 10.2)
655
+ //
656
+ // There is no skipping an unknown one and carrying on: the value's
657
+ // encoding comes from its definition, so a receiver that does not know
658
+ // the Type does not know how many bytes it spans either. Drafts 11
659
+ // through 15 say the opposite and keep collecting them.
660
+ throw new DecodeError(
661
+ 'INVALID_PARAMETER',
662
+ `Unknown Message Parameter type 0x${paramType.toString(16)}`,
663
+ reader.offset,
664
+ )
623
665
  }
624
666
  }
625
667
 
626
- if (unknown.length > 0) result.unknown = unknown
627
668
  return result
628
669
  }
629
670
 
@@ -964,7 +1005,7 @@ function decodeSubscribePayload(r: BufferReader): Draft19Message {
964
1005
  const request_id = r.readVarInt()
965
1006
  const track_namespace = r.readTuple()
966
1007
  const track_name = r.readString()
967
- const parameters = decodeParams(r)
1008
+ const parameters = decodeParams(r, 'subscribe')
968
1009
  return {
969
1010
  type: 'subscribe',
970
1011
  request_id,
@@ -976,14 +1017,14 @@ function decodeSubscribePayload(r: BufferReader): Draft19Message {
976
1017
 
977
1018
  function decodeSubscribeOkPayload(r: BufferReader, payloadEnd: number): Draft19Message {
978
1019
  const track_alias = r.readVarInt()
979
- const parameters = decodeParams(r)
1020
+ const parameters = decodeParams(r, 'subscribe_ok')
980
1021
  const track_properties = decodeTrackProperties(r, payloadEnd)
981
1022
  return { type: 'subscribe_ok', track_alias, parameters, track_properties }
982
1023
  }
983
1024
 
984
1025
  function decodeRequestUpdatePayload(r: BufferReader): Draft19Message {
985
1026
  const request_id = r.readVarInt()
986
- const parameters = decodeParams(r)
1027
+ const parameters = decodeParams(r, 'request_update')
987
1028
  return {
988
1029
  type: 'request_update',
989
1030
  request_id,
@@ -996,7 +1037,7 @@ function decodePublishPayload(r: BufferReader, payloadEnd: number): Draft19Messa
996
1037
  const track_namespace = r.readTuple()
997
1038
  const track_name = r.readString()
998
1039
  const track_alias = r.readVarInt()
999
- const parameters = decodeParams(r)
1040
+ const parameters = decodeParams(r, 'publish')
1000
1041
  const track_properties = decodeTrackProperties(r, payloadEnd)
1001
1042
  return {
1002
1043
  type: 'publish',
@@ -1019,7 +1060,7 @@ function decodePublishDonePayload(r: BufferReader): Draft19Message {
1019
1060
  function decodePublishNamespacePayload(r: BufferReader): Draft19Message {
1020
1061
  const request_id = r.readVarInt()
1021
1062
  const track_namespace = r.readTuple()
1022
- const parameters = decodeParams(r)
1063
+ const parameters = decodeParams(r, 'publish_namespace')
1023
1064
  return {
1024
1065
  type: 'publish_namespace',
1025
1066
  request_id,
@@ -1041,7 +1082,7 @@ function decodeNamespaceDonePayload(r: BufferReader): Draft19Message {
1041
1082
  function decodeSubscribeNamespacePayload(r: BufferReader): Draft19Message {
1042
1083
  const request_id = r.readVarInt()
1043
1084
  const namespace_prefix = r.readTuple()
1044
- const parameters = decodeParams(r)
1085
+ const parameters = decodeParams(r, 'subscribe_namespace')
1045
1086
  return {
1046
1087
  type: 'subscribe_namespace',
1047
1088
  request_id,
@@ -1053,7 +1094,7 @@ function decodeSubscribeNamespacePayload(r: BufferReader): Draft19Message {
1053
1094
  function decodeSubscribeTracksPayload(r: BufferReader): Draft19Message {
1054
1095
  const request_id = r.readVarInt()
1055
1096
  const namespace_prefix = r.readTuple()
1056
- const parameters = decodeParams(r)
1097
+ const parameters = decodeParams(r, 'subscribe_tracks')
1057
1098
  return {
1058
1099
  type: 'subscribe_tracks',
1059
1100
  request_id,
@@ -1101,7 +1142,7 @@ function decodeFetchPayload(r: BufferReader): Draft19Message {
1101
1142
  joining = { joining_request_id, joining_start }
1102
1143
  }
1103
1144
 
1104
- const parameters = decodeParams(r)
1145
+ const parameters = decodeParams(r, 'fetch')
1105
1146
 
1106
1147
  return {
1107
1148
  type: 'fetch',
@@ -1117,7 +1158,7 @@ function decodeFetchOkPayload(r: BufferReader, payloadEnd: number): Draft19Messa
1117
1158
  const end_of_track = r.readUint8()
1118
1159
  const end_group = r.readVarInt()
1119
1160
  const end_object = r.readVarInt()
1120
- const parameters = decodeParams(r)
1161
+ const parameters = decodeParams(r, 'fetch_ok')
1121
1162
  const track_properties = decodeTrackProperties(r, payloadEnd)
1122
1163
  return {
1123
1164
  type: 'fetch_ok',
@@ -1133,7 +1174,7 @@ function decodeTrackStatusPayload(r: BufferReader): Draft19Message {
1133
1174
  const request_id = r.readVarInt()
1134
1175
  const track_namespace = r.readTuple()
1135
1176
  const track_name = r.readString()
1136
- const parameters = decodeParams(r)
1177
+ const parameters = decodeParams(r, 'track_status')
1137
1178
  return {
1138
1179
  type: 'track_status',
1139
1180
  request_id,
@@ -1144,7 +1185,7 @@ function decodeTrackStatusPayload(r: BufferReader): Draft19Message {
1144
1185
  }
1145
1186
 
1146
1187
  function decodeRequestOkPayload(r: BufferReader, payloadEnd: number): Draft19Message {
1147
- const parameters = decodeParams(r)
1188
+ const parameters = decodeParams(r, 'request_ok')
1148
1189
  const track_properties = decodeTrackProperties(r, payloadEnd)
1149
1190
  return { type: 'request_ok', parameters, track_properties }
1150
1191
  }