@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.
@@ -15864,7 +15864,6 @@ _RistrettoPoint.Fn = /* @__PURE__ */ (() => Fn)();
15864
15864
  var BCPV2_LEAD_BOUND_MS = 5e3;
15865
15865
  var REPLAY_WINDOW = 64n;
15866
15866
  var BUCKET_DEPTH = 64;
15867
- var BUCKET_RATE_PER_MS = 1;
15868
15867
  var hex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
15869
15868
  function sameBytes(a, b) {
15870
15869
  if (a.byteLength !== b.byteLength)
@@ -15893,6 +15892,10 @@ var BcProvenanceV2Receiver = class {
15893
15892
  verifySig;
15894
15893
  accepted;
15895
15894
  queueLimit;
15895
+ /** Octets a held datagram is charged beyond its payload: the IPv4 or IPv6 plus UDP headers (step 2). */
15896
+ headerOctets;
15897
+ /** Budget refill, tokens per ms: V / 1000 (step 1). */
15898
+ bucketRatePerMs;
15896
15899
  current = null;
15897
15900
  previous = null;
15898
15901
  graceStart = {};
@@ -15938,6 +15941,8 @@ var BcProvenanceV2Receiver = class {
15938
15941
  multicast: this.maxAuthDelayMs * bandwidth / 8e3,
15939
15942
  unicast: (this.maxAuthDelayMs + this.widenMs) * bandwidth / 8e3
15940
15943
  };
15944
+ this.headerOctets = parseBcProvenanceV2Address(cfg.endpoint.groupAddress).byteLength === 16 ? 48 : 28;
15945
+ this.bucketRatePerMs = Math.max(1e3, 1.25 * bandwidth / (8 * 512 * 37)) / 1e3;
15941
15946
  const t = this.now();
15942
15947
  this.buckets = {
15943
15948
  multicast: { tokens: BUCKET_DEPTH, last: t, dropped: 0 },
@@ -16036,7 +16041,7 @@ var BcProvenanceV2Receiver = class {
16036
16041
  arrival: now,
16037
16042
  sent,
16038
16043
  digest: hex(sha2562(bytes)),
16039
- pairs: record.digests.map((d, i) => ({ digest: hex(d), seq: record.firstSeq + BigInt(i), admitted: false })),
16044
+ pairs: record.digests.map((d, i) => ({ digest: hex(d), epoch, seq: record.firstSeq + BigInt(i), admitted: false })),
16040
16045
  awaitingMulticast: path === "mirror"
16041
16046
  };
16042
16047
  this.verified.set(id, entry);
@@ -16103,12 +16108,12 @@ var BcProvenanceV2Receiver = class {
16103
16108
  digest(datagram) {
16104
16109
  return hex(bcProvenanceV2DatagramDigest(this.cfg.endpoint, datagram));
16105
16110
  }
16106
- /** Consume the lowest-seq pending entry for `digest`, admitting it. */
16111
+ /** Consume the pending entry for `digest` lowest in (epoch, sequence) order, admitting it. */
16107
16112
  consumePending(digest, now) {
16108
16113
  const list = this.pending.get(digest);
16109
16114
  if (!list?.length)
16110
16115
  return false;
16111
- list.sort((a, b) => a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0);
16116
+ 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));
16112
16117
  const hit = list.shift();
16113
16118
  if (list.length === 0)
16114
16119
  this.pending.delete(digest);
@@ -16117,14 +16122,18 @@ var BcProvenanceV2Receiver = class {
16117
16122
  this.admittedUntil.set(digest, now + this.maxAuthDelayMs + this.leadMs);
16118
16123
  return true;
16119
16124
  }
16125
+ /** A held datagram's charge against its queue: payload plus IP and UDP headers. */
16126
+ charge(held) {
16127
+ return held.datagram.byteLength + this.headerOctets;
16128
+ }
16120
16129
  enqueue(held, path, now) {
16121
- if (this.holdBytes[path] + held.datagram.byteLength > this.queueLimit[path]) {
16130
+ if (this.holdBytes[path] + this.charge(held) > this.queueLimit[path]) {
16122
16131
  return { verdict: "overflow", admitted: [] };
16123
16132
  }
16124
16133
  held.expiry = now + this.maxAuthDelayMs + (path === "unicast" ? this.widenMs : 0);
16125
16134
  held.order = this.order++;
16126
16135
  this.hold[path].push(held);
16127
- this.holdBytes[path] += held.datagram.byteLength;
16136
+ this.holdBytes[path] += this.charge(held);
16128
16137
  return { verdict: "held", admitted: [] };
16129
16138
  }
16130
16139
  /** Remove and return the oldest held datagram with `digest`, across both queues. */
@@ -16139,7 +16148,7 @@ var BcProvenanceV2Receiver = class {
16139
16148
  if (!best)
16140
16149
  return void 0;
16141
16150
  const [held] = this.hold[best.path].splice(best.index, 1);
16142
- this.holdBytes[best.path] -= held.datagram.byteLength;
16151
+ this.holdBytes[best.path] -= this.charge(held);
16143
16152
  return held.datagram;
16144
16153
  }
16145
16154
  dropAdmittedHeld() {
@@ -16147,7 +16156,7 @@ var BcProvenanceV2Receiver = class {
16147
16156
  this.hold[path] = this.hold[path].filter((h) => {
16148
16157
  const keep = h.digest === void 0 || !this.admittedUntil.has(h.digest);
16149
16158
  if (!keep)
16150
- this.holdBytes[path] -= h.datagram.byteLength;
16159
+ this.holdBytes[path] -= this.charge(h);
16151
16160
  return keep;
16152
16161
  });
16153
16162
  }
@@ -16231,7 +16240,7 @@ var BcProvenanceV2Receiver = class {
16231
16240
  }
16232
16241
  take(path, now) {
16233
16242
  const b = this.buckets[path];
16234
- b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * BUCKET_RATE_PER_MS);
16243
+ b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * this.bucketRatePerMs);
16235
16244
  b.last = now;
16236
16245
  if (b.tokens < 1) {
16237
16246
  b.dropped++;
@@ -16245,7 +16254,7 @@ var BcProvenanceV2Receiver = class {
16245
16254
  this.hold[path] = this.hold[path].filter((h) => {
16246
16255
  if (h.expiry > now)
16247
16256
  return true;
16248
- this.holdBytes[path] -= h.datagram.byteLength;
16257
+ this.holdBytes[path] -= this.charge(h);
16249
16258
  this.expired[path]++;
16250
16259
  return false;
16251
16260
  });
@@ -20863,6 +20872,9 @@ var AMTGatewayManager = class {
20863
20872
  */
20864
20873
  async subscribe(config) {
20865
20874
  const { key } = config;
20875
+ if (config.protected) {
20876
+ throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
20877
+ }
20866
20878
  const keyStr = this.getKeyString(key);
20867
20879
  if (this.subscriptions.has(keyStr)) {
20868
20880
  console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
@@ -21100,6 +21112,10 @@ var MulticastTransport = class {
21100
21112
  #acceptedBytesByPacketId = /* @__PURE__ */ new Map();
21101
21113
  #packetIds = null;
21102
21114
  #protected = false;
21115
+ // A protected join's S, and the last sender spelling that matched it, so a
21116
+ // matching sender parses once per spelling rather than per datagram.
21117
+ #protectedSource = void 0;
21118
+ #matchedSource = void 0;
21103
21119
  // The sender S this join's accepted packets came from, once known; see
21104
21120
  // packetsDroppedForeignSender. Cleared by every join and by a silence
21105
21121
  // failback, so a replacement publisher is not locked out.
@@ -21151,6 +21167,11 @@ var MulticastTransport = class {
21151
21167
  return this.#declined(generation, "unavailable");
21152
21168
  }
21153
21169
  let failure = "invalid-request";
21170
+ const protectedSource = config.protected === true ? config.sourceAddress ?? config.senderAddress : void 0;
21171
+ if (config.protected === true && (protectedSource === void 0 || config.atsc3)) {
21172
+ console.warn("[MulticastTransport] protected join refused: the path reports no per-datagram sender to check against S");
21173
+ return this.#declined(generation, "invalid-request");
21174
+ }
21154
21175
  try {
21155
21176
  const packetIds = config.packetIds?.length ? new Set(config.packetIds) : null;
21156
21177
  console.log(
@@ -21186,7 +21207,7 @@ var MulticastTransport = class {
21186
21207
  this.#subscription = subscription;
21187
21208
  const live = () => !this.#disposed && generation === this.#joinGeneration;
21188
21209
  const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
21189
- const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
21210
+ const packetListener = !anySource && protectedSource === void 0 && subscriptionDeliversPacketBatches(subscription) ? {
21190
21211
  type: "packets",
21191
21212
  listener: (event) => {
21192
21213
  if (!live()) return;
@@ -21202,6 +21223,8 @@ var MulticastTransport = class {
21202
21223
  this.#packetListener = packetListener;
21203
21224
  this.#packetIds = packetIds;
21204
21225
  this.#protected = config.protected === true;
21226
+ this.#protectedSource = protectedSource;
21227
+ this.#matchedSource = void 0;
21205
21228
  this.#providerFilter = {
21206
21229
  acknowledged: packetIds,
21207
21230
  sent: 0,
@@ -21661,13 +21684,23 @@ var MulticastTransport = class {
21661
21684
  this.#delivery = void 0;
21662
21685
  this.#packetIds = null;
21663
21686
  this.#protected = false;
21687
+ this.#protectedSource = void 0;
21688
+ this.#matchedSource = void 0;
21664
21689
  this.#providerFilter = null;
21665
21690
  this.#context = void 0;
21666
21691
  this.#withdrawnPacketIds.clear();
21667
21692
  }
21668
21693
  /** The ingress filter; an accepted packet is counted here, once. */
21669
21694
  #acceptPacket(packet, sourceAddress) {
21670
- const owner = this.#lockedTo?.() ?? this.#sender;
21695
+ const source = this.#protectedSource;
21696
+ if (source !== void 0 && (sourceAddress === void 0 || sourceAddress !== this.#matchedSource)) {
21697
+ if (sourceAddress === void 0 || !sameIpAddress(sourceAddress, source)) {
21698
+ this.#stats.packetsDroppedForeignSender++;
21699
+ return false;
21700
+ }
21701
+ this.#matchedSource = sourceAddress;
21702
+ }
21703
+ const owner = source === void 0 ? this.#lockedTo?.() ?? this.#sender : void 0;
21671
21704
  if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
21672
21705
  this.#stats.packetsDroppedForeignSender++;
21673
21706
  return false;