@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 CHANGED
@@ -277,7 +277,7 @@ per-commit `0.1.0-main.<sha>` prerelease from `stamp-prerelease.mjs`), and a
277
277
  > Superseded by BLO-40665. Every package is now bumped in lockstep by a `v*`
278
278
  > tag and the sibling ranges move with it, so the objection above no longer
279
279
  > applies -- it was an argument against bumping *one* package. The set is at
280
- > `2.0.0`; see `RELEASES.md`.
280
+ > `2.0.1`; see `RELEASES.md`.
281
281
 
282
282
  #### Track-role convention (Phase 14)
283
283
 
@@ -8514,6 +8514,9 @@ var AMTGatewayManager = class {
8514
8514
  */
8515
8515
  async subscribe(config) {
8516
8516
  const { key } = config;
8517
+ if (config.protected) {
8518
+ throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
8519
+ }
8517
8520
  const keyStr = this.getKeyString(key);
8518
8521
  if (this.subscriptions.has(keyStr)) {
8519
8522
  console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
@@ -21977,7 +21980,6 @@ _RistrettoPoint.Fn = /* @__PURE__ */ (() => Fn)();
21977
21980
  var BCPV2_LEAD_BOUND_MS = 5e3;
21978
21981
  var REPLAY_WINDOW = 64n;
21979
21982
  var BUCKET_DEPTH = 64;
21980
- var BUCKET_RATE_PER_MS = 1;
21981
21983
  var hex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
21982
21984
  function sameBytes(a, b) {
21983
21985
  if (a.byteLength !== b.byteLength)
@@ -22006,6 +22008,10 @@ var BcProvenanceV2Receiver = class {
22006
22008
  verifySig;
22007
22009
  accepted;
22008
22010
  queueLimit;
22011
+ /** Octets a held datagram is charged beyond its payload: the IPv4 or IPv6 plus UDP headers (step 2). */
22012
+ headerOctets;
22013
+ /** Budget refill, tokens per ms: V / 1000 (step 1). */
22014
+ bucketRatePerMs;
22009
22015
  current = null;
22010
22016
  previous = null;
22011
22017
  graceStart = {};
@@ -22051,6 +22057,8 @@ var BcProvenanceV2Receiver = class {
22051
22057
  multicast: this.maxAuthDelayMs * bandwidth / 8e3,
22052
22058
  unicast: (this.maxAuthDelayMs + this.widenMs) * bandwidth / 8e3
22053
22059
  };
22060
+ this.headerOctets = parseBcProvenanceV2Address(cfg.endpoint.groupAddress).byteLength === 16 ? 48 : 28;
22061
+ this.bucketRatePerMs = Math.max(1e3, 1.25 * bandwidth / (8 * 512 * 37)) / 1e3;
22054
22062
  const t = this.now();
22055
22063
  this.buckets = {
22056
22064
  multicast: { tokens: BUCKET_DEPTH, last: t, dropped: 0 },
@@ -22149,7 +22157,7 @@ var BcProvenanceV2Receiver = class {
22149
22157
  arrival: now,
22150
22158
  sent,
22151
22159
  digest: hex(sha2562(bytes)),
22152
- pairs: record.digests.map((d, i) => ({ digest: hex(d), seq: record.firstSeq + BigInt(i), admitted: false })),
22160
+ pairs: record.digests.map((d, i) => ({ digest: hex(d), epoch, seq: record.firstSeq + BigInt(i), admitted: false })),
22153
22161
  awaitingMulticast: path === "mirror"
22154
22162
  };
22155
22163
  this.verified.set(id, entry);
@@ -22216,12 +22224,12 @@ var BcProvenanceV2Receiver = class {
22216
22224
  digest(datagram) {
22217
22225
  return hex(bcProvenanceV2DatagramDigest(this.cfg.endpoint, datagram));
22218
22226
  }
22219
- /** Consume the lowest-seq pending entry for `digest`, admitting it. */
22227
+ /** Consume the pending entry for `digest` lowest in (epoch, sequence) order, admitting it. */
22220
22228
  consumePending(digest, now) {
22221
22229
  const list = this.pending.get(digest);
22222
22230
  if (!list?.length)
22223
22231
  return false;
22224
- list.sort((a, b) => a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0);
22232
+ list.sort((a, b) => a.pair.epoch - b.pair.epoch || (a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0));
22225
22233
  const hit = list.shift();
22226
22234
  if (list.length === 0)
22227
22235
  this.pending.delete(digest);
@@ -22230,14 +22238,18 @@ var BcProvenanceV2Receiver = class {
22230
22238
  this.admittedUntil.set(digest, now + this.maxAuthDelayMs + this.leadMs);
22231
22239
  return true;
22232
22240
  }
22241
+ /** A held datagram's charge against its queue: payload plus IP and UDP headers. */
22242
+ charge(held) {
22243
+ return held.datagram.byteLength + this.headerOctets;
22244
+ }
22233
22245
  enqueue(held, path, now) {
22234
- if (this.holdBytes[path] + held.datagram.byteLength > this.queueLimit[path]) {
22246
+ if (this.holdBytes[path] + this.charge(held) > this.queueLimit[path]) {
22235
22247
  return { verdict: "overflow", admitted: [] };
22236
22248
  }
22237
22249
  held.expiry = now + this.maxAuthDelayMs + (path === "unicast" ? this.widenMs : 0);
22238
22250
  held.order = this.order++;
22239
22251
  this.hold[path].push(held);
22240
- this.holdBytes[path] += held.datagram.byteLength;
22252
+ this.holdBytes[path] += this.charge(held);
22241
22253
  return { verdict: "held", admitted: [] };
22242
22254
  }
22243
22255
  /** Remove and return the oldest held datagram with `digest`, across both queues. */
@@ -22252,7 +22264,7 @@ var BcProvenanceV2Receiver = class {
22252
22264
  if (!best)
22253
22265
  return void 0;
22254
22266
  const [held] = this.hold[best.path].splice(best.index, 1);
22255
- this.holdBytes[best.path] -= held.datagram.byteLength;
22267
+ this.holdBytes[best.path] -= this.charge(held);
22256
22268
  return held.datagram;
22257
22269
  }
22258
22270
  dropAdmittedHeld() {
@@ -22260,7 +22272,7 @@ var BcProvenanceV2Receiver = class {
22260
22272
  this.hold[path] = this.hold[path].filter((h) => {
22261
22273
  const keep = h.digest === void 0 || !this.admittedUntil.has(h.digest);
22262
22274
  if (!keep)
22263
- this.holdBytes[path] -= h.datagram.byteLength;
22275
+ this.holdBytes[path] -= this.charge(h);
22264
22276
  return keep;
22265
22277
  });
22266
22278
  }
@@ -22344,7 +22356,7 @@ var BcProvenanceV2Receiver = class {
22344
22356
  }
22345
22357
  take(path, now) {
22346
22358
  const b = this.buckets[path];
22347
- b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * BUCKET_RATE_PER_MS);
22359
+ b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * this.bucketRatePerMs);
22348
22360
  b.last = now;
22349
22361
  if (b.tokens < 1) {
22350
22362
  b.dropped++;
@@ -22358,7 +22370,7 @@ var BcProvenanceV2Receiver = class {
22358
22370
  this.hold[path] = this.hold[path].filter((h) => {
22359
22371
  if (h.expiry > now)
22360
22372
  return true;
22361
- this.holdBytes[path] -= h.datagram.byteLength;
22373
+ this.holdBytes[path] -= this.charge(h);
22362
22374
  this.expired[path]++;
22363
22375
  return false;
22364
22376
  });
@@ -26069,6 +26081,10 @@ var MulticastTransport = class {
26069
26081
  #acceptedBytesByPacketId = /* @__PURE__ */ new Map();
26070
26082
  #packetIds = null;
26071
26083
  #protected = false;
26084
+ // A protected join's S, and the last sender spelling that matched it, so a
26085
+ // matching sender parses once per spelling rather than per datagram.
26086
+ #protectedSource = void 0;
26087
+ #matchedSource = void 0;
26072
26088
  // The sender S this join's accepted packets came from, once known; see
26073
26089
  // packetsDroppedForeignSender. Cleared by every join and by a silence
26074
26090
  // failback, so a replacement publisher is not locked out.
@@ -26120,6 +26136,11 @@ var MulticastTransport = class {
26120
26136
  return this.#declined(generation, "unavailable");
26121
26137
  }
26122
26138
  let failure = "invalid-request";
26139
+ const protectedSource = config.protected === true ? config.sourceAddress ?? config.senderAddress : void 0;
26140
+ if (config.protected === true && (protectedSource === void 0 || config.atsc3)) {
26141
+ console.warn("[MulticastTransport] protected join refused: the path reports no per-datagram sender to check against S");
26142
+ return this.#declined(generation, "invalid-request");
26143
+ }
26123
26144
  try {
26124
26145
  const packetIds = config.packetIds?.length ? new Set(config.packetIds) : null;
26125
26146
  console.log(
@@ -26155,7 +26176,7 @@ var MulticastTransport = class {
26155
26176
  this.#subscription = subscription;
26156
26177
  const live = () => !this.#disposed && generation === this.#joinGeneration;
26157
26178
  const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
26158
- const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
26179
+ const packetListener = !anySource && protectedSource === void 0 && subscriptionDeliversPacketBatches(subscription) ? {
26159
26180
  type: "packets",
26160
26181
  listener: (event) => {
26161
26182
  if (!live()) return;
@@ -26171,6 +26192,8 @@ var MulticastTransport = class {
26171
26192
  this.#packetListener = packetListener;
26172
26193
  this.#packetIds = packetIds;
26173
26194
  this.#protected = config.protected === true;
26195
+ this.#protectedSource = protectedSource;
26196
+ this.#matchedSource = void 0;
26174
26197
  this.#providerFilter = {
26175
26198
  acknowledged: packetIds,
26176
26199
  sent: 0,
@@ -26630,13 +26653,23 @@ var MulticastTransport = class {
26630
26653
  this.#delivery = void 0;
26631
26654
  this.#packetIds = null;
26632
26655
  this.#protected = false;
26656
+ this.#protectedSource = void 0;
26657
+ this.#matchedSource = void 0;
26633
26658
  this.#providerFilter = null;
26634
26659
  this.#context = void 0;
26635
26660
  this.#withdrawnPacketIds.clear();
26636
26661
  }
26637
26662
  /** The ingress filter; an accepted packet is counted here, once. */
26638
26663
  #acceptPacket(packet, sourceAddress) {
26639
- const owner = this.#lockedTo?.() ?? this.#sender;
26664
+ const source = this.#protectedSource;
26665
+ if (source !== void 0 && (sourceAddress === void 0 || sourceAddress !== this.#matchedSource)) {
26666
+ if (sourceAddress === void 0 || !sameIpAddress(sourceAddress, source)) {
26667
+ this.#stats.packetsDroppedForeignSender++;
26668
+ return false;
26669
+ }
26670
+ this.#matchedSource = sourceAddress;
26671
+ }
26672
+ const owner = source === void 0 ? this.#lockedTo?.() ?? this.#sender : void 0;
26640
26673
  if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
26641
26674
  this.#stats.packetsDroppedForeignSender++;
26642
26675
  return false;