@blockcast/mmt-container 1.0.1 → 2.0.0-main.609e08d7e29f
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 +9 -7
- package/dist/browser-runtime.js +6972 -6693
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +7 -7
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +65 -230
- package/dist/carp-catalog.js.map +3 -3
- package/dist/catalog-fec-params.js.map +1 -1
- package/dist/catalog-network-source.js.map +1 -1
- package/dist/delivery-catalog.js +3 -0
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +25 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +208 -15
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-state.js.map +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/held-layer-admission.d.ts +8 -0
- package/dist/held-layer-admission.d.ts.map +1 -1
- package/dist/held-layer-admission.js +22 -1
- package/dist/held-layer-admission.js.map +2 -2
- package/dist/iso-block-locator.js.map +1 -1
- package/dist/mfu-reassembler.js.map +1 -1
- package/dist/mmtp-container-parser.js.map +1 -1
- package/dist/mmtp-container-parser.no-modules.js +146 -332
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js.map +1 -1
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +61 -15
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +11 -6
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +47 -10
- package/dist/mmtp-router.js.map +3 -3
- package/dist/multicast-auth-catalog.d.ts +8 -8
- package/dist/multicast-auth-catalog.d.ts.map +1 -1
- package/dist/multicast-auth-catalog.js +171 -75
- package/dist/multicast-auth-catalog.js.map +4 -4
- package/dist/multicast-input.d.ts +5 -5
- package/dist/multicast-input.d.ts.map +1 -1
- package/dist/multicast-input.js +126 -49
- package/dist/multicast-input.js.map +3 -3
- package/dist/multicast-manager.d.ts +11 -4
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +7914 -7659
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +16 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +120 -5
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +2 -2
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +100 -288
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.js.map +1 -1
- package/dist/signaling.js.map +1 -1
- package/dist/timestamp-codec.js.map +1 -1
- package/dist/wasm-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js.map +1 -1
- package/package.json +5 -5
- package/schemas/catalog.schema.json +15 -7
- package/src/carp-catalog.ts +14 -13
- package/src/fec-manager.ts +89 -14
- package/src/held-layer-admission.ts +38 -1
- package/src/mmtp-fec-session.ts +20 -12
- package/src/mmtp-router.ts +65 -16
- package/src/multicast-auth-catalog.ts +43 -36
- package/src/multicast-input.ts +28 -8
- package/src/multicast-manager.ts +38 -34
- package/src/multicast-transport.ts +74 -7
- package/test-vectors/bc-provenance-v2.json +614 -175
- package/test-vectors/catalog/README.md +24 -8
- package/test-vectors/catalog/negative/multicast-auth-asm-missing-sender-address.json +0 -2
- package/test-vectors/catalog/negative/{multicast-auth-manifest-port-equals-port.json → multicast-auth-draft01-manifest-port.json} +1 -1
- package/test-vectors/catalog/negative/multicast-auth-duplicate-first-epoch.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-duplicate-kid.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-endpoint-auth-without-object-auth.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-endpoint-missing-bandwidth.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-manifest-timer-out-of-range.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-sender-address-on-ssm.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-shared-sg-port.json +73 -0
- package/test-vectors/catalog/negative/multicast-auth-version-1.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-version-3.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-version-missing.json +0 -1
- package/test-vectors/catalog/negative/multicast-endpoint-address-family-mismatch.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-host-name.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-leading-zero.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-ls32-leading-zero.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-mapped-family-mismatch.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-zone-id.json +55 -0
- package/test-vectors/catalog/positive/multicast-auth-asm-sender.json +0 -2
- package/test-vectors/catalog/{negative/multicast-auth-max-auth-delay-above-max.json → positive/multicast-auth-max-auth-delay-above-max-clamped.json} +0 -1
- package/test-vectors/catalog/{negative/multicast-auth-max-auth-delay-below-min.json → positive/multicast-auth-max-auth-delay-below-min-clamped.json} +0 -1
- package/test-vectors/catalog/positive/multicast-auth-max-auth-delay-string.json +55 -0
- package/test-vectors/catalog/positive/multicast-auth-rotation.json +0 -1
- package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +0 -1
- package/test-vectors/catalog/section-codes.json +8 -3
- package/test-vectors/catalog-multicast-auth-v1.json +0 -1
- package/test-vectors/catalog-validation.json +3 -3
- package/test-vectors/generate-bc-provenance-v2.mjs +94 -14
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `multicast.auth` and an endpoint's `auth`: bc-provenance version 2, per
|
|
3
|
-
* draft-ramadan-moq-multicast-
|
|
4
|
-
*
|
|
3
|
+
* draft-ramadan-moq-multicast-02 7.2.6 at Blockcast/moqcast-draft@5f49172.
|
|
4
|
+
* Records ride in band on the flow's own `port` (7.2.3); draft -01's separate
|
|
5
|
+
* record port is gone.
|
|
5
6
|
*
|
|
6
7
|
* The block lives in the `multicast` object, never the root: the root dotted
|
|
7
8
|
* key `"multicast.auth"` was draft -00's (libmmt called its `tracks[]` shape
|
|
@@ -48,15 +49,13 @@ export interface MulticastAuthConfig {
|
|
|
48
49
|
}
|
|
49
50
|
/** An endpoint's `auth` (7.2.6). A protected endpoint also declares `bandwidth`. */
|
|
50
51
|
export interface EndpointAuthConfig {
|
|
51
|
-
/** Destination port of the record datagrams on the endpoint's `(S, G)`. */
|
|
52
|
-
manifestPort: number;
|
|
53
52
|
/** u32 chosen per channel generation. */
|
|
54
53
|
streamId: number;
|
|
55
54
|
/** `S` of a protected ASM endpoint; never on SSM. */
|
|
56
55
|
senderAddress?: string;
|
|
57
|
-
/** 50..1000, default 200. */
|
|
56
|
+
/** 50..1000, default 200; a receiver clamps one outside the range. */
|
|
58
57
|
maxAuthDelayMs?: number;
|
|
59
|
-
/** At least 1, default 20. */
|
|
58
|
+
/** At least 1, default 20; a receiver ignores the endpoint below 1. */
|
|
60
59
|
manifestTimerMs?: number;
|
|
61
60
|
/** MoQ track mirroring the records. */
|
|
62
61
|
manifestTrack?: string;
|
|
@@ -68,12 +67,13 @@ export declare function isMulticastAuthConfig(obj: unknown): obj is MulticastAut
|
|
|
68
67
|
export interface ResolvedMulticastAuthFlow {
|
|
69
68
|
/** Index of the endpoint in `multicast.endpoints`. */
|
|
70
69
|
endpointIndex: number;
|
|
71
|
-
/** `S`: `sourceAddress` on SSM, `auth.senderAddress` on ASM. */
|
|
70
|
+
/** `S`: `sourceAddress` on SSM, `auth.senderAddress` on ASM; canonical (`canonicalIpAddress`). */
|
|
72
71
|
source: string;
|
|
72
|
+
/** Canonical (`canonicalIpAddress`). */
|
|
73
73
|
groupAddress: string;
|
|
74
|
+
/** The flow's port; its records ride in band on it (7.2.3). */
|
|
74
75
|
port: number;
|
|
75
76
|
bandwidth: number;
|
|
76
|
-
manifestPort: number;
|
|
77
77
|
streamId: number;
|
|
78
78
|
maxAuthDelayMs: number;
|
|
79
79
|
manifestTimerMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multicast-auth-catalog.d.ts","sourceRoot":"","sources":["../src/multicast-auth-catalog.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"multicast-auth-catalog.d.ts","sourceRoot":"","sources":["../src/multicast-auth-catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,13 +13,98 @@ var FEC_BLOCK_PROFILES = Object.freeze([
|
|
|
13
13
|
"iso-ssbg1-v1"
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
|
+
// ../mmt-base/dist/ip-address.js
|
|
17
|
+
var DEC_OCTET = /^(?:0|[1-9]\d{0,2})$/;
|
|
18
|
+
var H16 = /^[0-9A-Fa-f]{1,4}$/;
|
|
19
|
+
function parseIpv4(text2) {
|
|
20
|
+
const fields = text2.split(".");
|
|
21
|
+
if (fields.length !== 4)
|
|
22
|
+
return void 0;
|
|
23
|
+
const octets = [];
|
|
24
|
+
for (const field of fields) {
|
|
25
|
+
if (!DEC_OCTET.test(field))
|
|
26
|
+
return void 0;
|
|
27
|
+
const value = Number(field);
|
|
28
|
+
if (value > 255)
|
|
29
|
+
return void 0;
|
|
30
|
+
octets.push(value);
|
|
31
|
+
}
|
|
32
|
+
return octets;
|
|
33
|
+
}
|
|
34
|
+
function parseIpv6(text2) {
|
|
35
|
+
const halves = text2.split("::");
|
|
36
|
+
if (halves.length > 2)
|
|
37
|
+
return void 0;
|
|
38
|
+
const groupsOf = (part) => part === "" ? [] : part.split(":");
|
|
39
|
+
const left = groupsOf(halves[0]);
|
|
40
|
+
const right = halves.length === 2 ? groupsOf(halves[1]) : [];
|
|
41
|
+
const words = [];
|
|
42
|
+
const all = [...left, ...right];
|
|
43
|
+
const last = halves.length === 2 && right.length === 0 ? -1 : all.length - 1;
|
|
44
|
+
for (let index = 0; index < all.length; index++) {
|
|
45
|
+
const group = all[index];
|
|
46
|
+
if (index === last && group.includes(".")) {
|
|
47
|
+
const v4 = parseIpv4(group);
|
|
48
|
+
if (!v4)
|
|
49
|
+
return void 0;
|
|
50
|
+
words.push([v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]]);
|
|
51
|
+
} else if (H16.test(group)) {
|
|
52
|
+
words.push([Number.parseInt(group, 16)]);
|
|
53
|
+
} else {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const leftWords = words.slice(0, left.length).flat();
|
|
58
|
+
const rightWords = words.slice(left.length).flat();
|
|
59
|
+
const missing = 8 - leftWords.length - rightWords.length;
|
|
60
|
+
if (halves.length === 2 ? missing < 1 : missing !== 0)
|
|
61
|
+
return void 0;
|
|
62
|
+
const out = new Uint8Array(16);
|
|
63
|
+
[...leftWords, ...new Array(missing).fill(0), ...rightWords].forEach((word, index) => {
|
|
64
|
+
out[index * 2] = word >> 8;
|
|
65
|
+
out[index * 2 + 1] = word & 255;
|
|
66
|
+
});
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function parseIpAddressLiteral(text2) {
|
|
70
|
+
if (typeof text2 !== "string")
|
|
71
|
+
return void 0;
|
|
72
|
+
if (text2.includes(":"))
|
|
73
|
+
return parseIpv6(text2);
|
|
74
|
+
const v4 = parseIpv4(text2);
|
|
75
|
+
return v4 ? Uint8Array.from(v4) : void 0;
|
|
76
|
+
}
|
|
77
|
+
function canonicalIpAddress(text2) {
|
|
78
|
+
const bytes = parseIpAddressLiteral(text2);
|
|
79
|
+
if (!bytes)
|
|
80
|
+
return void 0;
|
|
81
|
+
if (bytes.length === 4)
|
|
82
|
+
return bytes.join(".");
|
|
83
|
+
const words = Array.from({ length: 8 }, (_, index) => bytes[index * 2] << 8 | bytes[index * 2 + 1]);
|
|
84
|
+
let bestStart = -1;
|
|
85
|
+
let bestLength = 1;
|
|
86
|
+
for (let start = 0; start < 8; ) {
|
|
87
|
+
let end = start;
|
|
88
|
+
while (end < 8 && words[end] === 0)
|
|
89
|
+
end++;
|
|
90
|
+
if (end - start > bestLength) {
|
|
91
|
+
bestStart = start;
|
|
92
|
+
bestLength = end - start;
|
|
93
|
+
}
|
|
94
|
+
start = end === start ? start + 1 : end;
|
|
95
|
+
}
|
|
96
|
+
const hex = words.map((word) => word.toString(16));
|
|
97
|
+
if (bestStart < 0)
|
|
98
|
+
return hex.join(":");
|
|
99
|
+
return `${hex.slice(0, bestStart).join(":")}::${hex.slice(bestStart + bestLength).join(":")}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
16
102
|
// ../mmt-base/dist/multicast-catalog.js
|
|
17
103
|
var ED25519_PUBLIC_KEY_BASE64URL = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
18
104
|
var UINT32_MAX = 4294967295;
|
|
19
105
|
var AUTH_MEMBERS = /* @__PURE__ */ new Set(["scheme", "version", "sig", "keys", "minEpoch"]);
|
|
20
106
|
var KEY_MEMBERS = /* @__PURE__ */ new Set(["kid", "publicKey", "firstEpoch"]);
|
|
21
107
|
var ENDPOINT_AUTH_MEMBERS = /* @__PURE__ */ new Set([
|
|
22
|
-
"manifestPort",
|
|
23
108
|
"streamId",
|
|
24
109
|
"senderAddress",
|
|
25
110
|
"maxAuthDelayMs",
|
|
@@ -29,17 +114,32 @@ var ENDPOINT_AUTH_MEMBERS = /* @__PURE__ */ new Set([
|
|
|
29
114
|
function isObject(value) {
|
|
30
115
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
116
|
}
|
|
32
|
-
function isNonEmptyString(value) {
|
|
33
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
34
|
-
}
|
|
35
117
|
function isIntegerIn(value, min, max) {
|
|
36
118
|
return Number.isSafeInteger(value) && value >= min && value <= max;
|
|
37
119
|
}
|
|
120
|
+
var BCP_DEFAULT_MAX_AUTH_DELAY_MS = 200;
|
|
121
|
+
var BCP_DEFAULT_MANIFEST_TIMER_MS = 20;
|
|
122
|
+
function isFiniteNumber(value) {
|
|
123
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
124
|
+
}
|
|
125
|
+
function endpointAuthTiming(auth) {
|
|
126
|
+
const record = isObject(auth) ? auth : {};
|
|
127
|
+
const delay = record["maxAuthDelayMs"];
|
|
128
|
+
const timer = record["manifestTimerMs"];
|
|
129
|
+
const track = record["manifestTrack"];
|
|
130
|
+
const maxAuthDelayMs = !isFiniteNumber(delay) ? BCP_DEFAULT_MAX_AUTH_DELAY_MS : delay < 50 ? 50 : delay > 1e3 ? 1e3 : Number.isInteger(delay) ? delay : BCP_DEFAULT_MAX_AUTH_DELAY_MS;
|
|
131
|
+
const manifestTimerMs = !isFiniteNumber(timer) ? BCP_DEFAULT_MANIFEST_TIMER_MS : timer < 1 || Number.isSafeInteger(timer) ? timer : BCP_DEFAULT_MANIFEST_TIMER_MS;
|
|
132
|
+
return {
|
|
133
|
+
maxAuthDelayMs,
|
|
134
|
+
manifestTimerMs,
|
|
135
|
+
...typeof track === "string" && track.length > 0 ? { manifestTrack: track } : {}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
38
138
|
function multicastEndpointSource(endpoint) {
|
|
39
139
|
if (!isObject(endpoint))
|
|
40
140
|
return void 0;
|
|
41
141
|
const sourceAddress = endpoint["sourceAddress"];
|
|
42
|
-
if (sourceAddress !== void 0 && !
|
|
142
|
+
if (sourceAddress !== void 0 && !parseIpAddressLiteral(sourceAddress))
|
|
43
143
|
return void 0;
|
|
44
144
|
const mode = sourceAddress === void 0 ? "asm" : "ssm";
|
|
45
145
|
const protocol = endpoint["protocol"];
|
|
@@ -51,7 +151,7 @@ function multicastEndpointSource(endpoint) {
|
|
|
51
151
|
return senderAddress === void 0 ? { mode, source: sourceAddress } : void 0;
|
|
52
152
|
if (auth === void 0)
|
|
53
153
|
return { mode };
|
|
54
|
-
return
|
|
154
|
+
return parseIpAddressLiteral(senderAddress) ? { mode, source: senderAddress } : void 0;
|
|
55
155
|
}
|
|
56
156
|
function authIssues(auth) {
|
|
57
157
|
const base = "$.multicast.auth";
|
|
@@ -62,12 +162,12 @@ function authIssues(auth) {
|
|
|
62
162
|
return issue("", "auth must be an object");
|
|
63
163
|
const unknown = Object.keys(auth).filter((key) => !AUTH_MEMBERS.has(key));
|
|
64
164
|
if (unknown.length > 0) {
|
|
65
|
-
return issue("", `auth has unknown member(s) ${unknown.join(", ")}; draft -
|
|
165
|
+
return issue("", `auth has unknown member(s) ${unknown.join(", ")}; draft -02 7.2.6 defines scheme, version, sig, keys and minEpoch`);
|
|
66
166
|
}
|
|
67
167
|
if (auth["scheme"] !== "bc-provenance")
|
|
68
168
|
return issue(".scheme", 'scheme must be "bc-provenance"');
|
|
69
169
|
if (auth["version"] !== 2)
|
|
70
|
-
return issue(".version", "version must be 2 (bc-provenance version 2, draft -
|
|
170
|
+
return issue(".version", "version must be 2 (bc-provenance version 2, draft -02 7.2)");
|
|
71
171
|
if (auth["sig"] !== "ed25519")
|
|
72
172
|
return issue(".sig", 'sig must be "ed25519"');
|
|
73
173
|
const keys = auth["keys"];
|
|
@@ -110,26 +210,17 @@ function endpointAuthIssue(endpoint, mode, at) {
|
|
|
110
210
|
const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
|
|
111
211
|
if (unknown.length > 0)
|
|
112
212
|
return { path: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
|
|
113
|
-
if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
|
|
114
|
-
return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
|
|
115
|
-
}
|
|
116
213
|
if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
|
|
117
214
|
return { path: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
|
|
118
215
|
}
|
|
119
216
|
if (mode === "ssm" && auth["senderAddress"] !== void 0) {
|
|
120
217
|
return { path: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
|
|
121
218
|
}
|
|
122
|
-
if (mode === "asm" && !
|
|
123
|
-
return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
|
|
219
|
+
if (mode === "asm" && !parseIpAddressLiteral(auth["senderAddress"])) {
|
|
220
|
+
return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress, an RFC 3986 IP address literal" };
|
|
124
221
|
}
|
|
125
|
-
if (
|
|
126
|
-
return { path: `${at}.auth.
|
|
127
|
-
}
|
|
128
|
-
if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
129
|
-
return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
|
|
130
|
-
}
|
|
131
|
-
if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
|
|
132
|
-
return { path: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
|
|
222
|
+
if (endpointAuthTiming(auth).manifestTimerMs < 1) {
|
|
223
|
+
return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be at least 1" };
|
|
133
224
|
}
|
|
134
225
|
if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
135
226
|
return { path: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
|
|
@@ -145,7 +236,7 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
145
236
|
issues.push({
|
|
146
237
|
code: "multicast_auth",
|
|
147
238
|
path: '$["multicast.auth"]',
|
|
148
|
-
message: 'the root "multicast.auth" key is retired; declare auth as multicast.auth (draft -
|
|
239
|
+
message: 'the root "multicast.auth" key is retired; declare auth as multicast.auth (draft -02 7.2.6)'
|
|
149
240
|
});
|
|
150
241
|
}
|
|
151
242
|
const multicast = catalog["multicast"];
|
|
@@ -179,7 +270,7 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
179
270
|
}
|
|
180
271
|
const protocol = endpoint["protocol"];
|
|
181
272
|
if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
|
|
182
|
-
fail("multicast_endpoint", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -
|
|
273
|
+
fail("multicast_endpoint", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -02 4.2)`);
|
|
183
274
|
continue;
|
|
184
275
|
}
|
|
185
276
|
const hasAuth = endpoint["auth"] !== void 0;
|
|
@@ -188,8 +279,18 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
188
279
|
continue;
|
|
189
280
|
}
|
|
190
281
|
const sourceAddress = endpoint["sourceAddress"];
|
|
191
|
-
|
|
192
|
-
|
|
282
|
+
const sourceBytes = sourceAddress === void 0 ? void 0 : parseIpAddressLiteral(sourceAddress);
|
|
283
|
+
if (sourceAddress !== void 0 && !sourceBytes) {
|
|
284
|
+
fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress must be an RFC 3986 IPv4address or IPv6address literal");
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const groupBytes = parseIpAddressLiteral(endpoint["groupAddress"]);
|
|
288
|
+
if (!groupBytes) {
|
|
289
|
+
fail("multicast_endpoint", `${at}.groupAddress`, "groupAddress must be an RFC 3986 IPv4address or IPv6address literal");
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (sourceBytes && sourceBytes.length !== groupBytes.length) {
|
|
293
|
+
fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress and groupAddress are of different address families");
|
|
193
294
|
continue;
|
|
194
295
|
}
|
|
195
296
|
const mode = sourceAddress === void 0 ? "asm" : "ssm";
|
|
@@ -208,7 +309,13 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
208
309
|
continue;
|
|
209
310
|
}
|
|
210
311
|
}
|
|
211
|
-
|
|
312
|
+
const source = multicastEndpointSource(endpoint);
|
|
313
|
+
const senderBytes = source?.mode === "asm" && source.source !== void 0 ? parseIpAddressLiteral(source.source) : void 0;
|
|
314
|
+
if (senderBytes && senderBytes.length !== groupBytes.length) {
|
|
315
|
+
fail("multicast_endpoint_auth", `${at}.auth.senderAddress`, "senderAddress and groupAddress are of different address families");
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
sources[index] = source;
|
|
212
319
|
const refs = Array.isArray(endpoint["tracks"]) ? endpoint["tracks"] : [];
|
|
213
320
|
for (let refIndex = 0; refIndex < refs.length; refIndex++) {
|
|
214
321
|
const ref = refs[refIndex];
|
|
@@ -224,38 +331,23 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
224
331
|
}
|
|
225
332
|
}
|
|
226
333
|
}
|
|
334
|
+
const addressKey = (text2) => {
|
|
335
|
+
const bytes = text2 === void 0 ? void 0 : parseIpAddressLiteral(text2);
|
|
336
|
+
return bytes ? `${bytes.length}:${bytes.join(".")}` : "-";
|
|
337
|
+
};
|
|
227
338
|
const tupleOf = (index) => {
|
|
228
339
|
const endpoint = endpoints[index];
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
sg: JSON.stringify([sources[index].source ?? null, endpoint["groupAddress"]]),
|
|
232
|
-
port: endpoint["port"],
|
|
233
|
-
manifestPort: isObject(auth) ? auth["manifestPort"] : void 0
|
|
234
|
-
};
|
|
340
|
+
return `${addressKey(sources[index].source)}|${addressKey(endpoint["groupAddress"])}|${endpoint["port"]}`;
|
|
235
341
|
};
|
|
236
342
|
for (let index = 0; index < endpoints.length; index++) {
|
|
237
343
|
if (!sources[index])
|
|
238
344
|
continue;
|
|
239
345
|
const own = tupleOf(index);
|
|
240
|
-
const at = `$.multicast.endpoints[${index}]`;
|
|
241
|
-
if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
|
|
242
|
-
issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
346
|
for (let other = 0; other < endpoints.length; other++) {
|
|
246
|
-
if (other === index || !sources[other])
|
|
247
|
-
continue;
|
|
248
|
-
const peer = tupleOf(other);
|
|
249
|
-
if (peer.sg !== own.sg)
|
|
347
|
+
if (other === index || !sources[other] || tupleOf(other) !== own)
|
|
250
348
|
continue;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
|
|
256
|
-
issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
349
|
+
issues.push({ code: "multicast_endpoint", path: `$.multicast.endpoints[${index}].port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
|
|
350
|
+
break;
|
|
259
351
|
}
|
|
260
352
|
}
|
|
261
353
|
return issues;
|
|
@@ -620,17 +712,23 @@ function lp16(value) {
|
|
|
620
712
|
return concat([u16(bytes.byteLength), bytes]);
|
|
621
713
|
}
|
|
622
714
|
function bcProvenanceV2ParamsHash(params) {
|
|
623
|
-
const fec = params.fec;
|
|
715
|
+
const fec = [...params.fec ?? []].sort((a, b) => (a.instanceId ?? 0) - (b.instanceId ?? 0));
|
|
716
|
+
if (fec.some((instance) => instance.scheme === "none")) {
|
|
717
|
+
throw new Error('FEC scheme "none" is reserved (fec-core-00 15.2)');
|
|
718
|
+
}
|
|
719
|
+
const tuples = fec.length === 0 ? [lp16("none"), u32(0), u32(0), u32(0), u32(0)] : fec.flatMap((instance) => [
|
|
720
|
+
lp16(instance.scheme),
|
|
721
|
+
u32(instance.sourceSymbols),
|
|
722
|
+
u32(instance.repairSymbols),
|
|
723
|
+
u32(instance.symbolSize),
|
|
724
|
+
u32(instance.interleaveDepthMs ?? 0)
|
|
725
|
+
]);
|
|
624
726
|
return sha2562(concat([
|
|
625
727
|
text.encode("BCPV2-params"),
|
|
626
728
|
lp16(params.codec),
|
|
627
729
|
u32(params.timescale),
|
|
628
730
|
u32(params.groupDurationTicks),
|
|
629
|
-
|
|
630
|
-
u32(fec?.sourceSymbols ?? 0),
|
|
631
|
-
u32(fec?.repairSymbols ?? 0),
|
|
632
|
-
u32(fec?.symbolSize ?? 0),
|
|
633
|
-
u32(fec?.interleaveDepthMs ?? 0)
|
|
731
|
+
...tuples
|
|
634
732
|
]));
|
|
635
733
|
}
|
|
636
734
|
function bcProvenanceV2Ctx(namespace, packetIds, params) {
|
|
@@ -649,8 +747,8 @@ function bcProvenanceV2Ctx(namespace, packetIds, params) {
|
|
|
649
747
|
|
|
650
748
|
// src/multicast-auth-catalog.ts
|
|
651
749
|
var RAW_ED25519_KEY_LENGTH = 32;
|
|
652
|
-
var DEFAULT_MAX_AUTH_DELAY_MS =
|
|
653
|
-
var DEFAULT_MANIFEST_TIMER_MS =
|
|
750
|
+
var DEFAULT_MAX_AUTH_DELAY_MS = BCP_DEFAULT_MAX_AUTH_DELAY_MS;
|
|
751
|
+
var DEFAULT_MANIFEST_TIMER_MS = BCP_DEFAULT_MANIFEST_TIMER_MS;
|
|
654
752
|
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
655
753
|
function encodeBase64Url(bytes) {
|
|
656
754
|
let output = "";
|
|
@@ -746,15 +844,13 @@ function resolveMulticastAuthCatalog(catalog, options = {}) {
|
|
|
746
844
|
}
|
|
747
845
|
flows.push({
|
|
748
846
|
endpointIndex,
|
|
749
|
-
source: endpoint["sourceAddress"] ?? flowAuth.senderAddress,
|
|
750
|
-
groupAddress: endpoint["groupAddress"],
|
|
847
|
+
source: canonicalIpAddress(endpoint["sourceAddress"] ?? flowAuth.senderAddress),
|
|
848
|
+
groupAddress: canonicalIpAddress(endpoint["groupAddress"]),
|
|
751
849
|
port: endpoint["port"],
|
|
752
850
|
bandwidth: endpoint["bandwidth"],
|
|
753
|
-
manifestPort: flowAuth.manifestPort,
|
|
754
851
|
streamId: flowAuth.streamId,
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
...flowAuth.manifestTrack === void 0 ? {} : { manifestTrack: flowAuth.manifestTrack },
|
|
852
|
+
// Clamped, or the defaults where absent (7.2.6).
|
|
853
|
+
...endpointAuthTiming(flowAuth),
|
|
758
854
|
tracks,
|
|
759
855
|
...ctx === void 0 ? {} : { ctx }
|
|
760
856
|
});
|
|
@@ -770,32 +866,32 @@ function positiveInteger(value) {
|
|
|
770
866
|
}
|
|
771
867
|
function ctxParams(catalogTracks, listed) {
|
|
772
868
|
const byName = new Map(catalogTracks.map((track) => [track["name"], track]));
|
|
869
|
+
const instancesOf = (track) => Array.isArray(track["fec"]) ? track["fec"].filter((f) => typeof f === "object" && f !== null) : [];
|
|
773
870
|
const ordered = [...listed].sort((a, b) => a.packetId - b.packetId).map(({ name }) => byName.get(name));
|
|
774
871
|
let entry = ordered.find((track) => track !== void 0 && track["packaging"] !== "fec-repair");
|
|
775
872
|
if (entry === void 0) {
|
|
776
873
|
const repair = ordered.find((track) => track !== void 0);
|
|
777
874
|
if (repair === void 0) return void 0;
|
|
778
|
-
|
|
779
|
-
entry = Array.isArray(depends) && depends.length === 1 ? byName.get(depends[0]) : catalogTracks.find((track) => track["fec"]?.["repairTrack"] === repair["name"]);
|
|
875
|
+
entry = typeof repair["source"] === "string" ? byName.get(repair["source"]) : catalogTracks.find((track) => instancesOf(track).some((f) => f["repairTrack"] === repair["name"]));
|
|
780
876
|
if (entry === void 0) return void 0;
|
|
781
877
|
}
|
|
878
|
+
const instances = instancesOf(entry);
|
|
879
|
+
if (instances.some((f) => f["scheme"] === "none")) return void 0;
|
|
782
880
|
const timescale = positiveInteger(entry["timescale"]);
|
|
783
881
|
const ms = positiveInteger(entry["groupDurationMs"]);
|
|
784
882
|
const groupDurationTicks = positiveInteger(entry["groupDurationTicks"]) || positiveInteger(timescale && ms * timescale % 1e3 === 0 ? ms * timescale / 1e3 : 0);
|
|
785
|
-
const fec = entry["fec"];
|
|
786
883
|
return {
|
|
787
884
|
codec: typeof entry["codec"] === "string" ? entry["codec"] : "",
|
|
788
885
|
timescale,
|
|
789
886
|
groupDurationTicks,
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}
|
|
887
|
+
fec: instances.map((f) => ({
|
|
888
|
+
instanceId: typeof f["instanceId"] === "number" ? f["instanceId"] : 0,
|
|
889
|
+
scheme: typeof f["scheme"] === "string" ? f["scheme"] : "",
|
|
890
|
+
sourceSymbols: positiveInteger(f["sourceSymbols"]),
|
|
891
|
+
repairSymbols: positiveInteger(f["repairSymbols"]),
|
|
892
|
+
symbolSize: positiveInteger(f["symbolSize"]),
|
|
893
|
+
interleaveDepthMs: positiveInteger(f["interleaveDepthMs"])
|
|
894
|
+
}))
|
|
799
895
|
};
|
|
800
896
|
}
|
|
801
897
|
function multicastAuthKeyFor(resolved, kid, epoch) {
|