werift 0.24.0 → 0.24.2

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.
Files changed (68) hide show
  1. package/lib/common/src/binary.js +2 -3
  2. package/lib/common/src/binary.js.map +1 -1
  3. package/lib/dtls/src/context/transport.d.ts +2 -2
  4. package/lib/dtls/src/context/transport.js +2 -2
  5. package/lib/dtls/src/context/transport.js.map +1 -1
  6. package/lib/dtls/src/socket.d.ts +3 -2
  7. package/lib/dtls/src/socket.js +2 -2
  8. package/lib/dtls/src/socket.js.map +1 -1
  9. package/lib/ice/src/ice.d.ts +20 -0
  10. package/lib/ice/src/ice.js +263 -66
  11. package/lib/ice/src/ice.js.map +1 -1
  12. package/lib/ice/src/iceBase.d.ts +22 -1
  13. package/lib/ice/src/iceBase.js +30 -1
  14. package/lib/ice/src/iceBase.js.map +1 -1
  15. package/lib/ice/src/internal/selectAddresses.d.ts +9 -0
  16. package/lib/ice/src/internal/selectAddresses.js +40 -0
  17. package/lib/ice/src/internal/selectAddresses.js.map +1 -0
  18. package/lib/ice/src/stun/protocol.d.ts +2 -2
  19. package/lib/ice/src/stun/protocol.js +17 -3
  20. package/lib/ice/src/stun/protocol.js.map +1 -1
  21. package/lib/ice/src/stun/tcpProtocol.d.ts +2 -2
  22. package/lib/ice/src/stun/tcpProtocol.js +13 -3
  23. package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
  24. package/lib/ice/src/stun/transaction.d.ts +41 -4
  25. package/lib/ice/src/stun/transaction.js +193 -24
  26. package/lib/ice/src/stun/transaction.js.map +1 -1
  27. package/lib/ice/src/turn/protocol.d.ts +3 -3
  28. package/lib/ice/src/turn/protocol.js +31 -6
  29. package/lib/ice/src/turn/protocol.js.map +1 -1
  30. package/lib/ice/src/types/model.d.ts +27 -1
  31. package/lib/ice/src/types/model.js.map +1 -1
  32. package/lib/ice/src/utils.js +2 -31
  33. package/lib/ice/src/utils.js.map +1 -1
  34. package/lib/ice-server/src/stun/attributes.js +9 -47
  35. package/lib/ice-server/src/stun/attributes.js.map +1 -1
  36. package/lib/ice-server/src/stun/ip.d.ts +12 -0
  37. package/lib/ice-server/src/stun/ip.js +88 -0
  38. package/lib/ice-server/src/stun/ip.js.map +1 -0
  39. package/lib/ice-server/src/stun/message.js +8 -0
  40. package/lib/ice-server/src/stun/message.js.map +1 -1
  41. package/lib/index.mjs +652 -234
  42. package/lib/nonstandard/index.mjs +121 -54
  43. package/lib/rtp/src/codec/av1.js +11 -5
  44. package/lib/rtp/src/codec/av1.js.map +1 -1
  45. package/lib/rtp/src/codec/leb128.d.ts +5 -0
  46. package/lib/rtp/src/codec/leb128.js +24 -0
  47. package/lib/rtp/src/codec/leb128.js.map +1 -0
  48. package/lib/rtp/src/rtp/rtx.js +4 -6
  49. package/lib/rtp/src/rtp/rtx.js.map +1 -1
  50. package/lib/rtp/src/srtp/context/context.d.ts +2 -0
  51. package/lib/rtp/src/srtp/context/context.js +12 -12
  52. package/lib/rtp/src/srtp/context/context.js.map +1 -1
  53. package/lib/sctp/src/param.js +31 -15
  54. package/lib/sctp/src/param.js.map +1 -1
  55. package/lib/sctp/src/sctp.js +10 -9
  56. package/lib/sctp/src/sctp.js.map +1 -1
  57. package/lib/webrtc/src/media/rtpSender.js +2 -3
  58. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  59. package/lib/webrtc/src/peerConnection.d.ts +2 -2
  60. package/lib/webrtc/src/sdp.js +1 -35
  61. package/lib/webrtc/src/sdp.js.map +1 -1
  62. package/lib/webrtc/src/secureTransportManager.d.ts +1 -1
  63. package/lib/webrtc/src/secureTransportManager.js +12 -0
  64. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  65. package/lib/webrtc/src/transport/dtls.d.ts +1 -1
  66. package/lib/webrtc/src/transport/sctp.js +17 -16
  67. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  68. package/package.json +1 -7
@@ -32,16 +32,11 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.Connection = void 0;
40
37
  const crypto_1 = require("crypto");
41
38
  const net_1 = require("net");
42
- const Int64 = __importStar(require("int64-buffer"));
43
39
  const timers = __importStar(require("node:timers/promises"));
44
- const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
45
40
  const common_1 = require("./imports/common");
46
41
  const candidate_1 = require("./candidate");
47
42
  const lookup_1 = require("./dns/lookup");
@@ -150,7 +145,7 @@ class Connection {
150
145
  enumerable: true,
151
146
  configurable: true,
152
147
  writable: true,
153
- value: BigInt(new Int64.Uint64BE((0, crypto_1.randomBytes)(64)).toString())
148
+ value: (0, crypto_1.randomBytes)(8).readBigUInt64BE(0)
154
149
  });
155
150
  Object.defineProperty(this, "state", {
156
151
  enumerable: true,
@@ -225,6 +220,32 @@ class Connection {
225
220
  writable: true,
226
221
  value: void 0
227
222
  });
223
+ /** RFC 7675 consent-to-send: application data may use the selected pair. */
224
+ Object.defineProperty(this, "consentFresh", {
225
+ enumerable: true,
226
+ configurable: true,
227
+ writable: true,
228
+ value: false
229
+ });
230
+ /** Invalidates in-flight consent callbacks on restart / close / replace / expire. */
231
+ Object.defineProperty(this, "consentSessionId", {
232
+ enumerable: true,
233
+ configurable: true,
234
+ writable: true,
235
+ value: 0
236
+ });
237
+ Object.defineProperty(this, "consentExpiryTimer", {
238
+ enumerable: true,
239
+ configurable: true,
240
+ writable: true,
241
+ value: void 0
242
+ });
243
+ Object.defineProperty(this, "consentRequestAbort", {
244
+ enumerable: true,
245
+ configurable: true,
246
+ writable: true,
247
+ value: void 0
248
+ });
228
249
  Object.defineProperty(this, "onData", {
229
250
  enumerable: true,
230
251
  configurable: true,
@@ -243,7 +264,7 @@ class Connection {
243
264
  writable: true,
244
265
  value: new common_1.Event()
245
266
  });
246
- // 4.1.1.4 ? 生存確認 life check
267
+ // RFC 7675 consent freshness
247
268
  Object.defineProperty(this, "queryConsent", {
248
269
  enumerable: true,
249
270
  configurable: true,
@@ -252,70 +273,164 @@ class Connection {
252
273
  if (this.iceLite) {
253
274
  return;
254
275
  }
255
- if (this.queryConsentHandle) {
256
- this.queryConsentHandle.resolve();
257
- }
258
- this.queryConsentHandle = (0, helper_1.cancelable)(async (_, __, onCancel) => {
259
- let failures = 0;
276
+ // Invalidate any previous consent session before starting a new one.
277
+ this.stopConsentLifecycle();
278
+ const sessionId = this.consentSessionId;
279
+ this.consentFresh = true;
280
+ const handle = (0, helper_1.cancelable)(async (_, __, onCancel) => {
260
281
  let canceled = false;
261
282
  const cancelEvent = new AbortController();
283
+ const clearConsentExpiry = () => {
284
+ if (this.consentExpiryTimer === undefined) {
285
+ return;
286
+ }
287
+ clearTimeout(this.consentExpiryTimer);
288
+ this.consentExpiryTimer = undefined;
289
+ };
290
+ const refreshConsentExpiry = () => {
291
+ // Only the active session may refresh the shared expiry timer.
292
+ if (canceled || sessionId !== this.consentSessionId) {
293
+ return;
294
+ }
295
+ clearConsentExpiry();
296
+ this.consentExpiryTimer = setTimeout(() => {
297
+ this.consentExpiryTimer = undefined;
298
+ if (canceled || sessionId !== this.consentSessionId) {
299
+ return;
300
+ }
301
+ if (this.state === "closed" || this.state === "failed") {
302
+ return;
303
+ }
304
+ log("Consent to send expired");
305
+ // Expire independently of request cadence / failure count (RFC 7675).
306
+ this.consentFresh = false;
307
+ this.consentSessionId++;
308
+ this.consentRequestAbort?.abort();
309
+ this.consentRequestAbort = undefined;
310
+ if (this.queryConsentHandle === handle) {
311
+ this.queryConsentHandle = undefined;
312
+ }
313
+ canceled = true;
314
+ cancelEvent.abort();
315
+ // Transport stays available for ICE restart; explicit close() uses "closed".
316
+ this.setState("failed");
317
+ }, iceBase_1.CONSENT_TIMEOUT * 1000);
318
+ };
262
319
  onCancel.once(() => {
263
320
  canceled = true;
264
- failures += iceBase_1.CONSENT_FAILURES;
321
+ // Avoid clearing a replacement session's timer/handle.
322
+ if (sessionId === this.consentSessionId) {
323
+ clearConsentExpiry();
324
+ this.consentRequestAbort?.abort();
325
+ this.consentRequestAbort = undefined;
326
+ }
265
327
  cancelEvent.abort();
266
- this.queryConsentHandle = undefined;
328
+ if (this.queryConsentHandle === handle) {
329
+ this.queryConsentHandle = undefined;
330
+ }
267
331
  });
268
- const { localUsername, remoteUsername, iceControlling } = this;
269
- // """
270
- // Periodically check consent (RFC 7675).
271
- // """
332
+ // Initial ICE check success is the first valid consent response.
333
+ refreshConsentExpiry();
334
+ const randomizedConsentInterval = () => iceBase_1.CONSENT_INTERVAL * (0.8 + 0.4 * Math.random()) * 1000;
335
+ // Cadence is measured between request *starts*, not response completions.
336
+ let nextConsentAt = Date.now() + randomizedConsentInterval();
337
+ const isTerminalState = () => this.state === "closed" || this.state === "failed";
272
338
  try {
273
- while (this.state !== "closed" && !canceled) {
274
- // # randomize between 0.8 and 1.2 times CONSENT_INTERVAL
275
- await timers.setTimeout(iceBase_1.CONSENT_INTERVAL * (0.8 + 0.4 * Math.random()) * 1000, undefined, { signal: cancelEvent.signal });
339
+ while (!isTerminalState() &&
340
+ !canceled &&
341
+ sessionId === this.consentSessionId) {
342
+ await this.abortableDelay(Math.max(0, nextConsentAt - Date.now()), cancelEvent.signal);
343
+ if (canceled ||
344
+ isTerminalState() ||
345
+ sessionId !== this.consentSessionId) {
346
+ break;
347
+ }
348
+ // Fix next start time before awaiting any response (independent timers).
349
+ nextConsentAt = Date.now() + randomizedConsentInterval();
276
350
  const nominated = this.nominated;
277
- if (!nominated || canceled) {
351
+ if (!nominated) {
278
352
  break;
279
353
  }
354
+ const pairId = nominated.id;
355
+ const generation = this.generation;
356
+ const remotePassword = this.remotePassword;
357
+ const { localUsername, remoteUsername, iceControlling } = this;
280
358
  const request = this.buildRequest({
281
- nominate: false,
359
+ nominate: this.shouldNominateConsentRequest(nominated),
282
360
  localUsername,
283
361
  remoteUsername,
284
362
  iceControlling,
285
363
  localCandidate: nominated.localCandidate,
286
364
  });
287
- try {
288
- nominated.consentRequestsSent++;
289
- nominated.requestsSent++;
290
- await nominated.protocol.request(request, nominated.remoteAddr, Buffer.from(this.remotePassword, "utf8"), 0, (attempt) => {
365
+ this.consentRequestAbort?.abort();
366
+ const requestAbort = new AbortController();
367
+ this.consentRequestAbort = requestAbort;
368
+ nominated.consentRequestsSent++;
369
+ nominated.requestsSent++;
370
+ // RTT-aware wait (floor 500ms); independent of retransmissions: 0.
371
+ const responseTimeout = (0, iceBase_1.consentResponseTimeoutMs)(nominated.rtt);
372
+ const requestStartedAt = performance.now();
373
+ // Do not await here: response wait must not stretch the 4–6s cadence.
374
+ nominated.protocol
375
+ .request(request, nominated.remoteAddr, Buffer.from(remotePassword, "utf8"), {
376
+ retransmissions: 0,
377
+ responseTimeout,
378
+ signal: requestAbort.signal,
379
+ onRequestSent: (attempt) => {
291
380
  if (attempt > 0) {
292
381
  nominated.retransmissionsSent++;
293
382
  }
294
- });
383
+ },
384
+ })
385
+ .then(() => {
386
+ // Accept only responses for the current pair / generation / session.
387
+ // Address / MESSAGE-INTEGRITY / response class are enforced in Transaction + protocol.
388
+ if (sessionId !== this.consentSessionId || canceled) {
389
+ return;
390
+ }
391
+ const state = this.state;
392
+ if (state === "closed" || state === "failed") {
393
+ return;
394
+ }
395
+ if (this.nominated?.id !== pairId) {
396
+ return;
397
+ }
398
+ if (this.generation !== generation) {
399
+ return;
400
+ }
401
+ if (this.remotePassword !== remotePassword) {
402
+ return;
403
+ }
404
+ const rtt = (performance.now() - requestStartedAt) / 1000; // seconds
405
+ nominated.rtt = rtt;
406
+ nominated.totalRoundTripTime += rtt;
407
+ nominated.roundTripTimeMeasurements++;
295
408
  nominated.responsesReceived++;
296
- failures = 0;
297
- if (this.state === "disconnected") {
409
+ this.consentFresh = true;
410
+ refreshConsentExpiry();
411
+ if (state === "disconnected") {
298
412
  this.setState("connected");
299
413
  }
300
- }
301
- catch (error) {
302
- if (nominated.id === this.nominated?.id) {
303
- log("no stun response");
304
- failures++;
305
- this.setState("disconnected");
306
- break;
414
+ })
415
+ .catch((error) => {
416
+ // Individual request loss is expected; keep monitoring (RFC 7675).
417
+ if (sessionId === this.consentSessionId &&
418
+ this.nominated?.id === pairId) {
419
+ log("no stun response", error);
307
420
  }
308
- }
309
- if (failures >= iceBase_1.CONSENT_FAILURES) {
310
- log("Consent to send expired");
311
- this.queryConsentHandle = undefined;
312
- this.setState("closed");
313
- break;
314
- }
421
+ });
422
+ }
423
+ }
424
+ catch (error) {
425
+ // Abort during delay is normal on cancel / expire.
426
+ }
427
+ finally {
428
+ if (sessionId === this.consentSessionId) {
429
+ clearConsentExpiry();
315
430
  }
316
431
  }
317
- catch (error) { }
318
432
  });
433
+ this.queryConsentHandle = handle;
319
434
  }
320
435
  });
321
436
  Object.defineProperty(this, "send", {
@@ -323,17 +438,15 @@ class Connection {
323
438
  configurable: true,
324
439
  writable: true,
325
440
  value: async (data) => {
326
- const activePair = this.nominated;
327
- if (activePair) {
328
- await activePair.protocol.sendData(data, activePair.remoteAddr);
329
- // Update statistics
330
- activePair.packetsSent++;
331
- activePair.bytesSent += data.length;
332
- }
333
- else {
334
- // log("Cannot send data, ice not connected");
441
+ // RFC 7675: after consent expiry, do not send application data on the 5-tuple.
442
+ if (!this.canSendApplicationData()) {
335
443
  return;
336
444
  }
445
+ const activePair = this.nominated;
446
+ await activePair.protocol.sendData(data, activePair.remoteAddr);
447
+ // Update statistics
448
+ activePair.packetsSent++;
449
+ activePair.bytesSent += data.length;
337
450
  }
338
451
  });
339
452
  // 3. Terminology : Check
@@ -421,7 +534,8 @@ class Connection {
421
534
  }
422
535
  }
423
536
  // # check remote address matches
424
- if (!(0, fast_deep_equal_1.default)(result.addr, pair.remoteAddr)) {
537
+ if (result.addr[0] !== pair.remoteAddr[0] ||
538
+ result.addr[1] !== pair.remoteAddr[1]) {
425
539
  pair.updateState(iceBase_1.CandidatePairState.FAILED);
426
540
  this.checkComplete(pair);
427
541
  r();
@@ -496,13 +610,13 @@ class Connection {
496
610
  if (this.iceLite) {
497
611
  value = false;
498
612
  }
499
- if (this.generation > 0 || this.nominated) {
613
+ // While a pair is selected, keep the negotiated role. ICE restart clears
614
+ // `nominated`, so offer/answer role and RFC 8445 role-conflict repair can
615
+ // reassign controlling/controlled for the new generation.
616
+ if (this.nominated) {
500
617
  return;
501
618
  }
502
- this._iceControlling = value;
503
- for (const pair of this.checkList) {
504
- pair.iceControlling = value;
505
- }
619
+ this.applyIceControlling(value);
506
620
  }
507
621
  get iceLite() {
508
622
  return this.options.iceLite;
@@ -542,13 +656,15 @@ class Connection {
542
656
  protocol.localCandidate.ufrag = this.localUsername;
543
657
  }
544
658
  }
545
- this.queryConsentHandle?.resolve?.();
546
- this.queryConsentHandle = undefined;
659
+ // Tear down consent timers/transactions; new credentials require a new session.
660
+ this.stopConsentLifecycle();
547
661
  }
548
662
  resetNominatedPair() {
549
663
  log("resetNominatedPair");
550
664
  this.nominated = undefined;
551
665
  this.nominating = false;
666
+ // Drop old pair's consent timers/transactions; restarted when a new pair is nominated.
667
+ this.stopConsentLifecycle();
552
668
  }
553
669
  setRemoteParams({ iceLite, usernameFragment, password, }) {
554
670
  log("setRemoteParams", { iceLite, usernameFragment, password });
@@ -560,6 +676,15 @@ class Connection {
560
676
  async gatherCandidates() {
561
677
  if (!this.localCandidatesStart) {
562
678
  this.localCandidatesStart = true;
679
+ // ICE restart keeps transport protocols; re-advertise their host candidates
680
+ // with the new generation / ufrag before gathering additional addresses.
681
+ for (const protocol of this.protocols) {
682
+ if (protocol.localCandidate) {
683
+ protocol.localCandidate.generation = this.generation;
684
+ protocol.localCandidate.ufrag = this.localUsername;
685
+ this.appendLocalCandidate(protocol.localCandidate);
686
+ }
687
+ }
563
688
  let address = (0, utils_1.getHostAddresses)(this.options.useIpv4, this.options.useIpv6, {
564
689
  useLinkLocalAddress: this.options.useLinkLocalAddress,
565
690
  });
@@ -921,13 +1046,69 @@ class Connection {
921
1046
  }
922
1047
  return false;
923
1048
  }
1049
+ /**
1050
+ * Stop consent request cadence, expiry timer, and outstanding transactions.
1051
+ * Does not change ICE state by itself.
1052
+ */
1053
+ stopConsentLifecycle() {
1054
+ this.consentSessionId++;
1055
+ this.consentFresh = false;
1056
+ if (this.consentExpiryTimer !== undefined) {
1057
+ clearTimeout(this.consentExpiryTimer);
1058
+ this.consentExpiryTimer = undefined;
1059
+ }
1060
+ this.consentRequestAbort?.abort();
1061
+ this.consentRequestAbort = undefined;
1062
+ const handle = this.queryConsentHandle;
1063
+ this.queryConsentHandle = undefined;
1064
+ // Resolve after clearing the field so a stale onCancel cannot wipe a replacement.
1065
+ handle?.resolve?.();
1066
+ }
1067
+ /**
1068
+ * ICE-lite interop only (not required by RFC 7675): mirror libwebrtc
1069
+ * semi-aggressive nomination — attach USE-CANDIDATE when we are controlling,
1070
+ * the remote is ICE-lite, and the target is the current selected pair.
1071
+ */
1072
+ shouldNominateConsentRequest(pair) {
1073
+ return (this.iceControlling && this.remoteIsLite && this.nominated?.id === pair.id);
1074
+ }
1075
+ canSendApplicationData() {
1076
+ if (!this.nominated) {
1077
+ return false;
1078
+ }
1079
+ if (this.state === "closed" || this.state === "failed") {
1080
+ return false;
1081
+ }
1082
+ // Local ICE-lite does not run consent checks; full agents need fresh consent.
1083
+ if (this.iceLite) {
1084
+ return true;
1085
+ }
1086
+ return this.consentFresh;
1087
+ }
1088
+ abortableDelay(ms, signal) {
1089
+ return new Promise((resolve, reject) => {
1090
+ if (signal.aborted) {
1091
+ reject(new DOMException("The operation was aborted", "AbortError"));
1092
+ return;
1093
+ }
1094
+ const timer = setTimeout(() => {
1095
+ signal.removeEventListener("abort", onAbort);
1096
+ resolve();
1097
+ }, ms);
1098
+ const onAbort = () => {
1099
+ clearTimeout(timer);
1100
+ reject(new DOMException("The operation was aborted", "AbortError"));
1101
+ };
1102
+ signal.addEventListener("abort", onAbort, { once: true });
1103
+ });
1104
+ }
924
1105
  async close() {
925
1106
  // """
926
1107
  // Close the connection.
927
1108
  // """
928
1109
  this.setState("closed");
929
1110
  // # stop consent freshness tests
930
- this.queryConsentHandle?.resolve?.();
1111
+ this.stopConsentLifecycle();
931
1112
  // # stop check list
932
1113
  if (this.checkList && !this.checkListDone) {
933
1114
  this.checkListState.put(new Promise((r) => {
@@ -1013,13 +1194,23 @@ class Connection {
1013
1194
  (0, iceBase_1.sortCandidatePairs)(this.checkList, this.iceControlling);
1014
1195
  }
1015
1196
  findPair(protocol, remoteCandidate) {
1016
- const pair = this.checkList.find((pair) => (0, fast_deep_equal_1.default)(pair.protocol, protocol) &&
1017
- (0, fast_deep_equal_1.default)(pair.remoteCandidate, remoteCandidate));
1197
+ const pair = this.checkList.find((pair) => pair.protocol === protocol && pair.remoteCandidate === remoteCandidate);
1018
1198
  return pair;
1019
1199
  }
1200
+ applyIceControlling(iceControlling) {
1201
+ this._iceControlling = iceControlling;
1202
+ for (const pair of this.checkList) {
1203
+ pair.iceControlling = iceControlling;
1204
+ }
1205
+ }
1020
1206
  switchRole(iceControlling) {
1021
1207
  log("switch role", iceControlling);
1022
- this.iceControlling = iceControlling;
1208
+ // Role conflicts must be repaired even after a prior generation or while
1209
+ // connectivity checks are in flight (RFC 8445 §7.2.5.1 / §7.3.1.1).
1210
+ if (this.iceLite) {
1211
+ iceControlling = false;
1212
+ }
1213
+ this.applyIceControlling(iceControlling);
1023
1214
  this.sortCheckList();
1024
1215
  }
1025
1216
  checkComplete(pair) {
@@ -1040,6 +1231,12 @@ class Connection {
1040
1231
  this.nominated = pair;
1041
1232
  this.nominating = false;
1042
1233
  this.pruneTcpConnections(pair);
1234
+ // After resetNominatedPair / renomination while already connected,
1235
+ // restart consent freshness on the new selected pair.
1236
+ if (!this.iceLite &&
1237
+ (this.state === "connected" || this.state === "completed")) {
1238
+ this.queryConsent();
1239
+ }
1043
1240
  // 8.1.2. Updating States
1044
1241
  // The agent MUST remove all Waiting and Frozen pairs in the check
1045
1242
  // list and triggered check queue for the same component as the