@blockcast/mmt-container 2.0.0 → 2.0.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.
- package/README.md +1 -1
- package/dist/browser-runtime.js +45 -12
- package/dist/browser-runtime.js.map +2 -2
- package/dist/delivery-catalog.js +3 -0
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +19 -10
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/multicast-manager.js +45 -12
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.d.ts +8 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +93 -2
- package/dist/multicast-transport.js.map +4 -4
- package/dist/no-modules.js +19 -10
- package/dist/no-modules.js.map +2 -2
- package/package.json +5 -5
- package/src/multicast-transport.ts +43 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockcast/mmt-container",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "MMTP/LOC container parsing for MMT over MoQ and SSM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -169,10 +169,10 @@
|
|
|
169
169
|
"test:watch": "vitest"
|
|
170
170
|
},
|
|
171
171
|
"dependencies": {
|
|
172
|
-
"@blockcast/fec-worker": "^2.0.
|
|
173
|
-
"@blockcast/mmt-base": "^2.0.
|
|
174
|
-
"@blockcast/mmt-fec": "^2.0.
|
|
175
|
-
"@blockcast/mmt-manifest-verify": "^2.0.
|
|
172
|
+
"@blockcast/fec-worker": "^2.0.1",
|
|
173
|
+
"@blockcast/mmt-base": "^2.0.1",
|
|
174
|
+
"@blockcast/mmt-fec": "^2.0.1",
|
|
175
|
+
"@blockcast/mmt-manifest-verify": "^2.0.1",
|
|
176
176
|
"@noble/curves": "^1.9.7",
|
|
177
177
|
"@noble/hashes": "^1.8.0",
|
|
178
178
|
"ajv": "^8.17.1",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
MMTP_PACKET_ID_MAX,
|
|
17
17
|
MMTP_SIGNALING_PACKET_ID,
|
|
18
18
|
readMMTPPacketId,
|
|
19
|
+
sameIpAddress,
|
|
19
20
|
} from '@blockcast/mmt-base'
|
|
20
21
|
import { isBcpV2RecordCandidate } from '@blockcast/mmt-manifest-verify'
|
|
21
22
|
|
|
@@ -41,6 +42,12 @@ export interface MulticastTransportConfig {
|
|
|
41
42
|
* are "MHM2" passes ahead of the packet_id read and the allow-list, and is
|
|
42
43
|
* never counted toward a packet_id. On an unprotected join it is an
|
|
43
44
|
* unparseable MMTP packet, as before.
|
|
45
|
+
*
|
|
46
|
+
* A protected join admits only datagrams whose reported sender is the
|
|
47
|
+
* flow's S (`sourceAddress`, or `senderAddress` on ASM), compared as IP
|
|
48
|
+
* addresses, before anything hashes them (multicast-02 7.2.7). A datagram
|
|
49
|
+
* with no reported sender is dropped, and a join that cannot report one
|
|
50
|
+
* (no S, or an ATSC 3.0 tuner) is declined `invalid-request`.
|
|
44
51
|
*/
|
|
45
52
|
protected?: boolean
|
|
46
53
|
}
|
|
@@ -148,7 +155,8 @@ export interface MulticastTransportStats {
|
|
|
148
155
|
* one (G, port, packet_id); the first sender whose packet is accepted owns
|
|
149
156
|
* the sub-flow until the next join, and every other sender is dropped here,
|
|
150
157
|
* before any gap, duplicate or FEC ledger sees it. A join or a silence
|
|
151
|
-
* failback releases the lock.
|
|
158
|
+
* failback releases the lock. On a protected join it also counts every
|
|
159
|
+
* datagram whose sender is not the flow's S, or is not reported.
|
|
152
160
|
*/
|
|
153
161
|
packetsDroppedForeignSender: number
|
|
154
162
|
}
|
|
@@ -215,7 +223,14 @@ function hasMulticastGateway(): boolean {
|
|
|
215
223
|
return typeof getMulticastGlobals().multicast?.subscribe === 'function'
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* The `bytes` event's sender (multicast-contract 1.3.0); undefined when unknown.
|
|
228
|
+
*
|
|
229
|
+
* The provider filters (S, G, port) and owns the multicast-02 7.2.7
|
|
230
|
+
* destination MUST: the event carries no destination address, so libmmt
|
|
231
|
+
* cannot discard a datagram whose IP destination is not G (pim BLO-51627).
|
|
232
|
+
* On a protected flow libmmt checks the sender itself, against S.
|
|
233
|
+
*/
|
|
219
234
|
function senderOf(event: CustomEvent<unknown>): string | undefined {
|
|
220
235
|
const sourceAddress = (event as { sourceAddress?: unknown }).sourceAddress
|
|
221
236
|
return typeof sourceAddress === 'string' ? sourceAddress : undefined
|
|
@@ -343,6 +358,10 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
343
358
|
readonly #acceptedBytesByPacketId = new Map<number, { bytes: number }>()
|
|
344
359
|
#packetIds: ReadonlySet<number> | null = null
|
|
345
360
|
#protected = false
|
|
361
|
+
// A protected join's S, and the last sender spelling that matched it, so a
|
|
362
|
+
// matching sender parses once per spelling rather than per datagram.
|
|
363
|
+
#protectedSource: string | undefined = undefined
|
|
364
|
+
#matchedSource: string | undefined = undefined
|
|
346
365
|
// The sender S this join's accepted packets came from, once known; see
|
|
347
366
|
// packetsDroppedForeignSender. Cleared by every join and by a silence
|
|
348
367
|
// failback, so a replacement publisher is not locked out.
|
|
@@ -406,6 +425,11 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
406
425
|
// failure before it runs, so the catch classifies a decline by where the
|
|
407
426
|
// join stopped, never by what the error says.
|
|
408
427
|
let failure: MulticastJoinDeclineReason = 'invalid-request'
|
|
428
|
+
const protectedSource = config.protected === true ? config.sourceAddress ?? config.senderAddress : undefined
|
|
429
|
+
if (config.protected === true && (protectedSource === undefined || config.atsc3)) {
|
|
430
|
+
console.warn('[MulticastTransport] protected join refused: the path reports no per-datagram sender to check against S')
|
|
431
|
+
return this.#declined(generation, 'invalid-request')
|
|
432
|
+
}
|
|
409
433
|
try {
|
|
410
434
|
const packetIds = config.packetIds?.length ? new Set(config.packetIds) : null
|
|
411
435
|
console.log(
|
|
@@ -454,8 +478,9 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
454
478
|
// costs batching on every any-source join, including one on a pre-1.3.0
|
|
455
479
|
// provider that sends no sender and so gets no lock for it; keep it
|
|
456
480
|
// unless the provider can say it carries the sender.
|
|
481
|
+
// A protected join checks every datagram's sender, so it listens to `bytes` too.
|
|
457
482
|
const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3
|
|
458
|
-
const packetListener: PacketListener = !anySource && subscriptionDeliversPacketBatches(subscription)
|
|
483
|
+
const packetListener: PacketListener = !anySource && protectedSource === undefined && subscriptionDeliversPacketBatches(subscription)
|
|
459
484
|
? {
|
|
460
485
|
type: 'packets',
|
|
461
486
|
listener: (event) => {
|
|
@@ -473,6 +498,8 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
473
498
|
this.#packetListener = packetListener
|
|
474
499
|
this.#packetIds = packetIds
|
|
475
500
|
this.#protected = config.protected === true
|
|
501
|
+
this.#protectedSource = protectedSource
|
|
502
|
+
this.#matchedSource = undefined
|
|
476
503
|
this.#providerFilter = {
|
|
477
504
|
acknowledged: packetIds,
|
|
478
505
|
sent: 0,
|
|
@@ -1009,6 +1036,8 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
1009
1036
|
this.#delivery = undefined
|
|
1010
1037
|
this.#packetIds = null
|
|
1011
1038
|
this.#protected = false
|
|
1039
|
+
this.#protectedSource = undefined
|
|
1040
|
+
this.#matchedSource = undefined
|
|
1012
1041
|
this.#providerFilter = null
|
|
1013
1042
|
this.#context = undefined
|
|
1014
1043
|
this.#withdrawnPacketIds.clear()
|
|
@@ -1016,8 +1045,18 @@ export class MulticastTransport<TPacketContext = never> {
|
|
|
1016
1045
|
|
|
1017
1046
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
1018
1047
|
#acceptPacket(packet: Uint8Array, sourceAddress?: string): boolean {
|
|
1048
|
+
const source = this.#protectedSource
|
|
1049
|
+
if (source !== undefined && (sourceAddress === undefined || sourceAddress !== this.#matchedSource)) {
|
|
1050
|
+
if (sourceAddress === undefined || !sameIpAddress(sourceAddress, source)) {
|
|
1051
|
+
this.#stats.packetsDroppedForeignSender++
|
|
1052
|
+
return false
|
|
1053
|
+
}
|
|
1054
|
+
this.#matchedSource = sourceAddress
|
|
1055
|
+
}
|
|
1019
1056
|
// Absent means unknown (a pre-1.3.0 provider, a tuner): never a new sender.
|
|
1020
|
-
|
|
1057
|
+
// A protected flow's S check above replaces the first-seen lock, which
|
|
1058
|
+
// compares spellings and would refuse S written another way.
|
|
1059
|
+
const owner = source === undefined ? this.#lockedTo?.() ?? this.#sender : undefined
|
|
1021
1060
|
if (sourceAddress !== undefined && owner !== undefined && sourceAddress !== owner) {
|
|
1022
1061
|
this.#stats.packetsDroppedForeignSender++
|
|
1023
1062
|
return false
|