@blockcast/mmt-container 0.4.1 → 0.5.0-main.34c7b9c2cfa0
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 +2 -2
- package/dist/browser-runtime.js +3769 -364
- package/dist/browser-runtime.js.map +4 -4
- package/dist/cmcd-producer.d.ts +5 -1
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +18 -2
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/fec-manager.d.ts +21 -16
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +197 -80
- package/dist/fec-manager.js.map +3 -3
- package/dist/held-layer-admission.d.ts +58 -0
- package/dist/held-layer-admission.d.ts.map +1 -0
- package/dist/held-layer-admission.js +105 -0
- package/dist/held-layer-admission.js.map +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +10 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +31 -1
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +2713 -1024
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js +31 -1
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-fec-session.d.ts +36 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +114 -5
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +96 -29
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +522 -205
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-auth-catalog.d.ts +10 -2
- package/dist/multicast-auth-catalog.d.ts.map +1 -1
- package/dist/multicast-auth-catalog.js +422 -22
- package/dist/multicast-auth-catalog.js.map +4 -4
- package/dist/multicast-manager.d.ts +78 -11
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +3663 -333
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +4 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +20 -3
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.d.ts +1 -2
- package/dist/no-modules.d.ts.map +1 -1
- package/dist/no-modules.js +2699 -1008
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.d.ts +50 -10
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +62 -20
- package/dist/repair-fetch.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +31 -1
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +7 -15
- package/src/cmcd-producer.ts +6 -1
- package/src/fec-manager.ts +147 -131
- package/src/held-layer-admission.ts +161 -0
- package/src/index.ts +6 -9
- package/src/mmtp-container-parser.ts +48 -0
- package/src/mmtp-fec-session.ts +76 -3
- package/src/mmtp-router.ts +637 -274
- package/src/multicast-auth-catalog.ts +81 -8
- package/src/multicast-manager.ts +274 -49
- package/src/multicast-transport.ts +6 -2
- package/src/no-modules.ts +1 -6
- package/src/repair-fetch.ts +154 -34
- package/test-vectors/catalog/negative/multicast-auth-padded-key.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-root-dotted-key.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +0 -1
- package/test-vectors/repair-policy-v1.json +113 -0
- package/dist/manifest-auth-plan.d.ts +0 -31
- package/dist/manifest-auth-plan.d.ts.map +0 -1
- package/dist/manifest-auth-plan.js +0 -426
- package/dist/manifest-auth-plan.js.map +0 -7
- package/dist/source-auth.d.ts +0 -2
- package/dist/source-auth.d.ts.map +0 -1
- package/dist/source-auth.js +0 -3912
- package/dist/source-auth.js.map +0 -7
- package/src/manifest-auth-plan.ts +0 -55
- package/src/source-auth.ts +0 -1
- package/test-vectors/generate-source-auth-fixture.mjs +0 -114
- package/test-vectors/source-auth-v1.json +0 -61
package/src/repair-fetch.ts
CHANGED
|
@@ -22,15 +22,23 @@
|
|
|
22
22
|
* everything.
|
|
23
23
|
*
|
|
24
24
|
* Priority (from the block's class, see `repairPriorityForClass`):
|
|
25
|
-
* - P0 (keyframe / unknown)
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
25
|
+
* - P0 (keyframe / unknown) dispatches as soon as it is enqueued, with no
|
|
26
|
+
* cap. Each has its own fail deadline, and a cap would turn a
|
|
27
|
+
* recoverable protected block into a stall; their number is already
|
|
28
|
+
* bounded by the blocks one fail window can hold. A P0 block that lies
|
|
29
|
+
* below a RAP the receiver already holds complete is not requested at
|
|
30
|
+
* all (`moot-rap`), and a P0 FETCH still running when such a RAP
|
|
31
|
+
* arrives is cancelled (`repair-stopped`, `moot`): the receiver resumes
|
|
32
|
+
* from that RAP (draft-ramadan-moq-fec §11.4.2).
|
|
33
|
+
* - P1 (audio) dispatches, uncapped, while no P0 is queued or in flight;
|
|
34
|
+
* P2 (delta) only while no P0/P1 is, earliest absolute fail deadline
|
|
35
|
+
* first (§11.4.5 item 3).
|
|
36
|
+
* - Only a P0 FETCH the scheduler actually sends aborts the in-flight P1
|
|
37
|
+
* and P2 FETCHes (`repair-preempted`), which return to the queue while
|
|
38
|
+
* their blocks are unresolved: a keyframe request that is refused, moot
|
|
39
|
+
* or otherwise never sent cancels nothing (§11.4.5 item 3), and an audio
|
|
40
|
+
* request never preempts. A P2 that never ran before its block resolved
|
|
41
|
+
* is reported as `repair-deferred`.
|
|
34
42
|
* - A block reclassified to a higher class while its P2 fetch is in flight
|
|
35
43
|
* is re-issued at once under the new class's wire priority: the running
|
|
36
44
|
* FETCH is aborted (FETCH_CANCEL) and a new one sent.
|
|
@@ -394,6 +402,18 @@ export type RepairSkipReason =
|
|
|
394
402
|
| 'presentation-delay-unavailable'
|
|
395
403
|
/** A lost SS_ID in an undelimited span had no §7.5 anchor to extend it from. */
|
|
396
404
|
| 'range-no-anchor'
|
|
405
|
+
/**
|
|
406
|
+
* The block lost more sources than its repair group carries symbols
|
|
407
|
+
* (sources + RSB_length < K'): no FETCH of the group can complete it, and
|
|
408
|
+
* when multicast already delivered the whole group the relay could serve
|
|
409
|
+
* only duplicates (BLO-50082).
|
|
410
|
+
*/
|
|
411
|
+
| 'group-cannot-complete'
|
|
412
|
+
/**
|
|
413
|
+
* The keyframe-bearing block lies below a RAP the receiver already holds
|
|
414
|
+
* complete, so its repair would be moot (draft-ramadan-moq-fec §11.4.2).
|
|
415
|
+
*/
|
|
416
|
+
| 'moot-rap'
|
|
397
417
|
|
|
398
418
|
/**
|
|
399
419
|
* Which term set a FETCH's deadline (BLO-33597): the multicast wait, the
|
|
@@ -477,6 +497,8 @@ export interface RepairFetchHost {
|
|
|
477
497
|
closeIsoHoles?(packetId: number, catalogTrackName: string | undefined, start: number, length: number): void
|
|
478
498
|
clearRepairForBlock(sbn: number, packetId: number, catalogTrackName?: string): void
|
|
479
499
|
processRepairFromMoQ(packet: Uint8Array, ownerPacketId?: number, catalogTrackName?: string): void
|
|
500
|
+
/** This scheduler can request unicast repair: protected flows keep pending entries for it (optional). */
|
|
501
|
+
setUnicastRepair?(enabled: boolean): void
|
|
480
502
|
noteRepairScheduling(
|
|
481
503
|
type: 'repair-preempted' | 'repair-deferred',
|
|
482
504
|
sbn: number,
|
|
@@ -504,6 +526,23 @@ export interface RepairFetchHost {
|
|
|
504
526
|
packetId: number,
|
|
505
527
|
catalogTrackName?: string,
|
|
506
528
|
): void
|
|
529
|
+
/** A running FETCH the scheduler stopped, and why (optional). */
|
|
530
|
+
noteRepairStopped?(
|
|
531
|
+
reason: RepairStopReason,
|
|
532
|
+
sbn: number,
|
|
533
|
+
packetId: number,
|
|
534
|
+
catalogTrackName?: string,
|
|
535
|
+
): void
|
|
536
|
+
/**
|
|
537
|
+
* Whether a complete RAP above SS_ID `ssId` already reached the stream
|
|
538
|
+
* (optional; absent, none ever has). Under the bc-provenance profile only
|
|
539
|
+
* an admitted RAP counts (multicast-01 §7.2.7).
|
|
540
|
+
*/
|
|
541
|
+
rapCompleteAbove?(packetId: number, catalogTrackName: string | undefined, ssId: number): boolean
|
|
542
|
+
/** Observe each complete RAP reaching a stream (optional; called synchronously). */
|
|
543
|
+
addRapCompleteObserver?(
|
|
544
|
+
observer: (packetId: number, catalogTrackName: string | undefined, ssId: number) => void,
|
|
545
|
+
): () => void
|
|
507
546
|
/**
|
|
508
547
|
* A FETCH the scheduler admitted and handed to the transport (optional):
|
|
509
548
|
* every attempt, a retry or a reprioritised restart included.
|
|
@@ -517,7 +556,22 @@ export interface RepairFetchHost {
|
|
|
517
556
|
): void
|
|
518
557
|
}
|
|
519
558
|
|
|
520
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Why the scheduler ended a running FETCH (the transport cancels it on the
|
|
561
|
+
* wire): preempted by a sent keyframe FETCH, reissued at a new class's
|
|
562
|
+
* priority, past its deadline, its block resolved, track reset or a newer
|
|
563
|
+
* request for the block, dispose, or `moot` (a RAP above the block arrived
|
|
564
|
+
* complete first, draft-ramadan-moq-fec §11.4.2).
|
|
565
|
+
*/
|
|
566
|
+
export type RepairStopReason =
|
|
567
|
+
| 'preempted'
|
|
568
|
+
| 'reprioritized'
|
|
569
|
+
| 'expired'
|
|
570
|
+
| 'resolved'
|
|
571
|
+
| 'reset'
|
|
572
|
+
| 'disposed'
|
|
573
|
+
| 'moot'
|
|
574
|
+
type StopReason = RepairStopReason
|
|
521
575
|
|
|
522
576
|
interface FetchRun {
|
|
523
577
|
readonly controller: AbortController
|
|
@@ -592,6 +646,7 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
592
646
|
) {
|
|
593
647
|
throw new Error('[RepairFetch] rangeTransport needs repairEpochReference and a host that reports undelimited losses')
|
|
594
648
|
}
|
|
649
|
+
mc.setUnicastRepair?.(true)
|
|
595
650
|
const entries = new Map<string, RepairEntry>()
|
|
596
651
|
const warnedNoTarget = new Set<string>()
|
|
597
652
|
let disposed = false
|
|
@@ -629,9 +684,43 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
629
684
|
entry.running = null
|
|
630
685
|
run.stopped = reason
|
|
631
686
|
run.controller.abort()
|
|
632
|
-
// A preempted
|
|
687
|
+
// A preempted request returns to the queue; until it runs again nothing is
|
|
633
688
|
// in flight that could land, so the block goes back to its multicast wait.
|
|
634
689
|
if (reason === 'preempted') releaseHold(entry)
|
|
690
|
+
mc.noteRepairStopped?.(reason, entry.sbn, entry.packetId, entry.catalogTrackName)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** The last SS_ID the entry covers, or undefined while its block is undelimited. */
|
|
694
|
+
const lastSsId = (entry: RepairEntry): number | undefined => {
|
|
695
|
+
if (entry.range) return entry.sbn + entry.range.holeLength - 1
|
|
696
|
+
const length = symbolCounts(entry.sbn, entry.packetId, entry.catalogTrackName)?.authoredLength
|
|
697
|
+
return length === undefined ? undefined : entry.sbn + length - 1
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** A keyframe request whose whole block lies below a RAP already held complete. */
|
|
701
|
+
const moot = (entry: RepairEntry): boolean => {
|
|
702
|
+
if (entry.priority !== 0) return false
|
|
703
|
+
const last = lastSsId(entry)
|
|
704
|
+
if (last === undefined) return false
|
|
705
|
+
try {
|
|
706
|
+
return mc.rapCompleteAbove?.(entry.packetId, entry.catalogTrackName, last) === true
|
|
707
|
+
} catch {
|
|
708
|
+
return false
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** Drop a request no FETCH will be sent for, and say why. */
|
|
713
|
+
const skip = (entry: RepairEntry, reason: RepairSkipReason, rttMs: number | null, budgetMs: number): void => {
|
|
714
|
+
retire(entry)
|
|
715
|
+
releaseHold(entry)
|
|
716
|
+
clearEntry(entry)
|
|
717
|
+
mc.noteRepairSkipped(
|
|
718
|
+
reason,
|
|
719
|
+
{ rttMs, budgetMs, priority: entry.priority },
|
|
720
|
+
entry.sbn,
|
|
721
|
+
entry.packetId,
|
|
722
|
+
entry.catalogTrackName,
|
|
723
|
+
)
|
|
635
724
|
}
|
|
636
725
|
|
|
637
726
|
/** The keyframe budget for a P0 block, or null: absent, throwing or not positive. */
|
|
@@ -656,24 +745,24 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
656
745
|
}
|
|
657
746
|
|
|
658
747
|
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
748
|
+
* Admission: send only a FETCH whose repair group can complete the block
|
|
749
|
+
* and that the measured round trip says can land before the entry's
|
|
750
|
+
* deadline. A refused entry is released, not queued: the
|
|
661
751
|
* deadline only gets closer. Returns the admitting RTT, or null when refused.
|
|
662
752
|
*/
|
|
663
753
|
const admit = (entry: RepairEntry, now: number): number | null => {
|
|
664
754
|
const budgetMs = entry.deadlineAt - now
|
|
665
755
|
const rttMs = readRtt()
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
)
|
|
756
|
+
const reason: RepairSkipReason | null =
|
|
757
|
+
!entry.range && !groupCanComplete(entry.sbn, entry.packetId, entry.catalogTrackName)
|
|
758
|
+
? 'group-cannot-complete'
|
|
759
|
+
: rttMs === null
|
|
760
|
+
? 'rtt-unmeasured'
|
|
761
|
+
: rttMs < budgetMs
|
|
762
|
+
? null
|
|
763
|
+
: 'rtt-exceeds-budget'
|
|
764
|
+
if (reason === null) return rttMs
|
|
765
|
+
skip(entry, reason, rttMs, budgetMs)
|
|
677
766
|
return null
|
|
678
767
|
}
|
|
679
768
|
|
|
@@ -682,16 +771,21 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
682
771
|
if (entry.expiry !== undefined) clearTimeout(entry.expiry)
|
|
683
772
|
}
|
|
684
773
|
|
|
685
|
-
|
|
774
|
+
/** Whether any entry of priority below `priority` is queued or in flight. */
|
|
775
|
+
const outstandingBelow = (priority: RepairPriority): boolean => {
|
|
686
776
|
for (const entry of entries.values()) {
|
|
687
|
-
if (entry.priority <
|
|
777
|
+
if (entry.priority < priority) return true
|
|
688
778
|
}
|
|
689
779
|
return false
|
|
690
780
|
}
|
|
691
781
|
|
|
692
|
-
|
|
782
|
+
/**
|
|
783
|
+
* A keyframe FETCH is being sent: cancel every in-flight audio and delta
|
|
784
|
+
* FETCH (§11.4.5 item 3). Called only for a FETCH actually sent.
|
|
785
|
+
*/
|
|
786
|
+
const preemptBestEffort = (): void => {
|
|
693
787
|
for (const entry of entries.values()) {
|
|
694
|
-
if (entry.priority
|
|
788
|
+
if (entry.priority === 0 || !entry.running) continue
|
|
695
789
|
stop(entry, 'preempted')
|
|
696
790
|
note('repair-preempted', entry)
|
|
697
791
|
}
|
|
@@ -721,8 +815,14 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
721
815
|
/** `counted` is false for a reprioritisation restart, which is no retry. */
|
|
722
816
|
const start = (entry: RepairEntry, counted = true): void => {
|
|
723
817
|
const now = performance.now()
|
|
818
|
+
if (moot(entry)) {
|
|
819
|
+
skip(entry, 'moot-rap', readRtt(), entry.deadlineAt - now)
|
|
820
|
+
return
|
|
821
|
+
}
|
|
724
822
|
const rttMs = admit(entry, now)
|
|
725
823
|
if (rttMs === null) return
|
|
824
|
+
// Before the transport sees this request, so the cancels go out first.
|
|
825
|
+
if (entry.priority === 0) preemptBestEffort()
|
|
726
826
|
if (counted) entry.attempts++
|
|
727
827
|
mc.noteRepairFetchSent?.(entry.priority, entry.sbn, entry.packetId, entry.catalogTrackName, {
|
|
728
828
|
rttMs,
|
|
@@ -756,7 +856,7 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
756
856
|
// A fetch still running at its block's fail bound can no longer land
|
|
757
857
|
// in time. Cancel it here rather than trust the transport or the FEC
|
|
758
858
|
// layer's resolution to end it: a hung P0/P1 left running would keep
|
|
759
|
-
//
|
|
859
|
+
// outstandingBelow() true and gate every P1/P2 for the session.
|
|
760
860
|
if (entry.deadlineAt <= now) {
|
|
761
861
|
stop(entry, 'expired')
|
|
762
862
|
retire(entry)
|
|
@@ -775,9 +875,13 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
775
875
|
queued.push(entry)
|
|
776
876
|
}
|
|
777
877
|
for (const entry of queued) {
|
|
778
|
-
if (entry.priority
|
|
878
|
+
if (entry.priority === 0) start(entry)
|
|
779
879
|
}
|
|
780
|
-
if (
|
|
880
|
+
if (outstandingBelow(1)) return
|
|
881
|
+
for (const entry of queued) {
|
|
882
|
+
if (entry.priority === 1) start(entry)
|
|
883
|
+
}
|
|
884
|
+
if (outstandingBelow(2)) return
|
|
781
885
|
queued
|
|
782
886
|
.filter((entry) => entry.priority === 2)
|
|
783
887
|
.sort((a, b) => a.deadlineAt - b.deadlineAt || a.order - b.order)
|
|
@@ -961,7 +1065,6 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
961
1065
|
stop(entry, 'reprioritized')
|
|
962
1066
|
start(entry, false)
|
|
963
1067
|
}
|
|
964
|
-
preemptDeltas()
|
|
965
1068
|
dispatch()
|
|
966
1069
|
return
|
|
967
1070
|
}
|
|
@@ -1137,7 +1240,6 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
1137
1240
|
}
|
|
1138
1241
|
entries.set(key, entry)
|
|
1139
1242
|
noteBudget(entry, deadline.extensionMs)
|
|
1140
|
-
if (entry.priority < 2) preemptDeltas()
|
|
1141
1243
|
dispatch()
|
|
1142
1244
|
})
|
|
1143
1245
|
|
|
@@ -1226,15 +1328,33 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
1226
1328
|
}
|
|
1227
1329
|
entries.set(entry.key, entry)
|
|
1228
1330
|
entry.expiry = setTimeout(dispatch, Math.max(0, entry.deadlineAt - performance.now()) + 1)
|
|
1229
|
-
preemptDeltas()
|
|
1230
1331
|
dispatch()
|
|
1231
1332
|
})
|
|
1232
1333
|
}
|
|
1233
1334
|
|
|
1335
|
+
// A RAP arrived complete: a keyframe FETCH below it is moot (§11.4.2). The
|
|
1336
|
+
// observer runs inside the frame push, so the cancel waits for a microtask
|
|
1337
|
+
// rather than re-entering the FEC layer through the hold release.
|
|
1338
|
+
const removeRapObserver = mc.addRapCompleteObserver?.((packetId) => {
|
|
1339
|
+
queueMicrotask(() => {
|
|
1340
|
+
if (disposed) return
|
|
1341
|
+
for (const entry of [...entries.values()]) {
|
|
1342
|
+
if (entry.packetId !== packetId || !entry.running || !moot(entry)) continue
|
|
1343
|
+
stop(entry, 'moot')
|
|
1344
|
+
retire(entry)
|
|
1345
|
+
releaseHold(entry)
|
|
1346
|
+
clearEntry(entry)
|
|
1347
|
+
}
|
|
1348
|
+
dispatch()
|
|
1349
|
+
})
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1234
1352
|
return () => {
|
|
1235
1353
|
disposed = true
|
|
1354
|
+
mc.setUnicastRepair?.(false)
|
|
1236
1355
|
removeResetObserver()
|
|
1237
1356
|
removeLifecycleObserver()
|
|
1357
|
+
removeRapObserver?.()
|
|
1238
1358
|
for (const entry of [...entries.values()]) {
|
|
1239
1359
|
clearEntry(entry)
|
|
1240
1360
|
if (entry.running) releaseHold(entry)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Receiver repair policy (draft-ramadan-moq-fec-01 Section 11.4.2 and Section 11.4.5 item 3, Blockcast/moqcast-draft PR #2 @ 3162ef93). Which repair FETCHes a receiver sends, which it cancels, and which it never sends. Hand-authored from the specification, NOT generated by any implementation (BLO-49776).",
|
|
4
|
+
"scope": [
|
|
5
|
+
"Section 11.4 applies only to a multicast or AMT leg with a MoQ session (fec-01 11.4). These cases describe that leg; a receiver with no repair FETCH path records ABSENT, not a pass.",
|
|
6
|
+
"schedulerCases drive a repair scheduler. Every block spans K' = 4 SS_IDs, so block B covers SS_IDs B..B+3. Track video is a source track (packetId 1, keyframe or delta blocks); track audio is packetId 2 (audio blocks). A block is named track/SS_Start.",
|
|
7
|
+
"Best-effort (delta and audio) requests in schedulerCases are presumed covered by unexpired credit under fec-01 11.4.5 item 2; that credit is a precondition of each case, not something the case exercises.",
|
|
8
|
+
"A RAP 'held complete at ssId' is a keyframe access unit at SS_ID ssId that the receiver holds complete: admitted complete under the multicast-01 7.2.7 profile, received or recovered complete outside it. A block lies below it when the block's last SS_ID is less than ssId; a RAP at or inside the block does not make the block moot.",
|
|
9
|
+
"fetched lists every FETCH the receiver sends, in order. cancelled lists every sent FETCH it then cancels (FETCH_CANCEL on the wire). skipped names requests made but not sent, with the receiver's reason; stopped names sent FETCHes stopped, with the stop reason. A list not given is expected empty.",
|
|
10
|
+
"assistedCases drive one block of a protected flow (multicast-01 7.2.7) that is one symbol short of SSB_length, with unlisted repair symbols pooled for it. Each unlisted symbol triggers one unlisted-assisted decode in a scratch decoder; outcome 'fail' is a failed decode, 'recover' a decode whose recovered data unit admission accepts, 'recover-unadmitted' a decode whose recovered data unit admission refuses.",
|
|
11
|
+
"status names where a case's rule comes from: 'fec-01' is a -01 requirement; 'libmmt interpretation' is our reading where -01 is silent; 'regression' rows were already required at moqcast-draft main 615a9a8 and are kept to pin standing behaviour.",
|
|
12
|
+
"A keyframe request refused before its FETCH is sent (libmmt's reliable-path RTT check) is libmmt-local and deviates from the fec-01 11.4.2 MUST, so it is pinned in libmmt's own suite (repair-fetch-keyframe-budget.test.ts), not here. The 11.4.5 item 3 rule that a block 11.4.2 does not request cancels nothing is exercised by keyframe-below-complete-rap-not-requested."
|
|
13
|
+
],
|
|
14
|
+
"schedulerCases": [
|
|
15
|
+
{
|
|
16
|
+
"name": "keyframe-below-complete-rap-not-requested",
|
|
17
|
+
"status": "fec-01",
|
|
18
|
+
"rule": "fec-01 11.4.2: a keyframe-bearing block at its FEC deadline is requested unless it lies below a RAP already held complete; 11.4.5 item 3: a block whose repair 11.4.2 does not request cancels nothing (case a).",
|
|
19
|
+
"ops": [
|
|
20
|
+
{ "op": "fire", "track": "video", "block": 20, "class": "delta" },
|
|
21
|
+
{ "op": "rapHeld", "ssId": 4 },
|
|
22
|
+
{ "op": "fire", "track": "video", "block": 0, "class": "keyframe" }
|
|
23
|
+
],
|
|
24
|
+
"expect": {
|
|
25
|
+
"fetched": ["video/20"],
|
|
26
|
+
"skipped": [{ "block": "video/0", "reason": "moot-rap" }]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "control-rap-inside-block-is-requested",
|
|
31
|
+
"status": "libmmt interpretation",
|
|
32
|
+
"rule": "Control for the case above. fec-01 does not define 'lies below'; libmmt reads it so that a RAP at the block's own last SS_ID is part of the block, so the block is not below it and is requested.",
|
|
33
|
+
"ops": [
|
|
34
|
+
{ "op": "rapHeld", "ssId": 3 },
|
|
35
|
+
{ "op": "fire", "track": "video", "block": 0, "class": "keyframe" }
|
|
36
|
+
],
|
|
37
|
+
"expect": { "fetched": ["video/0"] }
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "rap-completes-mid-fetch-cancels",
|
|
41
|
+
"status": "regression",
|
|
42
|
+
"rule": "fec-01 11.4.2 (already a SHOULD at main 615a9a8): a RAP completing while a keyframe repair is outstanding makes that repair moot and the receiver cancels it; a block above the RAP keeps its FETCH (case b).",
|
|
43
|
+
"ops": [
|
|
44
|
+
{ "op": "fire", "track": "video", "block": 0, "class": "keyframe" },
|
|
45
|
+
{ "op": "fire", "track": "video", "block": 8, "class": "keyframe" },
|
|
46
|
+
{ "op": "rapComplete", "ssId": 5 }
|
|
47
|
+
],
|
|
48
|
+
"expect": {
|
|
49
|
+
"fetched": ["video/0", "video/8"],
|
|
50
|
+
"cancelled": ["video/0"],
|
|
51
|
+
"stopped": [{ "block": "video/0", "reason": "moot" }]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "audio-request-preempts-nothing",
|
|
56
|
+
"status": "fec-01",
|
|
57
|
+
"rule": "fec-01 11.4.5 item 3: only the keyframe repair 11.4.2 requests cancels best-effort FETCHes; an audio request cancels nothing.",
|
|
58
|
+
"ops": [
|
|
59
|
+
{ "op": "fire", "track": "video", "block": 20, "class": "delta" },
|
|
60
|
+
{ "op": "fire", "track": "audio", "block": 0, "class": "audio" }
|
|
61
|
+
],
|
|
62
|
+
"expect": { "fetched": ["video/20", "audio/0"] }
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "sent-keyframe-repair-cancels-delta-and-audio",
|
|
66
|
+
"status": "regression",
|
|
67
|
+
"rule": "fec-01 11.4.5 item 3 (standing at main 615a9a8): a keyframe repair FETCH that is sent cancels the outstanding delta and audio FETCHes (case c-pos).",
|
|
68
|
+
"ops": [
|
|
69
|
+
{ "op": "fire", "track": "video", "block": 20, "class": "delta" },
|
|
70
|
+
{ "op": "fire", "track": "audio", "block": 0, "class": "audio" },
|
|
71
|
+
{ "op": "fire", "track": "video", "block": 40, "class": "keyframe" }
|
|
72
|
+
],
|
|
73
|
+
"expect": {
|
|
74
|
+
"fetched": ["video/20", "audio/0", "video/40"],
|
|
75
|
+
"cancelled": ["video/20", "audio/0"],
|
|
76
|
+
"stopped": [
|
|
77
|
+
{ "block": "video/20", "reason": "preempted" },
|
|
78
|
+
{ "block": "audio/0", "reason": "preempted" }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"assistedCases": [
|
|
84
|
+
{
|
|
85
|
+
"name": "two-failed-assisted-decodes-then-refused",
|
|
86
|
+
"status": "fec-01",
|
|
87
|
+
"rule": "multicast-01 7.2.7 step 2, referenced by fec-01 11.4: at most two unlisted-assisted decodes may fail per block; a failed decode's pooled symbol is discarded and a fresh one retried; a third is refused (case d).",
|
|
88
|
+
"decodes": ["fail", "fail", "fail"],
|
|
89
|
+
"expect": { "scratchDecodes": 2, "refused": 1, "recovered": false }
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "assisted-decode-recovers-on-retry",
|
|
93
|
+
"status": "fec-01",
|
|
94
|
+
"rule": "Control for the case above: after one failed decode, the retry on a fresh pooled symbol recovers the block (case d).",
|
|
95
|
+
"decodes": ["fail", "recover"],
|
|
96
|
+
"expect": { "scratchDecodes": 2, "refused": 0, "recovered": true }
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "recovered-but-unadmitted-is-unrecovered",
|
|
100
|
+
"status": "fec-01",
|
|
101
|
+
"rule": "multicast-01 7.2.7 / fec-01 11.4.2: a data unit recovered but refused by admission does not complete the block; the block stays unrecovered and is still due its repair at the FEC deadline (case e).",
|
|
102
|
+
"decodes": ["recover-unadmitted"],
|
|
103
|
+
"expect": { "scratchDecodes": 1, "refused": 0, "recovered": false }
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "unadmitted-recovery-counts-toward-decode-cap",
|
|
107
|
+
"status": "fec-01",
|
|
108
|
+
"rule": "multicast-01 7.2.7: a decode none of whose recovered packets is admitted recovers nothing, so it counts toward the two-decode cap like a failed decode (case e).",
|
|
109
|
+
"decodes": ["recover-unadmitted", "fail", "fail"],
|
|
110
|
+
"expect": { "scratchDecodes": 2, "refused": 1, "recovered": false }
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ManifestKey } from '@blockcast/mmt-manifest-verify';
|
|
2
|
-
import { type ResolvedMulticastAuthFlow } from './multicast-auth-catalog.js';
|
|
3
|
-
/**
|
|
4
|
-
* The bc-provenance version 2 plan a receiver binds from one catalog
|
|
5
|
-
* (draft-ramadan-moq-multicast-01 7.2.6): the key set, `minEpoch`, and one
|
|
6
|
-
* flow per protected endpoint. The record verifier and the 7.2.7 state machine
|
|
7
|
-
* consume it; this plan holds no verification state.
|
|
8
|
-
*/
|
|
9
|
-
export interface ManifestAuthPlan {
|
|
10
|
-
keys: readonly ManifestKey[];
|
|
11
|
-
minEpoch: number;
|
|
12
|
-
flows: readonly ResolvedMulticastAuthFlow[];
|
|
13
|
-
/** Every `manifestTrack` a flow names, for a unicast receiver to subscribe. */
|
|
14
|
-
manifestTracks: readonly string[];
|
|
15
|
-
/** The key that signs a record of `kid` at `epoch`; `undefined` drops it.
|
|
16
|
-
* Key selection only: it does not apply `minEpoch`, which the record
|
|
17
|
-
* verifier checks (BLO-49771). */
|
|
18
|
-
keyFor(kid: number, epoch: number): Uint8Array | undefined;
|
|
19
|
-
/** The flow whose records arrive on `(S, G, manifestPort)`. */
|
|
20
|
-
flowForManifestPort(source: string, groupAddress: string, manifestPort: number): ResolvedMulticastAuthFlow | undefined;
|
|
21
|
-
/** The flow a manifest track mirrors. */
|
|
22
|
-
flowForManifestTrack(manifestTrack: string): ResolvedMulticastAuthFlow | undefined;
|
|
23
|
-
}
|
|
24
|
-
/** Build the plan, or `undefined` for an unprotected catalog. Throws where
|
|
25
|
-
* `resolveMulticastAuthCatalog` does: no key set, or authentication pinned
|
|
26
|
-
* by `requireAuth` on a catalog without `multicast.auth`. */
|
|
27
|
-
export declare function createManifestAuthPlan(catalog: unknown, options?: {
|
|
28
|
-
requireAuth?: boolean;
|
|
29
|
-
catalogNamespace?: string;
|
|
30
|
-
}): ManifestAuthPlan | undefined;
|
|
31
|
-
//# sourceMappingURL=manifest-auth-plan.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-auth-plan.d.ts","sourceRoot":"","sources":["../src/manifest-auth-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,6BAA6B,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC3C,+EAA+E;IAC/E,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC;;sCAEkC;IAClC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;IAC1D,+DAA+D;IAC/D,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAA;IACtH,yCAAyC;IACzC,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAA;CACnF;AAED;;6DAE6D;AAC7D,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GACjE,gBAAgB,GAAG,SAAS,CAkB9B"}
|