lemon-tls 0.9.0 → 0.11.0

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.
@@ -35,16 +35,24 @@ import createSecureContext from './secure_context.js';
35
35
  import {
36
36
  DTLS_VERSION,
37
37
  build_dtls_handshake,
38
+ build_dtls_fragment,
38
39
  parse_dtls_handshake,
39
40
  build_message,
40
41
  build_hello_verify_request,
41
42
  parse_hello_verify_request,
42
43
  parse_hello,
44
+ peer_alert_error,
45
+ local_alert_error,
46
+ read_peer_alert,
47
+ MAX_WARNING_ALERTS,
48
+ MAX_RECORD_PLAINTEXT,
49
+ TLS_ALERT,
50
+ TLS_MESSAGE_TYPE,
51
+ parseError,
43
52
  } from './wire.js';
44
53
 
45
54
  import {
46
55
  TLS_CIPHER_SUITES,
47
- default_cipher_suites,
48
56
  hkdf_expand_label,
49
57
  derive_sn_key,
50
58
  LABEL_PREFIX_TLS13,
@@ -56,7 +64,8 @@ import {
56
64
  getAeadAlgo,
57
65
  deriveKeys,
58
66
  deriveKeys12,
59
- decryptDtls12,
67
+ openDtls12,
68
+ acceptRecordSeq,
60
69
  buildDtlsPlaintext,
61
70
  buildEncryptedDtls12,
62
71
  buildEncryptedDtls13,
@@ -66,9 +75,8 @@ import {
66
75
  decryptEncryptedDtls13,
67
76
  } from './record.js';
68
77
 
69
- import { timingSafeEqualU8 } from './utils.js';
70
- import { SUPPORTED_GROUPS as ECDH_SUPPORTED_GROUPS, SERVER_DEFAULT_GROUPS } from './session/ecdh.js';
71
- import { default_signature_schemes } from './session/signing.js';
78
+ import { timingSafeEqualU8, concatUint8Arrays } from './utils.js';
79
+ import { versionsInRange, localLists } from './node_options.js';
72
80
 
73
81
 
74
82
  // ============================================================
@@ -121,6 +129,37 @@ function insertCookieIntoClientHello(ch1, cookie) {
121
129
  // record, ordering decision, and completion visible without a packet dump.
122
130
  var LEMON_DEBUG = typeof process !== 'undefined' && process.env &&
123
131
  (process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1');
132
+ // The two settable link parameters, checked where they are set (option or
133
+ // setter): a wrong value is a programming error, thrown at once.
134
+ // The connectionId option (RFC 9146, RFC 9147 §9): the CID this side asks
135
+ // for. true — a server, SERVER_CID_LEN random bytes (createDTLSServer finds
136
+ // its connections by them); a client, an empty one (it needs none: the server
137
+ // does not move) —, or the CID itself. TLSSession negotiates whatever it is.
138
+ const SERVER_CID_LEN = 8;
139
+ function connectionIdOption(v, isServer) {
140
+ if (v === true) return isServer ? new Uint8Array(crypto.randomBytes(SERVER_CID_LEN)) : new Uint8Array(0);
141
+ return v;
142
+ }
143
+
144
+ function checkedMtu(v, dflt) {
145
+ if (v === undefined || v === null) {
146
+ if (dflt !== undefined) return dflt;
147
+ throw new TypeError('DTLS mtu: a number of bytes is required');
148
+ }
149
+ if (!Number.isInteger(v) || v < 128 || v > 65507) throw new RangeError('DTLS mtu must be an integer in [128, 65507], got ' + v);
150
+ return v;
151
+ }
152
+ // The least a timer from a round-trip estimate starts at (currentRto).
153
+ const RTO_MIN_MS = 100;
154
+ function checkedRetransmitTimeout(v, dflt) {
155
+ if (v === undefined || v === null) {
156
+ if (dflt !== undefined) return dflt;
157
+ throw new TypeError('DTLS initialRetransmitTimeout: a number of milliseconds is required');
158
+ }
159
+ if (!Number.isInteger(v) || v < 10 || v > 60000) throw new RangeError('DTLS initialRetransmitTimeout must be an integer in [10, 60000] ms, got ' + v);
160
+ return v;
161
+ }
162
+
124
163
  function _ldbg() {
125
164
  if (!LEMON_DEBUG) return;
126
165
  if (typeof console !== 'undefined' && console.log) {
@@ -178,7 +217,12 @@ function DTLSSession(options) {
178
217
  let isServer = !!options.isServer;
179
218
 
180
219
  // ---- Internal TLSSession ----
181
- let tls = new TLSSession({
220
+ // Every option goes on to the TLS session as the caller gave it — the
221
+ // TLS options (customExtensions, ALPNCallback, ticketData, signature and
222
+ // group choices, ...) mean the same over DTLS — and DTLS sets only these.
223
+ // A hand-picked list here left out whatever it did not name, as
224
+ // createDTLSServer's did (r96, A6).
225
+ let tls = new TLSSession(Object.assign({}, options, {
182
226
  isServer: isServer,
183
227
  // RFC 9147 §5.3: the DTLS ClientHello's legacy_session_id "MUST be
184
228
  // set to a zero-length vector" — the 32-byte compatibility sid is a
@@ -190,37 +234,57 @@ function DTLSSession(options) {
190
234
  // byte and never the body, so any non-empty sid shears its whole
191
235
  // ClientHello parse.)
192
236
  sessionId: new Uint8Array(0),
193
- srtpProfiles: options.srtpProfiles,
194
- servername: options.servername,
195
- SNICallback: options.SNICallback,
196
- rejectUnauthorized: options.rejectUnauthorized,
197
- checkServerIdentity: options.checkServerIdentity,
198
237
  // DTLS peers in WebRTC present self-signed certificates by design and are
199
238
  // authenticated by the SDP fingerprint, not by a CA chain. Falling back to
200
239
  // the platform trust store here would mark every such peer unauthorized.
201
240
  useDefaultCA: options.useDefaultCA === true,
202
- ca: options.ca,
203
- noTickets: options.noTickets,
204
- requestCert: options.requestCert,
205
- cert: options.cert,
206
- key: options.key,
207
- ticketKeys: options.ticketKeys,
208
- });
241
+ // Resumption, as over TCP (TLSSocket): a 'session' Buffer from an
242
+ // earlier connection — a DTLS 1.3 PSK ticket, or a DTLS 1.2 RFC 5077
243
+ // ticket — and whether this side issues / asks for tickets (default
244
+ // true; noTickets: true is the older spelling of sessionTickets: false).
245
+ session: options.session || null,
246
+ sessionTickets: options.sessionTickets !== undefined ? options.sessionTickets !== false
247
+ : options.noTickets !== true,
248
+ // No 0-RTT over DTLS here: early data has no DTLS record path.
249
+ earlyData: false,
250
+ connectionId: connectionIdOption(options.connectionId, isServer),
251
+ }));
209
252
 
210
253
  // ---- DTLS context ----
211
254
  let ctx = {
212
255
  state: 'idle', // idle → handshaking → connected → closed
256
+ // Connection IDs (RFC 9146, RFC 9147 §9): { local, peer } once
257
+ // negotiated ('connectionId'), the peer's as NewConnectionId moves it;
258
+ // and the newest record number authenticated (noteNewest), the one
259
+ // a peer's new address may be taken from.
260
+ cid: null,
261
+ rxNewest: null,
262
+ consecutiveWarnings: 0, // warning alerts with nothing else between them
263
+ // DTLS 1.3 ACK state (RFC 9147 §7.1), for the peer's flight in progress:
264
+ // the handshake records of it received so far, a timer for the ACK when
265
+ // the rest does not follow, and whether this datagram showed a gap.
266
+ lastFlight12: null, // DTLS 1.2: our last flight, kept for the peer's retransmissions
267
+ dgAnyNew: false, // this datagram: a handshake message not taken before
268
+ dgDuplicate: false, // this datagram: a handshake message already taken
269
+ dgNewest: false, // this datagram: a protected record newer than any before
270
+ pendingAcks: [],
271
+ ackTimer: null,
272
+ postAcks: [], // post-handshake records received, to ACK together
273
+ postAckTimer: null,
274
+ ackDisruption: false,
275
+ flightMessagesQueued: 0, // flight messages queued, ever (did this datagram answer with one?)
213
276
  isServer: isServer,
214
- mtu: options.mtu || 1200,
277
+ mtu: checkedMtu(options.mtu, 1200),
215
278
 
216
279
  // Upper bound on a single reassembled handshake message. The DTLS handshake
217
280
  // header carries a 24-bit length (up to 16MB), which is attacker-controlled
218
281
  // on the very first fragment. Without a cap, a peer could send one fragment
219
282
  // claiming length 0xFFFFFF for several msg_seqs and force large allocations
220
- // before anything is authenticated. 256KB comfortably fits a real
221
- // certificate-chain flight while bounding the damage. Configurable via
222
- // options.maxHandshakeMessageSize.
223
- maxHandshakeMessageSize: options.maxHandshakeMessageSize || (256 * 1024),
283
+ // before anything is authenticated. TLS over TCP's limit
284
+ // (tls.handshakeMessageLimit(): 128 KB, or maxHandshakeSize) — it was a
285
+ // limit of DTLS's own, 256 KB. maxHandshakeMessageSize, its older
286
+ // option, is still taken (never past the same 128 KB).
287
+ maxHandshakeMessageSize: options.maxHandshakeMessageSize > 0 ? Math.min(options.maxHandshakeMessageSize, tls.handshakeMessageLimit()) : tls.handshakeMessageLimit(),
224
288
 
225
289
  // Version (determined after negotiation)
226
290
  selectedVersion: null, // DTLS version (0xFEFC or 0xFEFD)
@@ -290,16 +354,41 @@ function DTLSSession(options) {
290
354
  // new write keys are installed immediately but stay dormant here until
291
355
  // the ACK arrives.
292
356
  pendingWriteEpoch: null,
293
- keyUpdateFlight: null, // { payload, epoch, timer, attempts, timeout }
357
+ postHandshake: [], // DTLS 1.3 post-handshake messages awaiting their ACK
358
+ deferredKeyUpdate: null, // DTLS 1.3: our KeyUpdate held back while one is unACKed (§8)
359
+ feeding: 0, // inside feedDatagram: post-handshake messages wait for its end
360
+ seenPlainSeqs: [], // the last epoch-0 record numbers received (duplicates dropped)
361
+ out: [], // what is to be sent, packed at the end of the pass (flushOut)
362
+ flightQueued: 0, // messages of the current flight queued (haveFlight)
363
+ outScheduled: false,
364
+ heldPostHandshake: [], // ...these (sendPostHandshake)
365
+ lastAckResend: 0, // when an ACK that confirmed nothing new last caused a resend
366
+ finalFlightPending: false, // DTLS 1.3 client: final flight not yet confirmed (see secureConnect)
294
367
 
295
368
  retransmitTimer: null,
296
369
  retransmitCount: 0,
297
- retransmitTimeout: 1000,
370
+ retransmitTimeout: checkedRetransmitTimeout(options.initialRetransmitTimeout, 1000),
371
+ initialRetransmitTimeout: checkedRetransmitTimeout(options.initialRetransmitTimeout, 1000), // where each flight's timer starts (RFC 9147 §5.8.2: 1 s)
372
+ // Given, the schedule is the caller's: no round-trip estimate moves it.
373
+ fixedRto: options.initialRetransmitTimeout !== undefined && options.initialRetransmitTimeout !== null,
374
+ lastSession: undefined, // the last 'session' Buffer (getSession)
375
+ // Giving up (RFC 9147 §5.8.2 leaves it to us): after at least this many
376
+ // retransmissions AND this much time since the flight first went out.
377
+ // A count alone was 6 tries: about a minute from a 1 s timer, but 6 s from
378
+ // a 100 ms one (as WebRTC stacks start), too soon on a lossy path.
298
379
  maxRetransmits: 6,
380
+ giveUpAfter: 60000,
381
+ // Round-trip estimate (RFC 6298's, as RFC 9147 §5.8.2 invites: "the
382
+ // retransmit timer [...] SHOULD be adjusted from RTT samples"): from a
383
+ // flight sent once to the first new message of the answer (Karn: never
384
+ // from one retransmitted). Later flights' timers start from it.
385
+ srtt: null, rttvar: null,
386
+ flightBegan: null, // when the current flight first went out (giving up)
387
+ rttStart: null, // ...the same, until its answer is sampled (RTT)
388
+ flightResent: false, // whether it has been resent
299
389
 
300
390
  // DTLS 1.2: CCS tracking
301
391
  localCcsSent: false,
302
- remoteCcsSeen: false,
303
392
 
304
393
  // HelloVerifyRequest (DTLS 1.2 server)
305
394
  hvrCookie: null, // cookie sent in HVR
@@ -312,58 +401,18 @@ function DTLSSession(options) {
312
401
  // ============================================================
313
402
 
314
403
  // Set DTLS versions and cookie (triggers DTLS format in build_hello)
315
- let versions = [];
316
- let minVer = options.minVersion || 'DTLSv1.2';
317
- let maxVer = options.maxVersion || 'DTLSv1.3';
318
- if (maxVer === 'DTLSv1.3' || maxVer === 'TLSv1.3') versions.push(DTLS_VERSION.DTLS1_3);
319
- if (minVer === 'DTLSv1.2' || minVer === 'TLSv1.2' || maxVer === 'DTLSv1.2') versions.push(DTLS_VERSION.DTLS1_2);
320
- if (versions.length === 0) versions = [DTLS_VERSION.DTLS1_3, DTLS_VERSION.DTLS1_2];
321
-
322
- let tlsSetup = {
323
- local_supported_versions: versions,
404
+ // node_options.js: the same reading as TLS over TCP. Inline compares took
405
+ // an unknown name silently.
406
+ let versions = versionsInRange(options.minVersion, options.maxVersion, true);
407
+
408
+ // The lists from the options (node_options.js localLists, as TLS over TCP
409
+ // builds them: the same names, cipherSuites and alpnProtocols as aliases);
410
+ // TLSSession fills what they leave out, by role (fill_default_lists). A
411
+ // DTLS server took no signatureAlgorithms nor prioritizeChaCha before, and
412
+ // a DTLS client offered every post-quantum group a server takes.
413
+ let tlsSetup = Object.assign(localLists(options, versions), {
324
414
  dtls_cookie: new Uint8Array(0), // empty cookie — triggers DTLS format
325
- };
326
-
327
- // Pass through cipher/group/alpn preferences
328
- if (options.cipherSuites) tlsSetup.local_supported_cipher_suites = options.cipherSuites;
329
-
330
- // Client cipher defaults: TLSSession auto-populates both TLS 1.3+1.2 ciphers,
331
- // but if we only support DTLS 1.2, we must not offer TLS 1.3 ciphers
332
- // (the server might erroneously select one for a 1.2 connection).
333
- if (!isServer && !options.cipherSuites && versions.indexOf(DTLS_VERSION.DTLS1_3) < 0) {
334
- tlsSetup.local_supported_cipher_suites = [0xC02F, 0xC030, 0xC02B, 0xC02C, 0xCCA8];
335
- }
336
- if (options.groups) tlsSetup.local_supported_groups = options.groups;
337
- if (options.alpnProtocols) tlsSetup.local_supported_alpns = options.alpnProtocols;
338
-
339
- // Default key-exchange groups, BOTH roles. With DTLS 1.3 offered: the
340
- // hybrid post-quantum group first (X25519MLKEM768, when node:crypto has
341
- // ML-KEM — see ecdh.js), then the classical ones. In WebRTC either peer can
342
- // end up the DTLS client, and harvest-now-decrypt-later applies to media
343
- // too (Chrome rolls PQ DTLS out from 149). A client leading with the hybrid
344
- // group sends a classical share beside it (tls_session), so a classical
345
- // server costs nothing extra. DTLS 1.2 only: classical — a KEM does not fit
346
- // its key exchange, and the server skips KEM groups there anyway.
347
- if (!tlsSetup.local_supported_groups) {
348
- // (ECDH_SUPPORTED_GROUPS leads with the hybrid too now; tls_session leaves
349
- // a KEM group out of a hello that does not offer 1.3.)
350
- tlsSetup.local_supported_groups = (versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0
351
- ? SERVER_DEFAULT_GROUPS : ECDH_SUPPORTED_GROUPS).slice();
352
- }
353
-
354
- // Server defaults — TLSSession client auto-populates these, but server expects them set externally
355
- if (isServer) {
356
- if (!tlsSetup.local_supported_cipher_suites) {
357
- // crypto.js owns the default suite list (see default_cipher_suites).
358
- tlsSetup.local_supported_cipher_suites = default_cipher_suites(
359
- versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
360
- versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
361
- );
362
- }
363
- // (key-exchange groups: set above, for both roles)
364
- // signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
365
- tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
366
- }
415
+ });
367
416
 
368
417
  // Server cert/key.
369
418
  //
@@ -372,13 +421,11 @@ function DTLSSession(options) {
372
421
  // pfx and ocsp, and picking out two fields silently dropped all of them —
373
422
  // an encrypted private key could never be decrypted here no matter what the
374
423
  // caller passed. Same defect the TLS paths carried.
375
- if (options.cert && options.key) {
376
- let sctx = createSecureContext(options);
377
- tlsSetup.local_cert_chain = sctx.certificateChain;
378
- tlsSetup.cert_private_key = sctx.privateKey;
379
- }
380
-
424
+ // Through TLSSession's setKeyCert, as an SNICallback's context goes in:
425
+ // the key/certificate pairs — several (key: [...], cert: [...]), the one
426
+ // the client can verify chosen per handshake — not only the first.
381
427
  tls.set_context(tlsSetup);
428
+ if (options.cert && options.key) tls.setKeyCert(createSecureContext(options));
382
429
 
383
430
 
384
431
  // ============================================================
@@ -594,24 +641,22 @@ function DTLSSession(options) {
594
641
  if (ctx.writeEpoch === null || ctx.readEpoch === null) return;
595
642
 
596
643
  if (info.direction === 'send') {
597
- // §8: only one KeyUpdate may be in flight. requestKeyUpdate() below
598
- // refuses up front; reaching here with one pending means the TLS session
599
- // rotated its secret anyway (e.g. an auto-response to the peer's
600
- // update_requested while ours was still unacknowledged), which would
601
- // strand an epoch. Say so rather than silently losing the connection.
644
+ // §8: only one KeyUpdate may be in flight. requestKeyUpdate() refuses
645
+ // up front; the TLS session's answer to the peer's update_requested
646
+ // is held back (deferredKeyUpdate). Anything else reaching here with
647
+ // one pending would strand an epoch: said, not lost in silence.
602
648
  if (ctx.pendingWriteEpoch !== null) {
649
+ // The secret of a KeyUpdate held back (deferredKeyUpdate): it takes
650
+ // effect when that one goes.
651
+ if (ctx.deferredKeyUpdate && ctx.deferredKeyUpdate.secret === null) {
652
+ ctx.deferredKeyUpdate.secret = info.secret;
653
+ return;
654
+ }
603
655
  ev.emit('error', new Error(
604
656
  'DTLS: a KeyUpdate is already awaiting acknowledgement (RFC 9147 §8)'));
605
657
  return;
606
658
  }
607
- let next = ctx.writeEpoch + 1;
608
- // Install the new write keys, but keep protecting with the old epoch
609
- // until the peer ACKs (§8). ctx.keys[next] may already carry a .read
610
- // half from the peer's own update — merge, never overwrite.
611
- ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(info.secret) });
612
- ctx.writeSeq[next] = 0;
613
- ctx.pendingWriteEpoch = next;
614
-
659
+ installWriteUpdate(info.secret);
615
660
  } else if (info.direction === 'receive') {
616
661
  let next = ctx.readEpoch + 1;
617
662
  ctx.keys[next] = Object.assign({}, ctx.keys[next], { read: deriveEpochKeys(info.secret) });
@@ -622,40 +667,259 @@ function DTLSSession(options) {
622
667
  }
623
668
  });
624
669
 
625
- /** Arm the post-handshake retransmit for an unacknowledged KeyUpdate. */
626
- function armKeyUpdateRetransmit(payload, epoch) {
627
- cancelKeyUpdateRetransmit();
628
- ctx.keyUpdateFlight = {
629
- payload: payload, epoch: epoch,
630
- attempts: 0, timeout: ctx.retransmitTimeout, timer: null,
670
+ // ── Datagrams ──
671
+ // Everything this side sends goes through one queue (ctx.out), and leaves
672
+ // it packed (flushOut): handshake messages of one epoch share a record —
673
+ // a message is cut where the space runs out, and the next goes on in the
674
+ // same record (RFC 6347 §4.2.3, RFC 9147 §5.5: several handshake messages
675
+ // or fragments to a record); records share a datagram, up to the MTU, each
676
+ // with its length (the DTLS 1.3 unified header's L bit is always set here:
677
+ // RFC 9147 §4.3, RFC 6347 §4.1.1). One record per message and one datagram
678
+ // per record left room unused (BoGo MTU-DTLS13: "previous packet had 212
679
+ // bytes available"); records alone packed into a datagram left room in the
680
+ // record (BoGo: "previous handshake record had 1107 bytes available, but
681
+ // shim did not fit another fragment in it"). Retransmissions are packed the
682
+ // same way: what is still unACKed, cut again to fill.
683
+ // Items: { kind: 'msg', tlsMsg, msgSeq, epoch, list } — a whole handshake
684
+ // message, fragmented here; each fragment becomes an entry of `list` (the
685
+ // flight, a post-handshake message), what ACKs and retransmission act on;
686
+ // { kind: 'entry', entry } — one of those, (re)sent; { kind: 'raw', epoch,
687
+ // contentType, payload } — a record of its own (ACK, application data,
688
+ // HelloVerifyRequest).
689
+ // A pass ends at the next microtask; at once before an alert (its own
690
+ // datagram: wolfSSL took a Finished and a close_notify packed together for
691
+ // a failed handshake) and when the session ends, so its last word is out
692
+ // before 'close'.
693
+ function queueOut(item) {
694
+ ctx.out.push(item);
695
+ if (!ctx.outScheduled) { ctx.outScheduled = true; queueMicrotask(function () { ctx.outScheduled = false; flushOut(); }); }
696
+ }
697
+ function queueMessage(tlsMsg, msgSeq, epoch, list, label) {
698
+ queueOut({ kind: 'msg', tlsMsg: tlsMsg, msgSeq: msgSeq, epoch: epoch, list: list, label: label });
699
+ }
700
+
701
+ // A handshake fragment entry cut in two: its first `n` body bytes stay, the
702
+ // rest becomes a new entry right after it in the same list.
703
+ function splitEntry(e, n) {
704
+ let p = e.payload, type = p[0], total = (p[1] << 16) | (p[2] << 8) | p[3];
705
+ let msgSeq = (p[4] << 8) | p[5], off = (p[6] << 16) | (p[7] << 8) | p[8];
706
+ let body = p.subarray(12);
707
+ let frag = function (o, b) { return build_dtls_fragment(type, total, msgSeq, o, b); };
708
+ let rest = { epoch: e.epoch, contentType: e.contentType, payload: frag(off + n, body.subarray(n)), list: e.list };
709
+ e.payload = frag(off, body.subarray(0, n));
710
+ if (e.list) { let i = e.list.indexOf(e); if (i >= 0) e.list.splice(i + 1, 0, rest); }
711
+ return rest;
712
+ }
713
+
714
+ // The most a record of ours carries: 2^14, or the peer's
715
+ // record_size_limit (RFC 8449) once negotiated — as TLSSocket's.
716
+ function flushOut() {
717
+ if (ctx.out.length === 0) return;
718
+ let queue = ctx.out;
719
+ ctx.out = [];
720
+ let parts = [], used = 0, rec = null, failed = false;
721
+ // Room left: in the datagram (the MTU), and in the record (2^14, or the
722
+ // peer's record_size_limit — RFC 8449).
723
+ let cap = tls.recordSendLimit();
724
+ let left = function () {
725
+ return Math.min(ctx.mtu - used - (rec ? recordOverhead(rec.epoch) + rec.len : 0), cap - (rec ? rec.len : 0));
726
+ };
727
+ let open = function (epoch, contentType, label) {
728
+ rec = { epoch: epoch, contentType: contentType, parts: [], entries: [], len: 0, label: label };
729
+ };
730
+ let closeRec = function () {
731
+ if (!rec) return;
732
+ let r = rec; rec = null;
733
+ let seq = (r.epoch in ctx.writeSeq) ? ctx.writeSeq[r.epoch] : 0;
734
+ let payload = r.parts.length === 1 ? r.parts[0] : concatUint8Arrays(r.parts);
735
+ let record = buildRecord(r.epoch, r.contentType, payload);
736
+ if (!record) {
737
+ // buildRecord refused (no keys for an encrypted epoch): said, not
738
+ // dropped in silence — the handshake cannot go on either way.
739
+ failed = true;
740
+ fail(new Error('DTLS: could not build outgoing record for epoch ' + r.epoch + ' — no keys installed for that epoch'), TLS_ALERT.INTERNAL_ERROR);
741
+ return;
742
+ }
743
+ let rn = recordNumber(r.epoch, seq);
744
+ for (let i = 0; i < r.entries.length; i++) (r.entries[i].sent || (r.entries[i].sent = [])).push(rn);
745
+ _ldbgTx(record, r.label || 'out');
746
+ parts.push(record); used += record.length;
747
+ };
748
+ let endDatagram = function () {
749
+ closeRec();
750
+ if (parts.length === 0) return;
751
+ let dg = parts.length === 1 ? parts[0] : concatUint8Arrays(parts);
752
+ parts = []; used = 0;
753
+ ev.emit('packet', dg);
631
754
  };
632
- scheduleKeyUpdateRetransmit();
755
+ // A record of `epoch` with at least `need` payload bytes: in this
756
+ // datagram if it fits, else the next.
757
+ let room = function (epoch, need) {
758
+ if (used > 0 && ctx.mtu - used < recordOverhead(epoch) + need) endDatagram();
759
+ };
760
+ // Into the open handshake record of `epoch`, or a new one.
761
+ let intoHandshake = function (epoch, need, label) {
762
+ if (rec && (rec.epoch !== epoch || rec.contentType !== CT.HANDSHAKE || left() < need)) closeRec();
763
+ if (!rec) { room(epoch, need); open(epoch, CT.HANDSHAKE, label); }
764
+ };
765
+
766
+ for (let qi = 0; qi < queue.length && !failed; qi++) {
767
+ let item = queue[qi];
768
+ if (item.kind === 'msg') {
769
+ let body = item.tlsMsg.subarray(4), total = body.length, off = 0;
770
+ do {
771
+ intoHandshake(item.epoch, 12 + Math.min(total - off, 1), item.label);
772
+ let take = Math.max(0, Math.min(total - off, left() - 12));
773
+ let frag = build_dtls_handshake(item.tlsMsg, item.msgSeq, off, take);
774
+ let entry = { epoch: item.epoch, contentType: CT.HANDSHAKE, payload: frag, list: item.list };
775
+ item.list.push(entry);
776
+ rec.parts.push(frag); rec.entries.push(entry); rec.len += frag.length;
777
+ off += take;
778
+ } while (off < total && !failed);
779
+ } else if (item.kind === 'entry' && item.entry.contentType === CT.HANDSHAKE) {
780
+ let e = item.entry;
781
+ if (e.acked) continue;
782
+ intoHandshake(e.epoch, Math.min(e.payload.length, 13), item.label);
783
+ // Too long for what is left: cut to fill (a fragment's body only).
784
+ if (e.payload.length > left() && e.payload.length > 13 && left() >= 13) {
785
+ let rest = splitEntry(e, left() - 12);
786
+ queue.splice(qi + 1, 0, { kind: 'entry', entry: rest, label: item.label });
787
+ }
788
+ rec.parts.push(e.payload); rec.entries.push(e); rec.len += e.payload.length;
789
+ } else {
790
+ // A record of its own: a ChangeCipherSpec (an entry of the flight),
791
+ // an ACK, application data, a HelloVerifyRequest.
792
+ let e = item.kind === 'entry' ? item.entry : null;
793
+ if (e && e.acked) continue;
794
+ // A new entry (sendCCS): it joins its list here, in sending order.
795
+ if (e && item.join && e.list) e.list.push(e);
796
+ let epoch = e ? e.epoch : item.epoch, contentType = e ? e.contentType : item.contentType;
797
+ let payload = e ? e.payload : item.payload;
798
+ closeRec();
799
+ room(epoch, payload.length);
800
+ open(epoch, contentType, item.label);
801
+ rec.parts.push(payload); rec.len += payload.length;
802
+ if (e) rec.entries.push(e);
803
+ closeRec();
804
+ }
805
+ }
806
+ if (!failed) endDatagram();
807
+ }
808
+
809
+ // Our next write epoch's keys, from a KeyUpdate we sent: installed now, used
810
+ // from its ACK on (§8). ctx.keys[next] may already carry a .read half from
811
+ // the peer's own update — merge, never overwrite.
812
+ function installWriteUpdate(secret) {
813
+ let next = ctx.writeEpoch + 1;
814
+ ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(secret) });
815
+ ctx.writeSeq[next] = 0;
816
+ ctx.pendingWriteEpoch = next;
817
+ }
818
+
819
+ // ── Post-handshake messages (RFC 9147 §5.8.4, §7.1) ──
820
+ // NewSessionTicket and KeyUpdate have no answering flight: each is
821
+ // resent on its own timer until the peer ACKs it — independently of the
822
+ // handshake flight and of each other. Only KeyUpdate was; a lost
823
+ // NewSessionTicket was simply lost (BoGo DTLS-Retransmit-*NewSessionTicket*).
824
+ // KeyUpdate's ACK also switches our write epoch (§8) — on the ACK that
825
+ // covers it, not on any ACK, as before.
826
+ // One made while a datagram is being taken in (feedDatagram) waits for its
827
+ // end — for the ACK that datagram may call for (§7.1). The server's
828
+ // NewSessionTickets come of the client's Finished: they went out ahead of
829
+ // its ACK, and a peer that waits for the ACK first (BoringSSL's runner)
830
+ // read the first ticket as renegotiation and the second as out of order
831
+ // (BoGo N1). A KeyUpdate answering the peer's follows its ACK the same way.
832
+ function sendPostHandshake(kind, tlsMsg, msgSeq, epoch) {
833
+ let item = { kind: kind, msg: { tlsMsg: tlsMsg, msgSeq: msgSeq, epoch: epoch }, entries: [],
834
+ attempts: 0, timeout: currentRto(), timer: null, began: Date.now() };
835
+ ctx.postHandshake.push(item);
836
+ if (ctx.feeding > 0) { ctx.heldPostHandshake.push(item); return; }
837
+ transmitPostHandshake(item);
838
+ }
839
+ function transmitPostHandshake(item) {
840
+ queueMessage(item.msg.tlsMsg, item.msg.msgSeq, item.msg.epoch, item.entries, item.kind);
841
+ schedulePostHandshake(item);
842
+ }
843
+ function flushHeldPostHandshake() {
844
+ let held = ctx.heldPostHandshake;
845
+ ctx.heldPostHandshake = [];
846
+ for (let i = 0; i < held.length; i++) {
847
+ if (ctx.state === 'closed' || ctx.postHandshake.indexOf(held[i]) < 0) continue;
848
+ transmitPostHandshake(held[i]);
849
+ }
633
850
  }
634
851
 
635
- function scheduleKeyUpdateRetransmit() {
636
- let f = ctx.keyUpdateFlight;
637
- if (!f) return;
638
- f.timer = setTimeout(function () {
639
- if (!ctx.keyUpdateFlight) return;
640
- if (f.attempts >= ctx.maxRetransmits) {
641
- ev.emit('error', new Error('DTLS: KeyUpdate was never acknowledged'));
642
- cancelKeyUpdateRetransmit();
852
+ function schedulePostHandshake(item) {
853
+ item.timer = setTimeout(function () {
854
+ item.timer = null;
855
+ if (ctx.postHandshake.indexOf(item) < 0 || ctx.state === 'closed') return;
856
+ if (exhausted(item.attempts, item.began)) {
857
+ removePostHandshake(item);
858
+ // An unacknowledged KeyUpdate leaves the next epoch unusable; a
859
+ // ticket is an offer, and the connection is fine without it.
860
+ if (item.kind === 'key_update') ev.emit('error', new Error('DTLS: KeyUpdate was never acknowledged'));
643
861
  return;
644
862
  }
645
- f.attempts++;
646
- // Rebuild rather than replay: a retransmission is a NEW record and needs
647
- // a fresh sequence number, or the peer's replay window rejects it.
648
- let record = buildRecord(f.epoch, CT.HANDSHAKE, f.payload);
649
- if (record) { _ldbgTx(record, 'keyupdate-rtx'); ev.emit('packet', record); }
650
- f.timeout = Math.min(f.timeout * 2, 60000);
651
- scheduleKeyUpdateRetransmit();
652
- }, f.timeout);
653
- if (f.timer && typeof f.timer.unref === 'function') f.timer.unref();
863
+ item.attempts++;
864
+ // Rebuilt, not replayed: each retransmission is a new record with a
865
+ // fresh sequence number, or the peer's replay window drops it.
866
+ transmitUnacked(item.entries, item.kind + '-rtx', false);
867
+ item.timeout = Math.min(item.timeout * 2, 60000);
868
+ schedulePostHandshake(item);
869
+ }, item.timeout);
870
+ if (item.timer && typeof item.timer.unref === 'function') item.timer.unref();
871
+ }
872
+
873
+ function removePostHandshake(item) {
874
+ if (item.timer) clearTimeout(item.timer);
875
+ let i = ctx.postHandshake.indexOf(item);
876
+ if (i >= 0) ctx.postHandshake.splice(i, 1);
877
+ }
878
+
879
+ // The session is over: every timer it runs stops with it.
880
+ function shutDown() {
881
+ flushOut();
882
+ ctx.state = 'closed';
883
+ cancelRetransmit();
884
+ cancelPostHandshake();
885
+ clearPendingAcks();
654
886
  }
655
887
 
656
- function cancelKeyUpdateRetransmit() {
657
- if (ctx.keyUpdateFlight && ctx.keyUpdateFlight.timer) clearTimeout(ctx.keyUpdateFlight.timer);
658
- ctx.keyUpdateFlight = null;
888
+ function cancelPostHandshake() {
889
+ for (let i = 0; i < ctx.postHandshake.length; i++) {
890
+ if (ctx.postHandshake[i].timer) clearTimeout(ctx.postHandshake[i].timer);
891
+ }
892
+ ctx.postHandshake = [];
893
+ ctx.heldPostHandshake = [];
894
+ ctx.deferredKeyUpdate = null;
895
+ }
896
+
897
+ // The post-handshake messages an ACK covers (by record number): done.
898
+ // A second ACK of them finds nothing left, and changes nothing
899
+ // (BoGo ACKPostHandshakeTwice).
900
+ function ackPostHandshake(got) {
901
+ for (let i = ctx.postHandshake.length - 1; i >= 0; i--) {
902
+ let item = ctx.postHandshake[i];
903
+ if (item.entries.length === 0) continue; // not out yet
904
+ markAcked(item.entries, got);
905
+ if (item.entries.some(function (e) { return !e.acked; })) continue;
906
+ removePostHandshake(item);
907
+ if (item.kind === 'key_update' && ctx.pendingWriteEpoch !== null) {
908
+ // §8: the new epoch becomes live on the ACK of the KeyUpdate.
909
+ ctx.writeEpoch = ctx.pendingWriteEpoch;
910
+ ctx.pendingWriteEpoch = null;
911
+ ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
912
+ if (ctx.state === 'closed') return; // a listener closed it
913
+ // The KeyUpdate held back for this one (§8): now, under the epoch
914
+ // just made live — the one it replaces.
915
+ let held = ctx.deferredKeyUpdate;
916
+ if (held && held.secret !== null) {
917
+ ctx.deferredKeyUpdate = null;
918
+ sendPostHandshake('key_update', held.tlsMsg, held.msgSeq, ctx.writeEpoch);
919
+ installWriteUpdate(held.secret);
920
+ }
921
+ }
922
+ }
659
923
  }
660
924
 
661
925
 
@@ -688,28 +952,25 @@ function DTLSSession(options) {
688
952
  // epoch, which is 3 until a KeyUpdate moves it to 4, 5, ... Hardcoding 3
689
953
  // meant every post-handshake message stayed on the pre-rekey epoch.
690
954
  dtlsEpoch = (tlsEpoch === 1) ? 2
691
- : (ctx.writeEpoch !== null ? ctx.writeEpoch : 3);
955
+ : appEpoch();
692
956
  } else {
693
957
  dtlsEpoch = 1; // DTLS 1.2: epoch 1 for all encrypted
694
958
  }
695
959
 
696
960
  if (type === 'alert') {
697
- sendAlertRecord(dtlsEpoch, data);
961
+ // TLSSession names the epoch the PEER reads at (sendAlert); this layer
962
+ // holds the keys. Its choice when we hold write keys for it, else the
963
+ // highest epoch we do (alertEpoch) — never a record for an epoch we
964
+ // have no keys for, which a DTLS 1.3 alert before any keys could
965
+ // become: a unified header the peer could not even parse ("bad type
966
+ // byte"; BoGo FragmentMessage*Mismatch).
967
+ let ae = (dtlsEpoch === 0 || (ctx.keys[dtlsEpoch] && ctx.keys[dtlsEpoch].write)) ? dtlsEpoch : alertEpoch();
968
+ sendAlertRecord(ae, data);
698
969
  return;
699
970
  }
700
971
 
701
- // DTLS 1.2: derive keys before first encrypted message
702
- if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1 && !ctx.keys[1]) {
703
- let ts = tls.getTrafficSecrets();
704
- if (ts.masterSecret) {
705
- let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
706
- ctx.keys[1] = {
707
- write: { key: d12.writeKey, iv: d12.writeIv },
708
- read: { key: d12.readKey, iv: d12.readIv },
709
- };
710
- ctx.writeSeq[1] = 0;
711
- }
712
- }
972
+ // DTLS 1.2: keys before the first encrypted message
973
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1) installKeys12();
713
974
 
714
975
  // Flight boundary: first outgoing record after accepting peer data
715
976
  // starts a fresh flight (clears the previous flight's records and
@@ -724,49 +985,32 @@ function DTLSSession(options) {
724
985
  sendCCS();
725
986
  }
726
987
 
727
- // Convert TLS handshake message → DTLS handshake message (add reconstruction header)
728
- let dtlsMsg = build_dtls_handshake(data, seq);
729
-
730
- // Fragment if needed
731
- let frags = fragmentMessage(dtlsMsg, data, seq);
732
-
733
- // Build DTLS records and emit
988
+ // Out as a DTLS handshake message — fragmented as it is packed (flushOut).
734
989
  // RFC 9147 §5.8.4: post-handshake messages do NOT belong to the handshake
735
990
  // flight. Each category gets an independent state machine; folding a
736
991
  // KeyUpdate into currentFlight would make a handshake retransmission
737
992
  // replay it, and a KeyUpdate ACK cancel the handshake's timer.
738
- let isPostHandshakeKU = (type === 'key_update' &&
739
- ctx.selectedVersion === DTLS_VERSION.DTLS1_3);
740
- if (isPostHandshakeKU) {
741
- for (let i = 0; i < frags.length; i++) {
742
- let kuRecord = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
743
- if (kuRecord) { _ldbgTx(kuRecord, 'keyupdate'); ev.emit('packet', kuRecord); }
993
+ // DTLS 1.3, after the handshake: KeyUpdate, NewSessionTicket — each on
994
+ // its own retransmission until ACKed (sendPostHandshake).
995
+ // (A TLS 1.3 NewSessionTicket is post-handshake whenever it goes — the
996
+ // server sends its first one just before it reports the handshake done.)
997
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3 &&
998
+ (type === 'new_session_ticket' || (type === 'key_update' && ctx.state === 'connected'))) {
999
+ // §8: one KeyUpdate at a time — "until the KeyUpdate message has been
1000
+ // acknowledged" no other. Ours answering the peer's update_requested
1001
+ // while our own is unACKed waits for that ACK (ackPostHandshake), then
1002
+ // goes under the epoch it replaces. Sent at once, the next one's keys
1003
+ // found an epoch still pending and the connection failed (BoringSSL,
1004
+ // which delays its ACKs: every time).
1005
+ if (type === 'key_update' && ctx.pendingWriteEpoch !== null) {
1006
+ ctx.deferredKeyUpdate = { tlsMsg: data, msgSeq: seq, secret: null };
1007
+ return;
744
1008
  }
745
- // KeyUpdate must be acknowledged (§8), so it is retransmitted on its own
746
- // timer until an ACK arrives. Only the last fragment is tracked; a
747
- // KeyUpdate body is 1 byte and never fragments in practice.
748
- armKeyUpdateRetransmit(frags[frags.length - 1], dtlsEpoch);
1009
+ sendPostHandshake(type, data, seq, dtlsEpoch);
749
1010
  return;
750
1011
  }
751
1012
 
752
- for (let i = 0; i < frags.length; i++) {
753
- let record = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
754
- // The flight captures the fragment, not the record — see currentFlight.
755
- ctx.currentFlight.push({ epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] });
756
- if (record) {
757
- _ldbgTx(record, 'L543'); ev.emit('packet', record);
758
- } else {
759
- // buildRecord refused to build (no keys for an encrypted epoch).
760
- // Dropping the record here without a word is how a key-epoch mismatch
761
- // turns into an unexplained peer timeout instead of a diagnosable
762
- // failure: nothing reaches the wire and nothing is reported. Surface
763
- // it — the handshake cannot proceed either way.
764
- ev.emit('error', new Error(
765
- 'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
766
- ' — no keys installed for that epoch'));
767
- return;
768
- }
769
- }
1013
+ queueFlightMessage(data, seq, dtlsEpoch);
770
1014
 
771
1015
  // Start retransmit timer after sending flight messages.
772
1016
  //
@@ -815,67 +1059,151 @@ function DTLSSession(options) {
815
1059
 
816
1060
  if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
817
1061
  // DTLS 1.3 encrypted (unified header)
818
- return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write);
1062
+ return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write, peerCid());
819
1063
  } else {
820
1064
  // DTLS 1.2 encrypted (classic header)
821
- return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write);
1065
+ return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write, peerCid());
822
1066
  }
823
1067
  }
824
1068
 
1069
+ // The CID the peer asked for, in every protected record we send (null:
1070
+ // none). Ours, that the peer's carry: from the moment we ask for it — a
1071
+ // record with it can come in the datagram that agrees to it (a resumed
1072
+ // DTLS 1.2 server's Finished, behind its ServerHello) — and required once
1073
+ // agreed to.
1074
+ function peerCid() { return ctx.cid && ctx.cid.peer.length > 0 ? ctx.cid.peer : null; }
1075
+ function localCid() { let c = tls.context.local_cid; return c && c.length > 0 ? c : null; }
1076
+ function localCidRequired() { return !!(ctx.cid && ctx.cid.local.length > 0); }
1077
+
825
1078
  function nextWriteSeq(epoch) {
826
1079
  if (!(epoch in ctx.writeSeq)) ctx.writeSeq[epoch] = 0;
827
1080
  return ctx.writeSeq[epoch]++;
828
1081
  }
829
1082
 
830
1083
  /**
831
- * Fragment a DTLS handshake message if it exceeds MTU.
832
- * Returns array of DTLS handshake message fragments (each with reconstruction header).
1084
+ * The one way a handshake message of a flight goes out: queued, to be cut
1085
+ * and packed (flushOut) into entries of the current flight — kept for
1086
+ * retransmission — and counted as our answer (the implicit ACK, RFC 9147
1087
+ * §7.1). The cookie-bearing ClientHello goes the same way.
833
1088
  */
834
- function fragmentMessage(dtlsMsg, tlsMsg, msgSeq) {
835
- // Record-layer overhead we must leave room for inside one datagram/MTU:
836
- // DTLS 1.2: 13-byte classic record header + 16-byte AEAD tag (+8 explicit
837
- // nonce for GCM, but that lives inside the encrypted payload the
838
- // record layer builds, so we keep a small safety margin).
839
- // DTLS 1.3: 5-byte unified header (2-byte seq + length) + 16-byte AEAD tag.
840
- // Using the correct, smaller 1.3 overhead avoids over-fragmenting.
841
- let isDtls13 = ctx.selectedVersion === DTLS_VERSION.DTLS1_3;
842
- let recordOverhead = isDtls13 ? (5 + 16) : (13 + 16 + 8);
843
- let maxFragment = ctx.mtu - recordOverhead;
844
-
845
- // The DTLS handshake message header is 12 bytes; the body is what we chunk.
846
- const HS_HDR = 12;
847
- if (dtlsMsg.length <= maxFragment) return [dtlsMsg];
848
-
849
- // Need to fragment: split the TLS body into chunks
850
- let body = tlsMsg.subarray(4); // skip TLS header (type+length)
851
- let totalLength = body.length;
852
- let fragments = [];
853
- let offset = 0;
854
-
855
- // Each fragment carries its own 12-byte handshake header, so the payload
856
- // budget per fragment is maxFragment - HS_HDR.
857
- let maxChunk = Math.max(1, maxFragment - HS_HDR);
858
- while (offset < totalLength) {
859
- let chunkLen = Math.min(maxChunk, totalLength - offset);
860
- let frag = build_dtls_handshake(tlsMsg, msgSeq, offset, chunkLen);
861
- fragments.push(frag);
862
- offset += chunkLen;
863
- }
864
-
865
- return fragments;
1089
+ function queueFlightMessage(tlsMsg, msgSeq, dtlsEpoch) {
1090
+ ctx.flightMessagesQueued++;
1091
+ ctx.flightQueued++;
1092
+ if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
1093
+ if (ctx.rttStart === null && ctx.currentFlight.length === 0 && ctx.flightQueued === 1) ctx.rttStart = Date.now();
1094
+ queueMessage(tlsMsg, msgSeq, dtlsEpoch, ctx.currentFlight, 'flight');
1095
+ }
1096
+
1097
+ // What one record adds around its payload, by the epoch it goes out in
1098
+ // (flushOut fills datagrams to the MTU with it):
1099
+ // unprotected (epoch 0, and DTLS 1.2 before the CCS): the 13-byte
1100
+ // DTLSPlaintext header, nothing else;
1101
+ // DTLS 1.3 protected: the unified header (1 + 2-byte sequence number +
1102
+ // 2-byte length), the inner content-type byte, the 16-byte tag;
1103
+ // DTLS 1.2 protected: 13 + the 16-byte tag + the 8-byte explicit nonce
1104
+ // of AES-GCM / CCM (ChaCha20-Poly1305 has none, RFC 7905 §2);
1105
+ // and, with the peer's CID, the CID — and in 1.2 the inner type byte.
1106
+ // A single figure for all — 37, or 21 for 1.3 — left 24 bytes unused in
1107
+ // every plaintext fragment (the whole DTLS 1.2 certificate flight), and
1108
+ // was a byte short for 1.3, which forgot the content type.
1109
+ function recordOverhead(epoch) {
1110
+ if (!ctx.keys[epoch] || !ctx.keys[epoch].write) return 13;
1111
+ let cid = peerCid(), cidLen = cid ? cid.length : 0;
1112
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return 5 + cidLen + 1 + 16;
1113
+ // The explicit nonce, as the suite table has it (record_ivlen).
1114
+ let meta = TLS_CIPHER_SUITES[tls.getCipher()];
1115
+ return 13 + (cid ? cidLen + 1 : 0) + 16 + (meta && meta.record_ivlen ? meta.record_ivlen : 0);
1116
+ }
1117
+
1118
+ // DTLS 1.2, a ticket offered with no session_id (a hello offering DTLS
1119
+ // 1.3 too): an epoch-1 record before any keys is the resumed server's
1120
+ // ChangeCipherSpec or Finished — a full handshake has none before our
1121
+ // flight. Tried under the keys the saved session gives; one that opens
1122
+ // takes the resumption (tls.commitResume12), and the keys are installed.
1123
+ // One that does not — anyone may send a datagram — changes nothing.
1124
+ function tryResumedKeys12(record) {
1125
+ let ms = tls.resumeCandidate12();
1126
+ if (!ms) return;
1127
+ let d12;
1128
+ try { d12 = keys12(ms); } catch (e) { return; }
1129
+ let opened;
1130
+ try { opened = openDtls12(record.content, { key: d12.readKey, iv: d12.readIv }, record.epoch, record.seq, record.type, localCid()); }
1131
+ catch (e) { return; }
1132
+ if (!tls.commitResume12() || !installKeys12()) return;
1133
+ open12(record, ctx.keys[1].read, opened);
1134
+ }
1135
+
1136
+ // A DTLS 1.2 record of an epoch whose keys came after the datagram was
1137
+ // parsed, opened now (or `opened` already: { type, content }), and through
1138
+ // the replay window, as the datagram parser does. False when it does not
1139
+ // open; one that opens and is malformed inside is the peer's (alertDesc).
1140
+ function open12(record, readKeys, opened) {
1141
+ try {
1142
+ opened = opened || openDtls12(record.content, readKeys, record.epoch, record.seq, record.type, localCid());
1143
+ } catch (e) { if (e && e.alertDesc) throw e; return false; }
1144
+ record.type = opened.type;
1145
+ record.content = opened.content;
1146
+ record.encrypted = true;
1147
+ if (!acceptRecordSeq(readKeys, record.seq)) record.replayed = true;
1148
+ return true;
1149
+ }
1150
+
1151
+ // DTLS 1.2: epoch 1's keys, from a master secret — the session's, once
1152
+ // there is one, or a candidate's (tryResumedKeys12). One place: they were
1153
+ // derived in three.
1154
+ function keys12(masterSecret) {
1155
+ let ts = tls.getTrafficSecrets();
1156
+ return deriveKeys12(masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
1157
+ }
1158
+ // …installed, once there is a master secret to make them of. True when
1159
+ // they are there.
1160
+ function installKeys12() {
1161
+ if (ctx.keys[1]) return true;
1162
+ let ts = tls.getTrafficSecrets();
1163
+ if (!ts.masterSecret) return false;
1164
+ let d12 = keys12(ts.masterSecret);
1165
+ ctx.keys[1] = {
1166
+ write: { key: d12.writeKey, iv: d12.writeIv },
1167
+ read: { key: d12.readKey, iv: d12.readIv },
1168
+ };
1169
+ ctx.writeSeq[1] = 0;
1170
+ return true;
1171
+ }
1172
+
1173
+ // DTLS 1.3: the application epoch we write — 3 until a KeyUpdate moves it on.
1174
+ function appEpoch() { return ctx.writeEpoch !== null ? ctx.writeEpoch : 3; }
1175
+
1176
+ /**
1177
+ * The epoch our alerts (and close_notify) go out in: the highest one we
1178
+ * hold write keys for and the peer reads. DTLS 1.3: the live application
1179
+ * epoch once connected — after a KeyUpdate that is 4, 5, ..., and the peer
1180
+ * no longer reads 3 — else the handshake epoch once its keys exist, else 0
1181
+ * (a DTLSPlaintext record). DTLS 1.2: epoch 1 once our ChangeCipherSpec
1182
+ * has gone, else 0.
1183
+ */
1184
+ function alertEpoch() {
1185
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
1186
+ if (ctx.state === 'connected' && ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch] && ctx.keys[ctx.writeEpoch].write) return ctx.writeEpoch;
1187
+ if (ctx.keys[2] && ctx.keys[2].write) return 2;
1188
+ return 0;
1189
+ }
1190
+ return (ctx.localCcsSent && ctx.keys[1] && ctx.keys[1].write) ? 1 : 0;
866
1191
  }
867
1192
 
868
1193
  /**
869
1194
  * Send an alert record.
870
1195
  */
871
1196
  function sendAlertRecord(epoch, alertData) {
1197
+ // What is queued goes first, the alert in a datagram of its own (see
1198
+ // flushOut).
1199
+ flushOut();
872
1200
  let record = buildRecord(epoch, CT.ALERT, alertData);
873
1201
  // buildRecord returns null when it refuses to emit a malformed record
874
1202
  // (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
875
1203
  // packet pushes `null` into the transport; drop it instead — we already
876
1204
  // surfaced the reason through 'error'.
877
1205
  if (!record) return;
878
- _ldbgTx(record, 'L664'); ev.emit('packet', record);
1206
+ _ldbgTx(record, 'alert'); ev.emit('packet', record);
879
1207
  }
880
1208
 
881
1209
  /**
@@ -890,7 +1218,6 @@ function DTLSSession(options) {
890
1218
  ctx.newFlightPending = false;
891
1219
  startNewFlight();
892
1220
  }
893
- let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
894
1221
  // RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
895
1222
  // when the flight is retransmitted. Captured as a descriptor (epoch 0,
896
1223
  // plaintext), so resendFlight rebuilds it with a fresh seq like every
@@ -898,8 +1225,16 @@ function DTLSSession(options) {
898
1225
  // retransmitted final flight arrived WITHOUT the cipher-spec change and
899
1226
  // the peer could never decrypt the accompanying Finished — an
900
1227
  // unrecoverable stall under single-datagram loss.
901
- ctx.currentFlight.push({ epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]) });
902
- _ldbgTx(record, 'L688'); ev.emit('packet', record);
1228
+ //
1229
+ // Into the flight where it goes out, as the messages' fragments are
1230
+ // (flushOut): the messages queued before it — the ClientKeyExchange, a
1231
+ // Certificate — are cut into entries only then, and a CCS put in the
1232
+ // list here, at once, went ahead of them. Every retransmission then
1233
+ // opened with the CCS (BoGo DTLS-RetransmitFinished-Fragmented: "record
1234
+ // of type 20 in retransmit, but expected 22"); a peer that reads in
1235
+ // order takes it before the key exchange it follows.
1236
+ let ccs = { epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]), list: ctx.currentFlight };
1237
+ queueOut({ kind: 'entry', entry: ccs, label: 'ccs', join: true });
903
1238
  }
904
1239
 
905
1240
 
@@ -907,7 +1242,67 @@ function DTLSSession(options) {
907
1242
  // Incoming: UDP datagram → parse → feed TLSSession
908
1243
  // ============================================================
909
1244
 
1245
+ /**
1246
+ * The one way a DTLS session ends in failure: our alert (when there is one
1247
+ * to send — none when the peer's alert or TLSSession's own already went),
1248
+ * the session closed (no more input, no retransmission), 'error', then
1249
+ * 'close'. 'close' is what DTLSServer drops the connection on: a session
1250
+ * that only emitted 'error' stayed in its table, deaf, and the address
1251
+ * could not start a new handshake.
1252
+ */
1253
+ function fail(err, alertDesc) {
1254
+ if (ctx.state === 'closed') return;
1255
+ if (alertDesc !== undefined && alertDesc !== null) {
1256
+ try { tls.sendAlert(2, alertDesc); } catch (e) {}
1257
+ err = local_alert_error(alertDesc, err);
1258
+ }
1259
+ shutDown();
1260
+ ev.emit('error', err);
1261
+ ev.emit('close');
1262
+ }
1263
+
1264
+ /**
1265
+ * The input boundary: nothing a peer sends may throw out of this session
1266
+ * (a single UDP datagram, spoofable by anyone, took the process down —
1267
+ * BoGo). What cannot be read as a record is dropped where it is read, as
1268
+ * RFC 9147 §4.5.2 / RFC 6347 §4.1.2.7 ask ("invalid records SHOULD be
1269
+ * silently discarded"); what is left to throw here comes from a record
1270
+ * that WAS read, or from a bug, and ends the session — the error's own
1271
+ * alert (alertDesc), internal_error otherwise. TLSSession has the same
1272
+ * boundary for what reaches it (run_guarded).
1273
+ */
1274
+ //
1275
+ // Returns whether the datagram held a record that authenticated and is
1276
+ // the newest yet (by epoch, then sequence number) — RFC 9146 §6 / RFC
1277
+ // 9147 §9's condition for taking a new peer address, which the layer
1278
+ // above decides.
910
1279
  function feedDatagram(data) {
1280
+ ctx.dgNewest = false;
1281
+ try {
1282
+ feedDatagramUnguarded(data);
1283
+ } catch (e) {
1284
+ let err = e instanceof Error ? e : new Error(String(e));
1285
+ fail(err, (e && e.alertDesc) || TLS_ALERT.INTERNAL_ERROR);
1286
+ return false;
1287
+ }
1288
+ return ctx.dgNewest;
1289
+ }
1290
+
1291
+ // A protected record taken: the newest so far?
1292
+ function noteNewest(record) {
1293
+ let n = ctx.rxNewest;
1294
+ if (n && (record.epoch < n.epoch || (record.epoch === n.epoch && record.seq <= n.seq))) return;
1295
+ ctx.rxNewest = { epoch: record.epoch, seq: record.seq };
1296
+ ctx.dgNewest = true;
1297
+ }
1298
+
1299
+ function feedDatagramUnguarded(data) {
1300
+ ctx.feeding++;
1301
+ try { takeDatagram(data); }
1302
+ finally { if (--ctx.feeding === 0) flushHeldPostHandshake(); }
1303
+ }
1304
+
1305
+ function takeDatagram(data) {
911
1306
  if (ctx.state === 'closed') return;
912
1307
  if (ctx.state === 'idle') ctx.state = 'handshaking';
913
1308
 
@@ -919,7 +1314,7 @@ function DTLSSession(options) {
919
1314
  }
920
1315
  }
921
1316
 
922
- let records = parseDtlsDatagram(data, keysByEpoch);
1317
+ let records = parseDtlsDatagram(data, keysByEpoch, localCid(), localCidRequired());
923
1318
 
924
1319
  if (LEMON_DEBUG) {
925
1320
  var _summ = [];
@@ -933,9 +1328,9 @@ function DTLSSession(options) {
933
1328
  }
934
1329
 
935
1330
  // RFC 9147 §7: collect the record numbers of handshake records we received
936
- // in this datagram so we can ACK them once, after processing. Only for
937
- // DTLS 1.3, and only while we're still handshaking (post-handshake ACKs of
938
- // NewSessionTicket etc. are optional and skipped here for simplicity).
1331
+ // in this datagram so we can ACK them once, after processing (DTLS 1.3):
1332
+ // a flight's at once or a quarter-timer later, post-handshake ones
1333
+ // together, delayed (see "ACKs" below).
939
1334
  let ackable = [];
940
1335
 
941
1336
  // Whether these records arrived AFTER the handshake must be decided here,
@@ -946,8 +1341,27 @@ function DTLSSession(options) {
946
1341
  // the spurious epoch-3 ACK that makes a peer answer decode_error on the
947
1342
  // NEXT record — so a perfectly good SCTP COOKIE_ECHO takes the blame.
948
1343
  let wasConnectedOnEntry = (ctx.state === 'connected');
1344
+ let sentBefore = ctx.flightMessagesQueued;
1345
+ ctx.ackDisruption = false;
1346
+ ctx.dgAnyNew = false;
1347
+ ctx.dgDuplicate = false;
1348
+ ctx.dgNewest = false;
949
1349
 
950
1350
  for (let i = 0; i < records.length; i++) {
1351
+ // An unprotected record received before — same epoch-0 sequence number
1352
+ // — is the same record again (replayed, or duplicated on the way), not
1353
+ // the peer retransmitting: a retransmission is a new record, with a new
1354
+ // number. Taken as one, it had us resend our flight (BoGo
1355
+ // DTLS-Replay-TLS12). Not a sliding window, as for protected records
1356
+ // (acceptRecordSeq): an unauthenticated number must not move one — a
1357
+ // forged high one would make every real record "too old". Just the
1358
+ // last numbers seen.
1359
+ if (records[i].epoch === 0 && !records[i].encrypted) {
1360
+ if (ctx.seenPlainSeqs.indexOf(records[i].seq) >= 0) continue;
1361
+ ctx.seenPlainSeqs.push(records[i].seq);
1362
+ if (ctx.seenPlainSeqs.length > 64) ctx.seenPlainSeqs.shift();
1363
+ }
1364
+ if (records[i].replayed) continue;
951
1365
  // A record we could not decrypt when the datagram was parsed. Keys may
952
1366
  // have arrived since — processing an earlier record in THIS datagram
953
1367
  // (the ServerHello) is exactly what installs them. Retry now, in order,
@@ -958,7 +1372,9 @@ function DTLSSession(options) {
958
1372
  let readKeys = epochKeys ? epochKeys.read : null;
959
1373
  if (!readKeys) continue; // still unreadable — let the peer retransmit
960
1374
  let dec = null;
961
- try { dec = decryptEncryptedDtls13(records[i].raw, readKeys); } catch (e) { dec = null; }
1375
+ // A record that opened but is malformed inside (alertDesc) is the
1376
+ // peer's: to the boundary. Anything else: it did not open, dropped.
1377
+ try { dec = decryptEncryptedDtls13(records[i].raw, readKeys, records[i].epoch, localCid(), localCidRequired()); } catch (e) { if (e && e.alertDesc) throw e; dec = null; }
962
1378
  if (!dec) continue;
963
1379
  records[i] = {
964
1380
  type: dec.type, epoch: dec.epoch, seq: dec.seq,
@@ -982,67 +1398,124 @@ function DTLSSession(options) {
982
1398
  ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
983
1399
  }
984
1400
 
1401
+ let hadKeys12 = !!ctx.keys[1];
1402
+ if (!hadKeys12 && ctx.selectedVersion === DTLS_VERSION.DTLS1_2 && records[i].epoch === 1 && !records[i].encrypted) {
1403
+ tryResumedKeys12(records[i]);
1404
+ }
985
1405
  processRecord(records[i]);
1406
+ if (ctx.state === 'closed') break;
1407
+ if (records[i].encrypted) noteNewest(records[i]);
1408
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2 && !ctx.keys[1]) installKeys12();
986
1409
 
987
- // After CCS, keys are newly available — re-decrypt remaining epoch>0 records
988
- if (records[i].type === CT.CHANGE_CIPHER_SPEC && ctx.keys[1]) {
1410
+ // DTLS 1.2 keys just installed: what of this datagram came protected
1411
+ // under them can be read now.
1412
+ if (!hadKeys12 && ctx.keys[1]) {
989
1413
  let readKeys = ctx.keys[1].read;
990
1414
  for (let j = i + 1; j < records.length; j++) {
991
- if (!records[j].encrypted && records[j].epoch > 0 && readKeys) {
992
- try {
993
- records[j].content = decryptDtls12(records[j].content, readKeys.key, readKeys.iv, records[j].epoch, records[j].seq, records[j].type);
994
- records[j].encrypted = true;
995
- } catch(e) {
996
- }
997
- }
1415
+ if (!records[j].encrypted && records[j].epoch > 0 && readKeys) open12(records[j], readKeys);
998
1416
  }
999
1417
  }
1000
1418
  }
1001
1419
 
1002
- // Send a single ACK covering all handshake records received in this datagram.
1003
- // We ACK from the highest epoch for which we already have write keys (so the
1004
- // ACK itself is protected once handshake keys exist; epoch 0 before that).
1005
- if (ackable.length > 0) {
1006
- // ACKs RIDE THE HANDSHAKE EPOCH (RFC 9147 §7): they acknowledge
1007
- // handshake records, so epoch 2 once handshake keys exist and
1008
- // epoch 0 before that — never epoch 3. Picking "the highest epoch
1009
- // we have keys for" sent them under APPLICATION-DATA protection,
1010
- // where the peer decrypts with app keys and finds a content type
1011
- // that does not belong. Chrome answers decode_error.
1012
- // This shows up the moment a ClientHello is large enough to be
1013
- // FRAGMENTED — an SFU with several transceivers crosses the MTU
1014
- // and a two-way call does not, which is exactly why bidirectional
1015
- // worked and party mode did not.
1016
- // Post-handshake (§7.1: "For post-handshake messages, ACKs SHOULD be
1017
- // sent once for each received and processed handshake record") the
1018
- // handshake epoch is gone and §7 requires the HIGHEST available sending
1019
- // epoch. During the handshake it is still epoch 2, or 0 before keys.
1020
- // RFC 9147 §7.1 lists the flights that are IMPLICITLY acknowledged by
1021
- // the responding flight, and the client's final flight is pointedly not
1022
- // among them: "In general, flights MUST be ACKed unless they are
1023
- // implicitly acknowledged", excepting "handshake flights other than the
1024
- // client's final flight of the main handshake".
1025
- //
1026
- // So the two roles need opposite answers here, which is why a
1027
- // state-only test could not get both right:
1028
- // client — the server's flight is implicitly acked by our own final
1029
- // flight, so an explicit ACK is both redundant and (at the
1030
- // application epoch) actively harmful;
1031
- // server — the client's final flight has no responding flight, so
1032
- // without an explicit ACK the client retransmits it forever.
1033
- // §7 then fixes the epoch: after the handshake, the highest available
1034
- // sending epoch.
1035
- let justCompleted = (!wasConnectedOnEntry && ctx.state === 'connected');
1036
- let finalFlightAck = isServer && justCompleted;
1037
- let atAppEpoch = wasConnectedOnEntry || finalFlightAck;
1038
-
1039
- let ackEpoch = atAppEpoch
1040
- ? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
1041
- : (ctx.keys[2] ? 2 : 0);
1042
- sendAck(ackEpoch, ackable, atAppEpoch);
1420
+ dtls12AnswerRetransmission();
1421
+
1422
+ // ── ACKs (RFC 9147 §7.1) ──
1423
+ // Post-handshake records (NewSessionTicket, KeyUpdate, or the client's
1424
+ // final flight again, its ACK lost): nothing answers them, so they are
1425
+ // ACKed — at the highest sending epoch (§7) — but not each at once: one
1426
+ // ACK a quarter of the timer later covers all that came meanwhile (§7.1,
1427
+ // "implementations SHOULD [...] delay sending ACKs"; a server's two
1428
+ // tickets, two records each, were four ACKs).
1429
+ if (wasConnectedOnEntry) {
1430
+ for (let ai = 0; ai < ackable.length; ai++) noteAck(ctx.postAcks, ackable[ai]);
1431
+ if (ctx.postAcks.length > 0 && ctx.postAckTimer === null) {
1432
+ ctx.postAckTimer = setTimeout(function () {
1433
+ ctx.postAckTimer = null;
1434
+ if (ctx.state === 'closed' || ctx.postAcks.length === 0) return;
1435
+ let all = ctx.postAcks; ctx.postAcks = [];
1436
+ sendAck(appEpoch(), all, true);
1437
+ }, ackDelay(currentRto()));
1438
+ if (ctx.postAckTimer.unref) ctx.postAckTimer.unref();
1439
+ }
1440
+ return;
1441
+ }
1442
+ for (let ai = 0; ai < ackable.length; ai++) noteAck(ctx.pendingAcks, ackable[ai]);
1443
+ if (ctx.pendingAcks.length === 0) return;
1444
+
1445
+ // The client's final flight, now complete (the handshake is): nothing of
1446
+ // ours answers it (a NewSessionTicket that follows is no answer — it is
1447
+ // checked first for that reason), so it is ACKed explicitly, at once — §7.1 "MUST send
1448
+ // ACKs upon receiving all of any flight which they do not respond to" —
1449
+ // at the highest sending epoch, now the application one (§7).
1450
+ if (isServer && ctx.state === 'connected') {
1451
+ let all = ctx.pendingAcks; clearPendingAcks();
1452
+ sendAck(appEpoch(), all, true);
1453
+ return;
1454
+ }
1455
+ // The flight is complete and we answered it: our flight is the ACK
1456
+ // (implicit). Sending one as well is what the RFC asks us not to do —
1457
+ // "ACKs SHOULD NOT be sent for these complete flights" — and what BoGo
1458
+ // saw: an ACK for every datagram of a flight, landing where the peer
1459
+ // does not expect one.
1460
+ if (ctx.flightMessagesQueued > sentBefore) { clearPendingAcks(); return; }
1461
+
1462
+ if (ctx.state !== 'handshaking') { clearPendingAcks(); return; }
1463
+
1464
+ // Part of a flight. A sign it is disrupted — a message or fragment out
1465
+ // of order — is ACKed now, so the peer resends only what is missing;
1466
+ // otherwise a timer of a quarter of the retransmit timeout (§7.1's
1467
+ // suggestion) gives the rest of the flight time to arrive.
1468
+ if (ctx.ackDisruption) {
1469
+ cancelAckTimer();
1470
+ sendHandshakeAck();
1471
+ } else if (ctx.ackTimer === null) {
1472
+ ctx.ackTimer = setTimeout(function () {
1473
+ ctx.ackTimer = null;
1474
+ if (ctx.state === 'handshaking' && ctx.pendingAcks.length > 0) sendHandshakeAck();
1475
+ // A quarter of the retransmit timer (§7.1) — its initial value: the
1476
+ // timer's backoff counts losses of OUR earlier flight (a ClientHello
1477
+ // resent until the ServerHello came), which say nothing about how long
1478
+ // the rest of the peer's flight may take. Read off the backed-off timer
1479
+ // it was 2 s after three lost ClientHellos.
1480
+ }, ackDelay(Math.min(ctx.retransmitTimeout, currentRto())));
1481
+ if (ctx.ackTimer.unref) ctx.ackTimer.unref();
1043
1482
  }
1044
1483
  }
1045
1484
 
1485
+ // How long an ACK waits: a quarter of the retransmit timer (§7.1's
1486
+ // suggestion), a millisecond at least.
1487
+ function ackDelay(rto) { return Math.max(1, Math.floor(rto / 4)); }
1488
+ // A record to ACK, kept in `list`: the records of the peer's flight in
1489
+ // progress (pendingAcks), ACKed cumulatively — each ACK lists all of them
1490
+ // received so far (§7.1 "the messages from that flight which it has
1491
+ // received and processed so far") — or post-handshake ones (postAcks);
1492
+ // either way, up to what fits in one ACK record: its header (13), the
1493
+ // record numbers' length (2) and an AEAD tag's worth (22) out of the MTU,
1494
+ // 16 bytes a record number (RFC 9147 §7).
1495
+ function noteAck(list, rn) {
1496
+ for (let i = 0; i < list.length; i++) {
1497
+ if (list[i].epoch === rn.epoch && list[i].seq === rn.seq) return;
1498
+ }
1499
+ list.push(rn);
1500
+ let room = Math.max(1, Math.floor((ctx.mtu - 13 - 22 - 2) / 16));
1501
+ if (list.length > room) list.splice(0, list.length - room);
1502
+ }
1503
+ function cancelAckTimer() {
1504
+ if (ctx.ackTimer !== null) { clearTimeout(ctx.ackTimer); ctx.ackTimer = null; }
1505
+ }
1506
+ function clearPendingAcks() {
1507
+ cancelAckTimer();
1508
+ ctx.pendingAcks = [];
1509
+ if (ctx.postAckTimer !== null) { clearTimeout(ctx.postAckTimer); ctx.postAckTimer = null; }
1510
+ ctx.postAcks = [];
1511
+ }
1512
+ // During the handshake: the handshake epoch (2), never 0 — only protected
1513
+ // records are ever ACKed (the first flight never is).
1514
+ function sendHandshakeAck() {
1515
+ if (!ctx.keys[2]) return;
1516
+ sendAck(2, ctx.pendingAcks.slice(), false);
1517
+ }
1518
+
1046
1519
  function processRecord(record) {
1047
1520
  // A record in an epoch above 0 that did not decrypt is not the peer's
1048
1521
  // (forged, corrupted, or a late packet from a previous session on the
@@ -1058,6 +1531,23 @@ function DTLSSession(options) {
1058
1531
  if (ctx.state === 'connected' && !record.encrypted &&
1059
1532
  (record.type === CT.ALERT || record.type === CT.APPLICATION_DATA)) return;
1060
1533
 
1534
+ if (record.type !== CT.ALERT) ctx.consecutiveWarnings = 0;
1535
+
1536
+ // A record that opened under its keys is the peer's: more plaintext
1537
+ // than a record may carry — 2^14 (RFC 8446 §5.1, RFC 5246 §6.2.1), or
1538
+ // our record_size_limit once negotiated (RFC 8449 §4) — is
1539
+ // record_overflow, as in TLS. Without a limit agreed, nothing was
1540
+ // checked at all (BoGo LargePlaintext-DTLS). An unprotected one is
1541
+ // anyone's: what arrives too long is dropped as the datagram is parsed
1542
+ // (record.js).
1543
+ if (record.encrypted && record.content) {
1544
+ let rsl = tls.getRecordSizeLimit(), max = rsl ? rsl.receive : MAX_RECORD_PLAINTEXT;
1545
+ if (record.content.length > max) {
1546
+ throw parseError('DTLS record of ' + record.content.length + ' bytes, more than ' +
1547
+ (rsl ? 'our record_size_limit (' + max + ')' : '2^14'), TLS_ALERT.RECORD_OVERFLOW);
1548
+ }
1549
+ }
1550
+
1061
1551
  if (record.type === CT.HANDSHAKE) {
1062
1552
  // An epoch>0 record that could not be decrypted (keys not yet
1063
1553
  // installed, AEAD failure) must NOT be fed to the handshake
@@ -1074,39 +1564,81 @@ function DTLSSession(options) {
1074
1564
  processHandshakeRecord(record.content, record.epoch, record.encrypted);
1075
1565
  } else if (record.type === CT.APPLICATION_DATA) {
1076
1566
  if (ctx.state === 'connected') {
1077
- ev.emit('data', record.content);
1567
+ // DTLS 1.2: protected data from the peer means it has our final
1568
+ // flight — nothing more to answer its retransmissions with.
1569
+ //
1570
+ // NOT DTLS 1.3's final flight (finalFlightPending): that one is
1571
+ // acknowledged by an ACK alone (RFC 9147 §7.1: it is the one flight
1572
+ // not implicitly acknowledged, and "flights MUST be ACKed unless
1573
+ // they are implicitly acknowledged"). A server sends data before it
1574
+ // has our Finished — half-RTT data, its tickets — so its data said
1575
+ // nothing of our Finished, and taken for it, a lost Finished was
1576
+ // never resent: the server's handshake never ended (BoGo
1577
+ // DTLS-Retransmit-Client-NoImplictACKFinished). A peer that never
1578
+ // ACKs costs only the retransmissions: when they run out the flight
1579
+ // is let go, the connection kept (startRetransmitTimer).
1580
+ ctx.lastFlight12 = null;
1581
+ // A Buffer, as node:tls gives (and every other buffer this API hands
1582
+ // out): a view of the decrypted record, not a copy.
1583
+ let c = record.content;
1584
+ ev.emit('data', Buffer.isBuffer(c) ? c : Buffer.from(c.buffer, c.byteOffset, c.byteLength));
1078
1585
  }
1079
1586
  } else if (record.type === CT.CHANGE_CIPHER_SPEC) {
1080
- // DTLS 1.2: peer sent CCS — derive keys if not yet done
1081
- ctx.remoteCcsSeen = true;
1082
- if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && !ctx.keys[1]) {
1083
- let ts = tls.getTrafficSecrets();
1084
- if (ts.masterSecret) {
1085
- let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
1086
- ctx.keys[1] = {
1087
- write: { key: d12.writeKey, iv: d12.writeIv },
1088
- read: { key: d12.readKey, iv: d12.readIv },
1089
- };
1090
- ctx.writeSeq[1] = 0;
1091
- }
1092
- }
1587
+ // One byte, 1 (RFC 6347 §4.1, RFC 5246 §7.1), and DTLS 1.2 only — DTLS
1588
+ // 1.3 has none (RFC 9147 §5: not sent, and none is a key change here).
1589
+ // Anything else changed our read state all the same. It is an
1590
+ // unprotected record that does not read as one: dropped (§4.1.2.7).
1591
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return;
1592
+ if (!record.content || record.content.length !== 1 || record.content[0] !== 1) return;
1593
+ // DTLS 1.2: the peer's key change. The keys come of the master
1594
+ // secret, and are installed when it exists (installKeys12, after each
1595
+ // record) — a CCS ahead of the key exchange (reordered: RFC 6347 §4.1),
1596
+ // or one lost, changes nothing: no alert that would end a good
1597
+ // handshake on one reordered datagram, and no flight resent for it
1598
+ // either (DTLS-Fuzzer D5). A Finished must still come protected
1599
+ // (epochFitsHandshake): the key change is proved by it, not by this.
1600
+ installKeys12();
1093
1601
  } else if (record.type === CT.ACK) {
1094
- processAck(record.content);
1602
+ // DTLS 1.3 only (RFC 9147 §7). One that does not parse: protected, the
1603
+ // peer's (decode_error, thrown to the boundary); unprotected, dropped.
1604
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return;
1605
+ let acks;
1606
+ try { acks = parseDtlsAck(record.content); }
1607
+ catch (e) { if (!record.encrypted) return; throw e; }
1608
+ processAck(acks);
1095
1609
  } else if (record.type === CT.ALERT) {
1096
- let level = record.content[0];
1097
- let desc = record.content[1];
1610
+ // As TLS over TCP reads it (wire.read_peer_alert). A malformed one:
1611
+ // protected, the alert for it; unprotected — anyone can send a
1612
+ // datagram — dropped.
1613
+ let a = read_peer_alert(record.content, ctx.selectedVersion === DTLS_VERSION.DTLS1_3);
1614
+ if (a.error) {
1615
+ if (!record.encrypted) return;
1616
+ throw parseError('DTLS ' + a.error.message, a.error.alert);
1617
+ }
1618
+ let desc = a.desc;
1098
1619
  // Name the alert either way. A close_notify is a CLEAN shutdown and
1099
1620
  // a failure is not, but both present to the application as "the
1100
1621
  // connection ended" — and telling them apart is the difference
1101
1622
  // between "the peer hung up" and "the peer rejected us".
1102
- _ldbg('RX Alert level=' + level + ' desc=' + desc +
1103
- (desc === 0 ? ' (close_notify — clean shutdown)' : ' (failure)'));
1104
- if (desc === 0) {
1105
- // close_notify
1106
- ctx.state = 'closed';
1107
- ev.emit('close');
1623
+ _ldbg('RX Alert level=' + a.level + ' desc=' + desc +
1624
+ (desc === TLS_ALERT.CLOSE_NOTIFY ? ' (close_notify — clean shutdown)' : ' (failure)'));
1625
+ ev.emit('alert', { level: a.level, description: desc });
1626
+ if (desc === TLS_ALERT.CLOSE_NOTIFY) {
1627
+ // close_notify — answered with ours (RFC 5246 §7.2.1: "The other
1628
+ // party MUST respond with a close_notify alert of its own"; RFC 8446
1629
+ // §6.1 the same, in effect: each side closes its write side with
1630
+ // one). close() sends it and ends the session. It was not answered.
1631
+ close();
1632
+ } else if (!a.fatal) {
1633
+ // A warning (user_canceled among them): the connection goes on. A
1634
+ // run of them is not progress.
1635
+ if (++ctx.consecutiveWarnings > MAX_WARNING_ALERTS) {
1636
+ throw parseError('DTLS: too many consecutive warning alerts', TLS_ALERT.UNEXPECTED_MESSAGE);
1637
+ }
1108
1638
  } else {
1109
- ev.emit('error', new Error('DTLS alert: level=' + level + ' desc=' + desc));
1639
+ // Fatal: the session ends; it used to report and carry on,
1640
+ // retransmitting.
1641
+ fail(peer_alert_error(desc)); // as TLS over TCP reports it
1110
1642
  }
1111
1643
  }
1112
1644
  }
@@ -1119,8 +1651,25 @@ function DTLSSession(options) {
1119
1651
  let off = 0;
1120
1652
  let sawMessage = false; // record contained ≥1 parseable handshake message
1121
1653
  let anyNew = false; // ≥1 message advanced our state (not a duplicate)
1122
- while (off + 12 <= data.length) {
1123
- let parsed = parse_dtls_handshake(data.subarray(off));
1654
+ // A handshake fragment lies wholly inside its record (RFC 6347 §4.2.3,
1655
+ // RFC 9147 §5.5: fragments are not split across records). One that does
1656
+ // not — its 12-byte header cut short, or its body running past the end
1657
+ // — is decode_error: the record itself read fine, the handshake inside
1658
+ // it is malformed (§4.5.2's silent discard is for records that do not
1659
+ // read as records). Dropping it left the session waiting for good (BoGo
1660
+ // SplitFragments-Header/Body/Boundary). Except for an unprotected record
1661
+ // once we are connected: then it can only be a stray retransmission or
1662
+ // anyone's (spoofed) datagram, and one must not end the connection — it
1663
+ // is dropped, as an unprotected ACK or alert is.
1664
+ let strict = encrypted || ctx.state !== 'connected';
1665
+ while (off < data.length) {
1666
+ if (data.length - off < 12) {
1667
+ if (!strict) return;
1668
+ throw parseError('DTLS: a handshake fragment header cut short (' + (data.length - off) + ' bytes) at the end of its record', TLS_ALERT.DECODE_ERROR);
1669
+ }
1670
+ let parsed;
1671
+ try { parsed = parse_dtls_handshake(data.subarray(off)); } // decode_error when the body runs past
1672
+ catch (e) { if (!strict) return; throw e; }
1124
1673
 
1125
1674
  let msgSeq = parsed.msg_seq;
1126
1675
  let totalLen = parsed.length;
@@ -1134,10 +1683,10 @@ function DTLSSession(options) {
1134
1683
  // Is this a complete message or a fragment?
1135
1684
  if (fragOffset === 0 && fragLen === totalLen) {
1136
1685
  // Complete message — feed directly
1137
- if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
1686
+ if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq, epoch)) anyNew = true;
1138
1687
  } else {
1139
1688
  // Fragment — reassemble
1140
- if (reassembleFragment(parsed)) anyNew = true;
1689
+ if (reassembleFragment(parsed, epoch)) anyNew = true;
1141
1690
  }
1142
1691
 
1143
1692
  off += 12 + fragLen;
@@ -1162,28 +1711,42 @@ function DTLSSession(options) {
1162
1711
  // of the flight we JUST sent, not on the acknowledged one. The
1163
1712
  // cancel happens at the acceptance points inside
1164
1713
  // deliverHandshakeMessage instead, before the feed.
1165
- if (anyNew) {
1166
- // handled at acceptance time
1167
- } else if (sawMessage && ctx.currentFlight.length > 0 &&
1168
- ctx.state !== 'connected' &&
1169
- ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 &&
1170
- // Damping: a peer retransmits its flight as SEVERAL
1171
- // datagrams; without a guard, EACH duplicate datagram
1172
- // elicited a full resend of our flight — a ×N
1173
- // amplification burst that itself increased loss
1174
- // pressure. One resend per 250ms answers the peer's
1175
- // whole retransmitted flight exactly once.
1176
- (Date.now() - (ctx._lastDupElicitedResend || 0)) >= 250) {
1714
+ // Whether this record carried only messages already taken — the
1715
+ // peer retransmitting — is decided for the whole datagram, in
1716
+ // feedDatagram (dtls12AnswerRetransmission): a datagram that also
1717
+ // brings something new is progress, not a retransmission.
1718
+ if (anyNew) ctx.dgAnyNew = true;
1719
+ else if (sawMessage) ctx.dgDuplicate = true;
1720
+ }
1721
+
1722
+ /**
1723
+ * DTLS 1.2 (RFC 6347 §4.2.4): the peer retransmitting its last flight
1724
+ * means it never got our answer — we resend ours at once, rather than
1725
+ * waiting out our own timer. DTLS 1.3 has ACKs for this instead.
1726
+ *
1727
+ * After the handshake, too: the side that sent the LAST flight (the
1728
+ * server's CCS + Finished in a full handshake) keeps it, and resends it
1729
+ * when the peer's final flight comes again — the state machine's FINISHED
1730
+ * state. It was dropped at 'connect', so a lost server Finished left the
1731
+ * client retransmitting into a server that had nothing to say: the
1732
+ * handshake never completed (BoGo Retransmit-*-TLS12; and 1 in 7 at 30%
1733
+ * loss in dtls_loss_test). Kept until the peer's application data shows
1734
+ * it finished.
1735
+ */
1736
+ function dtls12AnswerRetransmission() {
1737
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return;
1738
+ if (!ctx.dgDuplicate || ctx.dgAnyNew) return;
1739
+ // Damping: a peer retransmits its flight as SEVERAL datagrams; one
1740
+ // resend per 250 ms answers the whole retransmitted flight once.
1741
+ if ((Date.now() - (ctx._lastDupElicitedResend || 0)) < 250) return;
1742
+ if (ctx.state === 'handshaking' && haveFlight()) {
1177
1743
  ctx._lastDupElicitedResend = Date.now();
1178
- // Pure duplicate of a previous peer flight while we still have an
1179
- // outstanding flight of our own: the peer is telling us it never
1180
- // got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
1181
- // behavior) rather than waiting out our own backoff. The regular
1182
- // timer keeps running unchanged, so the retransmit budget still
1183
- // bounds the total effort; the resend rate is naturally bounded by
1184
- // the peer's own retransmission schedule. DTLS 1.3 is excluded —
1185
- // it uses explicit ACKs (RFC 9147 §7) instead of flight inference.
1744
+ // The regular timer keeps running unchanged, so the retransmit budget
1745
+ // still bounds the total effort.
1186
1746
  resendFlight();
1747
+ } else if (ctx.state === 'connected' && ctx.lastFlight12) {
1748
+ ctx._lastDupElicitedResend = Date.now();
1749
+ transmitUnacked(ctx.lastFlight12, 'lastFlight12', false);
1187
1750
  }
1188
1751
  }
1189
1752
 
@@ -1197,7 +1760,7 @@ function DTLSSession(options) {
1197
1760
  * merged byte-ranges, rather than requiring each fragment to start exactly
1198
1761
  * where the previous one ended.
1199
1762
  */
1200
- function reassembleFragment(parsed) {
1763
+ function reassembleFragment(parsed, epoch) {
1201
1764
  let key = parsed.msg_seq;
1202
1765
 
1203
1766
  // Ignore fragments for messages we've already delivered.
@@ -1207,14 +1770,28 @@ function DTLSSession(options) {
1207
1770
  // fragment. Reject anything claiming more than our cap BEFORE allocating,
1208
1771
  // so a peer can't force huge buffers with a single unauthenticated packet.
1209
1772
  if (parsed.length > ctx.maxHandshakeMessageSize) {
1210
- ev.emit('error', new Error('DTLS handshake message exceeds maxHandshakeMessageSize (' +
1211
- parsed.length + ' > ' + ctx.maxHandshakeMessageSize + ')'));
1212
- return false;
1773
+ // It was reported and the session carried on, waiting for a message
1774
+ // it had refused. The same alert as TLS over TCP for an oversized
1775
+ // message (illegal_parameter, as OpenSSL).
1776
+ throw parseError('DTLS handshake message of ' + parsed.length + ' bytes, more than ' + ctx.maxHandshakeMessageSize, TLS_ALERT.ILLEGAL_PARAMETER);
1213
1777
  }
1214
1778
 
1215
1779
  let frag = ctx.fragments[key];
1780
+ // Every fragment of one message names the same type and total length
1781
+ // (RFC 6347 §4.2.3, RFC 9147 §5.5). One that disagrees with those
1782
+ // already held was copied in anyway — or, past the buffer, dropped — and
1783
+ // the message never completed: the session waited for good.
1784
+ if (frag && frag.epoch !== epoch) {
1785
+ // Half a message under one key and half under another (RFC 8446
1786
+ // §5.1: handshake messages MUST NOT span key changes).
1787
+ throw parseError('DTLS: message ' + key + ' in fragments of epochs ' + frag.epoch + ' and ' + epoch, TLS_ALERT.UNEXPECTED_MESSAGE);
1788
+ }
1789
+ if (frag && (frag.type !== parsed.type || frag.totalLength !== parsed.length)) {
1790
+ throw parseError('DTLS: fragments of message ' + key + ' disagree on its type or length', TLS_ALERT.ILLEGAL_PARAMETER);
1791
+ }
1216
1792
  if (!frag) {
1217
1793
  frag = ctx.fragments[key] = {
1794
+ epoch: epoch,
1218
1795
  totalLength: parsed.length,
1219
1796
  type: parsed.type,
1220
1797
  buf: new Uint8Array(parsed.length),
@@ -1225,8 +1802,11 @@ function DTLSSession(options) {
1225
1802
  let start = parsed.frag_offset;
1226
1803
  let end = parsed.frag_offset + parsed.frag_length;
1227
1804
 
1228
- // Bounds guard against a malformed fragment claiming to exceed totalLength.
1229
- if (end > frag.totalLength) return false;
1805
+ // A fragment past the message's end (parse_dtls_handshake already holds
1806
+ // the body to frag_length; this is offset + length > the total).
1807
+ if (end > frag.totalLength) {
1808
+ throw parseError('DTLS: fragment ' + start + '+' + parsed.frag_length + ' runs past message length ' + frag.totalLength, TLS_ALERT.ILLEGAL_PARAMETER);
1809
+ }
1230
1810
 
1231
1811
  // Coverage before this fragment — used to decide whether it contributed
1232
1812
  // anything new (a retransmitted fragment that only re-covers known bytes
@@ -1259,10 +1839,13 @@ function DTLSSession(options) {
1259
1839
  }
1260
1840
 
1261
1841
 
1842
+ // A fragment past a hole: one before it is missing (§7.1).
1843
+ if (merged.length > 1 || merged[0][0] !== 0) ctx.ackDisruption = true;
1844
+
1262
1845
  // Complete when a single range covers [0, totalLength).
1263
1846
  if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
1264
1847
  delete ctx.fragments[key];
1265
- return deliverHandshakeMessage(frag.type, frag.buf, key);
1848
+ return deliverHandshakeMessage(frag.type, frag.buf, key, frag.epoch);
1266
1849
  }
1267
1850
  return coveredAfter > coveredBefore;
1268
1851
  }
@@ -1277,12 +1860,19 @@ function DTLSSession(options) {
1277
1860
  * dropped. processHandshakeRecord uses this for the implicit-ACK
1278
1861
  * decision — only genuinely new data may cancel our retransmit timer.
1279
1862
  */
1280
- function deliverHandshakeMessage(type, body, msgSeq) {
1863
+ function deliverHandshakeMessage(type, body, msgSeq, epoch) {
1281
1864
  // Skip if we've already processed this msg_seq (duplicate / retransmit).
1282
1865
  if (msgSeq < ctx.nextReadMsgSeq) return false;
1283
1866
 
1284
1867
  // Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
1285
1868
  if (type === 3 && !isServer) {
1869
+ // Only where one can come (RFC 6347 §4.2.1): unprotected, before any
1870
+ // ServerHello, as the answer to a ClientHello (message_seq 0 or 1).
1871
+ // Anywhere else it is no server's — and it was taken anywhere: one
1872
+ // datagram, after our last flight or after the handshake, made the
1873
+ // client start over (a new ClientHello, transcript and flights wiped).
1874
+ // Dropped (D2).
1875
+ if (epoch !== 0 || tls.context.remote_random != null || msgSeq > 1) return false;
1286
1876
  ctx.nextReadMsgSeq = msgSeq + 1;
1287
1877
  // The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
1288
1878
  // explicit cancel is needed: triggerClientHelloWithCookie arms the
@@ -1327,10 +1917,9 @@ function DTLSSession(options) {
1327
1917
  // 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
1328
1918
  // EXTENSION; the legacy body field is vestigial and must stay empty.
1329
1919
  if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
1330
- tls.sendAlert(2, 47); // fatal, illegal_parameter
1331
- ev.emit('error', new Error(
1920
+ fail(new Error(
1332
1921
  'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
1333
- ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'));
1922
+ ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'), 47);
1334
1923
  return false;
1335
1924
  }
1336
1925
 
@@ -1339,7 +1928,12 @@ function DTLSSession(options) {
1339
1928
  // does return-routability with HelloRetryRequest + the cookie extension
1340
1929
  // instead. The old guard tested ctx.selectedVersion, which is null here,
1341
1930
  // so a DTLS 1.3 handshake could still be answered with an HVR.
1342
- if (options.useCookies === true && !offersDtls13) {
1931
+ // Only for the handshake's first ClientHello (nothing read yet): a
1932
+ // later one with a valid cookie — the attacker's replay of the real
1933
+ // one — reset the read cursor mid-handshake (or after it) and stalled
1934
+ // it without a word (D3). Later, a ClientHello goes the normal way:
1935
+ // in order, to the TLS state machine, which refuses it.
1936
+ if (options.useCookies === true && !offersDtls13 && ctx.nextReadMsgSeq === 0) {
1343
1937
 
1344
1938
  let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
1345
1939
 
@@ -1388,9 +1982,12 @@ function DTLSSession(options) {
1388
1982
  // and wait for the gap to fill. If it's the next expected one, feed it, then
1389
1983
  // drain any consecutive messages that were buffered earlier.
1390
1984
  if (msgSeq > ctx.nextReadMsgSeq) {
1985
+ // Out of order: a message is missing before it — the peer should
1986
+ // learn now what arrived (an ACK, RFC 9147 §7.1).
1987
+ ctx.ackDisruption = true;
1391
1988
  // Out of order — hold it (ignore a duplicate already buffered).
1392
1989
  if (!(msgSeq in ctx.pendingMessages)) {
1393
- ctx.pendingMessages[msgSeq] = { type: type, body: body };
1990
+ ctx.pendingMessages[msgSeq] = { type: type, body: body, epoch: epoch };
1394
1991
  return true; // new information, even if not yet deliverable
1395
1992
  }
1396
1993
  return false; // duplicate of an already-buffered message
@@ -1421,6 +2018,8 @@ function DTLSSession(options) {
1421
2018
  // When the feed does respond, the flight boundary (newFlightPending
1422
2019
  // → startNewFlight) resets the retransmit budget and the outgoing
1423
2020
  // path arms the new flight's timer, superseding this re-arm.
2021
+ if (!epochFitsHandshake(epoch, type)) return false;
2022
+ flightAnswered();
1424
2023
  startRetransmitTimer();
1425
2024
  ctx.newFlightPending = true;
1426
2025
  feedHandshakeToTls(type, body, msgSeq);
@@ -1435,6 +2034,9 @@ function DTLSSession(options) {
1435
2034
  while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
1436
2035
  let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
1437
2036
  delete ctx.pendingMessages[ctx.nextReadMsgSeq];
2037
+ // Held while a message before it was missing — and that message may
2038
+ // have changed the keys (a ServerHello): checked now, not when held.
2039
+ if (!epochFitsHandshake(next.epoch, next.type)) break;
1438
2040
  startRetransmitTimer();
1439
2041
  ctx.newFlightPending = true;
1440
2042
  feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
@@ -1443,11 +2045,60 @@ function DTLSSession(options) {
1443
2045
  return true;
1444
2046
  }
1445
2047
 
2048
+ /**
2049
+ * DTLS 1.3: the epoch a handshake message must arrive in is the one the
2050
+ * handshake is at when it is taken — 0 up to the ServerHello, 2 (the
2051
+ * handshake keys) up to the peer's Finished, 3 and up after it (RFC 9147
2052
+ * §6.1, RFC 8446 §5.1). A message held out of order was checked when it
2053
+ * arrived, before the message ahead of it moved the keys: a plaintext
2054
+ * message buffered behind a missing ServerHello was then taken as if it
2055
+ * had come encrypted (BoGo: a key change with messages buffered).
2056
+ * Unprotected, it may be anyone's, and is dropped (RFC 9147 §4.5.2) — the
2057
+ * real one, protected, takes its place; protected, it is the peer's own
2058
+ * doing: unexpected_message.
2059
+ *
2060
+ * DTLS 1.2 is not checked here: its key change is the peer's
2061
+ * ChangeCipherSpec, which may arrive ahead of the ClientKeyExchange it
2062
+ * depends on, and a protected message cannot be read before it anyway.
2063
+ */
2064
+ function epochFitsHandshake(epoch, type) {
2065
+ if (typeof epoch !== 'number') return true;
2066
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2) {
2067
+ // DTLS 1.2 (RFC 6347 §4.1, RFC 5246 §7.1): Finished is the first
2068
+ // message under the new keys — epoch 1. One in epoch 0 was taken: the
2069
+ // handshake ended with no key change at all, the Finished in the clear
2070
+ // (DTLS-Fuzzer D1; the CCS-injection family). Epoch 1 is the proof of
2071
+ // the change — the record opened under the new keys — not the CCS
2072
+ // record itself, which can be lost while the Finished behind it
2073
+ // arrives (dtls_loss_test: requiring both failed a third of the
2074
+ // handshakes at 15% loss). Messages before it may still come in epoch
2075
+ // 0 after the CCS: reordered, a CertificateVerify can trail it.
2076
+ if (type === 20 && epoch !== 1) {
2077
+ throw parseError('DTLS 1.2: Finished in epoch ' + epoch + ' — it must come protected, after the key change', TLS_ALERT.UNEXPECTED_MESSAGE);
2078
+ }
2079
+ // After the handshake an unprotected handshake message is anyone's — a
2080
+ // HelloVerifyRequest or ClientHello sent to tear the connection down
2081
+ // (D2, D3): dropped. (Renegotiation would come protected, epoch 1.)
2082
+ if (epoch === 0 && ctx.state === 'connected') return false;
2083
+ return true;
2084
+ }
2085
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return true;
2086
+ let hsKeys = !!(ctx.keys[2] && ctx.keys[2].read);
2087
+ let fits = !hsKeys ? epoch === 0 : (ctx.state === 'connected' ? epoch >= 3 : epoch === 2);
2088
+ if (fits) return true;
2089
+ if (epoch === 0) return false;
2090
+ throw parseError('DTLS 1.3: a handshake message in epoch ' + epoch + ', where epoch ' +
2091
+ (!hsKeys ? 0 : (ctx.state === 'connected' ? '3 or later' : 2)) + ' was due', TLS_ALERT.UNEXPECTED_MESSAGE);
2092
+ }
2093
+
1446
2094
  /**
1447
2095
  * Feed one in-order handshake message to the TLSSession, converting from DTLS
1448
2096
  * to TLS wire format and driving version detection.
1449
2097
  */
1450
2098
  function feedHandshakeToTls(type, body, msgSeq) {
2099
+ // DTLS 1.3's own post-handshake messages: the TLS engine has no part in them.
2100
+ if (type === TLS_MESSAGE_TYPE.REQUEST_CONNECTION_ID || type === TLS_MESSAGE_TYPE.NEW_CONNECTION_ID) { takeConnectionIdMessage(type, body); return; }
2101
+
1451
2102
  // Build TLS-format message: type(1) + length(3) + body
1452
2103
  let tlsMsg = build_message(type, body);
1453
2104
 
@@ -1466,6 +2117,54 @@ function DTLSSession(options) {
1466
2117
  }
1467
2118
  }
1468
2119
 
2120
+ /**
2121
+ * RequestConnectionId / NewConnectionId (RFC 9147 §5.8, §9): DTLS 1.3,
2122
+ * after the handshake, and only where CIDs were agreed to — a peer that
2123
+ * did not, or asked for an empty one, "MUST NOT send" them
2124
+ * (unexpected_message otherwise).
2125
+ *
2126
+ * NewConnectionId: CIDs to put in what we send. cid_immediate — the first
2127
+ * of them, from now on (§9: the peer "wants the recipient to use" one of
2128
+ * them at once); cid_spare — there for later: we have no reason to move,
2129
+ * and are not obliged to.
2130
+ * RequestConnectionId: "does not force the receiver to send a
2131
+ * NewConnectionId": we give out one CID, the one agreed, and keep it.
2132
+ * Both are ACKed, as any handshake record.
2133
+ */
2134
+ function takeConnectionIdMessage(type, body) {
2135
+ let unexpected = function (why) { throw parseError(why, TLS_ALERT.UNEXPECTED_MESSAGE); };
2136
+ let malformed = function (why) { throw parseError(why, TLS_ALERT.DECODE_ERROR); };
2137
+ let name = type === TLS_MESSAGE_TYPE.NEW_CONNECTION_ID ? 'NewConnectionId' : 'RequestConnectionId';
2138
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 || ctx.state !== 'connected' || !ctx.cid) unexpected(name + ' without connection IDs agreed to');
2139
+ if (type === TLS_MESSAGE_TYPE.REQUEST_CONNECTION_ID) {
2140
+ if (body.length !== 1) malformed('RequestConnectionId is malformed');
2141
+ if (ctx.cid.local.length === 0) unexpected('RequestConnectionId, and we asked for no CID');
2142
+ return;
2143
+ }
2144
+ if (ctx.cid.peer.length === 0) unexpected('NewConnectionId from a peer that asked for no CID');
2145
+ if (body.length < 3) malformed('NewConnectionId is malformed');
2146
+ let listLen = (body[0] << 8) | body[1];
2147
+ if (2 + listLen + 1 !== body.length) malformed('NewConnectionId is malformed');
2148
+ let cids = [];
2149
+ for (let off = 2; off < 2 + listLen;) {
2150
+ let n = body[off];
2151
+ if (n === 0 || off + 1 + n > 2 + listLen) malformed('NewConnectionId: an empty or truncated CID');
2152
+ cids.push(body.slice(off + 1, off + 1 + n));
2153
+ off += 1 + n;
2154
+ }
2155
+ let usage = body[2 + listLen];
2156
+ if (usage > 1) throw parseError('NewConnectionId: usage ' + usage, TLS_ALERT.ILLEGAL_PARAMETER);
2157
+ if (usage === 0 && cids.length > 0) {
2158
+ ctx.cid = { local: ctx.cid.local, peer: new Uint8Array(cids[0]) };
2159
+ ev.emit('connectionId', getConnectionIds());
2160
+ }
2161
+ }
2162
+
2163
+ // The Connection IDs as they are now: { local, peer }, or null.
2164
+ function getConnectionIds() {
2165
+ return ctx.cid ? { local: new Uint8Array(ctx.cid.local), peer: new Uint8Array(ctx.cid.peer) } : null;
2166
+ }
2167
+
1469
2168
  /**
1470
2169
  * Server: build and send a HelloVerifyRequest with a fresh cookie (DTLS 1.2).
1471
2170
  * The cookie is a CSPRNG value stored on the context; a matching cookie must
@@ -1479,8 +2178,7 @@ function DTLSSession(options) {
1479
2178
 
1480
2179
  // HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
1481
2180
  let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
1482
- let record = buildDtlsPlaintext(CT.HANDSHAKE, 0, nextWriteSeq(0), hvrMsg);
1483
- _ldbgTx(record, 'L1194'); ev.emit('packet', record);
2181
+ queueOut({ kind: 'raw', epoch: 0, contentType: CT.HANDSHAKE, payload: hvrMsg, label: 'hvr' });
1484
2182
  }
1485
2183
 
1486
2184
  /**
@@ -1529,6 +2227,7 @@ function DTLSSession(options) {
1529
2227
  // a wire message sent with seq 1 would fail Finished verification.
1530
2228
  tls.context.message_sent_seq = 1;
1531
2229
  ctx.currentFlight = [];
2230
+ ctx.flightQueued = 0;
1532
2231
  ctx.fragments = {}; // discard any partial reassembly from CH1 flight
1533
2232
  ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
1534
2233
  transcriptMsgSeqs = [];
@@ -1540,11 +2239,9 @@ function DTLSSession(options) {
1540
2239
  tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
1541
2240
  );
1542
2241
 
1543
- // Build DTLS message and send — msg_seq 1 per the RFC figure above.
1544
- let dtlsMsg = build_dtls_handshake(ch2, 1);
1545
- let record = buildRecord(0, CT.HANDSHAKE, dtlsMsg);
1546
- ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
1547
- if (record) { _ldbgTx(record, 'L1258'); ev.emit('packet', record); }
2242
+ // Send — msg_seq 1 per the RFC figure above — as every flight message
2243
+ // goes: fragmented to the MTU, each fragment kept for retransmission.
2244
+ queueFlightMessage(ch2, 1, 0);
1548
2245
 
1549
2246
  tls.context.message_sent_seq = 2;
1550
2247
  startRetransmitTimer();
@@ -1574,17 +2271,50 @@ function DTLSSession(options) {
1574
2271
  // payload never came from the application at all.
1575
2272
  // RFC 9147 §5.8: retransmission is bounded by the handshake; after
1576
2273
  // it, ACKs are the mechanism, not a timer.
1577
- if (ctx.state === 'connected' || ctx.state === 'closed') {
2274
+ if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) {
1578
2275
  cancelRetransmit();
1579
2276
  return;
1580
2277
  }
1581
- for (let i = 0; i < ctx.currentFlight.length; i++) {
1582
- let f = ctx.currentFlight[i];
1583
- let record = buildRecord(f.epoch, f.contentType, f.payload);
1584
- if (record) { _ldbgTx(record, 'L1280'); ev.emit('packet', record); }
2278
+ // DTLS 1.3 (RFC 9147 §7.1): records the peer has ACKed are not resent.
2279
+ ctx.flightResent = true; // no RTT sample from it now (Karn)
2280
+ transmitUnacked(ctx.currentFlight, 'flight-rtx', false);
2281
+ }
2282
+
2283
+ /**
2284
+ * (Re)send the entries of a flight or post-handshake message that are not
2285
+ * ACKed yet — in new records, with new numbers (flushOut). `skipPlaintext`:
2286
+ * not the epoch-0 ones.
2287
+ */
2288
+ function transmitUnacked(entries, label, skipPlaintext) {
2289
+ for (let i = 0; i < entries.length; i++) {
2290
+ let f = entries[i];
2291
+ if (f.acked || (skipPlaintext && f.epoch === 0)) continue;
2292
+ queueOut({ kind: 'entry', entry: f, label: label });
1585
2293
  }
1586
2294
  }
1587
2295
 
2296
+ // A record number (RFC 9147 §4: epoch, sequence number) as one key — the
2297
+ // way sent records and ACKs are matched up. A string: epoch * 2^48 + seq
2298
+ // stops being exact past epoch 31 (2^53), which KeyUpdates reach, and two
2299
+ // records' numbers would collide.
2300
+ function recordNumber(epoch, seq) { return epoch + '/' + seq; }
2301
+
2302
+ // Entries (of a flight, or of a post-handshake message) an ACK covers:
2303
+ // any of the records an entry went out in, its first sending or a
2304
+ // retransmission, is in `got`.
2305
+ // Returns how many it marked now.
2306
+ function markAcked(entries, got) {
2307
+ let newly = 0;
2308
+ for (let i = 0; i < entries.length; i++) {
2309
+ let e = entries[i];
2310
+ if (e.acked || !e.sent) continue;
2311
+ for (let k = 0; k < e.sent.length; k++) {
2312
+ if (got.has(e.sent[k])) { e.acked = true; newly++; break; }
2313
+ }
2314
+ }
2315
+ return newly;
2316
+ }
2317
+
1588
2318
  function startRetransmitTimer() {
1589
2319
  cancelRetransmit();
1590
2320
  // ONE GATE, AT THE SOURCE. Four call sites arm this timer and only
@@ -1593,12 +2323,17 @@ function DTLSSession(options) {
1593
2323
  // flight into the application-data epoch. Refusing to arm once
1594
2324
  // connected (or closed) covers every caller, including any added
1595
2325
  // later, without each having to remember the rule.
1596
- if (ctx.state === 'connected' || ctx.state === 'closed') return;
2326
+ if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
2327
+ if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
1597
2328
  ctx.retransmitTimer = setTimeout(function() {
1598
- if (ctx.retransmitCount >= ctx.maxRetransmits) {
1599
- ev.emit('error', new Error('DTLS handshake timeout — max retransmits exceeded'));
1600
- ctx.state = 'closed';
1601
- ev.emit('close');
2329
+ if (ctx.finalFlightPending && exhausted(ctx.retransmitCount, ctx.flightBegan)) {
2330
+ // The connection is up on our side; stop resending without failing it.
2331
+ finalFlightDone();
2332
+ return;
2333
+ }
2334
+ if (exhausted(ctx.retransmitCount, ctx.flightBegan)) {
2335
+ fail(new Error('DTLS handshake timeout — no answer after ' + ctx.retransmitCount +
2336
+ ' retransmissions in ' + Math.round((Date.now() - ctx.flightBegan) / 1000) + ' s'));
1602
2337
  return;
1603
2338
  }
1604
2339
 
@@ -1613,6 +2348,13 @@ function DTLSSession(options) {
1613
2348
  if (ctx.retransmitTimer.unref) ctx.retransmitTimer.unref();
1614
2349
  }
1615
2350
 
2351
+ // The client's final flight is known to have arrived: stop resending it.
2352
+ function finalFlightDone() {
2353
+ ctx.finalFlightPending = false;
2354
+ cancelRetransmit();
2355
+ startNewFlight();
2356
+ }
2357
+
1616
2358
  function cancelRetransmit() {
1617
2359
  if (ctx.retransmitTimer !== null) {
1618
2360
  clearTimeout(ctx.retransmitTimer);
@@ -1620,10 +2362,54 @@ function DTLSSession(options) {
1620
2362
  }
1621
2363
  }
1622
2364
 
2365
+ // Whether we have a flight out (or on its way out: its messages become
2366
+ // entries of ctx.currentFlight only when packed, at the end of the pass).
2367
+ // Asked of currentFlight alone, the client that had just queued its
2368
+ // Finished found nothing to keep under the timer — and a lost Finished
2369
+ // was never resent.
2370
+ function haveFlight() { return ctx.currentFlight.length > 0 || ctx.flightQueued > 0; }
2371
+
1623
2372
  function startNewFlight() {
1624
2373
  ctx.currentFlight = [];
2374
+ ctx.flightQueued = 0;
1625
2375
  ctx.retransmitCount = 0;
1626
- ctx.retransmitTimeout = 1000;
2376
+ ctx.retransmitTimeout = currentRto();
2377
+ ctx.flightBegan = null;
2378
+ ctx.rttStart = null;
2379
+ ctx.flightResent = false;
2380
+ }
2381
+
2382
+ // Where a timer starts: the initial value until there is a round-trip
2383
+ // sample, then SRTT + 4·RTTVAR (RFC 6298 §2) — not under RTO_MIN_MS, not
2384
+ // over 60 s. An initialRetransmitTimeout given (the option, or
2385
+ // setInitialRetransmitTimeout) is a schedule the caller chose: it is kept
2386
+ // — from it, doubling — and no round trip changes it (fixedRto).
2387
+ //
2388
+ // The floor was 50 ms. A loopback, or a peer that answers within the same
2389
+ // tick, measures a round trip of 0, and a real path that once measured
2390
+ // short then lost its next flight to a 50 ms timer. RFC 9147 sets no
2391
+ // floor; 100 ms is WebRTC's own initial timer. And a fixed schedule
2392
+ // matters to a peer that times itself by one (BoGo advances its clock by
2393
+ // BoringSSL's 400 ms, 800 ms …: an adaptive 50 ms timer fired several
2394
+ // times in each step).
2395
+ function currentRto() {
2396
+ if (ctx.fixedRto || ctx.srtt === null) return ctx.initialRetransmitTimeout;
2397
+ return Math.max(RTO_MIN_MS, Math.min(60000, Math.round(ctx.srtt + 4 * ctx.rttvar)));
2398
+ }
2399
+ function sampleRtt(r) {
2400
+ if (ctx.srtt === null) { ctx.srtt = r; ctx.rttvar = r / 2; }
2401
+ else { ctx.rttvar = 0.75 * ctx.rttvar + 0.25 * Math.abs(ctx.srtt - r); ctx.srtt = 0.875 * ctx.srtt + 0.125 * r; }
2402
+ }
2403
+ // The peer's answer to our flight has begun (a new message of its next
2404
+ // flight, or — DTLS 1.3 — an ACK of a protected record of ours).
2405
+ function flightAnswered() {
2406
+ if (ctx.rttStart !== null && !ctx.flightResent) sampleRtt(Date.now() - ctx.rttStart);
2407
+ ctx.rttStart = null;
2408
+ }
2409
+ // Time to give up on what keeps going unanswered: tried enough, and long
2410
+ // enough (maxRetransmits, giveUpAfter).
2411
+ function exhausted(attempts, began) {
2412
+ return attempts >= ctx.maxRetransmits && began !== null && Date.now() - began >= ctx.giveUpAfter;
1627
2413
  }
1628
2414
 
1629
2415
 
@@ -1631,25 +2417,81 @@ function DTLSSession(options) {
1631
2417
  // ACK (DTLS 1.3)
1632
2418
  // ============================================================
1633
2419
 
1634
- function processAck(content) {
1635
- let acks = parseDtlsAck(content);
1636
-
1637
- // An outstanding KeyUpdate is what this ACK is for: §8 forbids protecting
1638
- // anything with the new keys until it arrives, so THIS is the moment the
1639
- // new epoch becomes live. Keys were installed when the KeyUpdate was sent;
1640
- // only the switch happens here.
1641
- if (ctx.pendingWriteEpoch !== null) {
1642
- ctx.writeEpoch = ctx.pendingWriteEpoch;
1643
- ctx.pendingWriteEpoch = null;
1644
- cancelKeyUpdateRetransmit();
1645
- ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
1646
- return;
2420
+ function processAck(acks) {
2421
+ let got = new Set();
2422
+ for (let i = 0; i < acks.length; i++) got.add(recordNumber(acks[i].epoch, acks[i].seq));
2423
+
2424
+ // Post-handshake messages first — a KeyUpdate's epoch switch among them.
2425
+ if (ctx.postHandshake.length > 0) ackPostHandshake(got);
2426
+ if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
2427
+
2428
+ // During the handshake an ACK may cover only PART of our flight (RFC 9147
2429
+ // §7.1): the peer lists the records it received. Those are done; the
2430
+ // rest stay under the retransmit timer and are what the next
2431
+ // retransmission resends. Treating any ACK as acknowledging the whole
2432
+ // flight cancelled the timer while a record was still missing — the
2433
+ // peer, waiting for it, ACKed again and again, and the handshake hung
2434
+ // (≈ half of all handshakes at 5% loss, when the lost record was inside
2435
+ // the server's flight).
2436
+ if ((ctx.state !== 'connected' || ctx.finalFlightPending) && ctx.currentFlight.length > 0) {
2437
+ let newly = markAcked(ctx.currentFlight, got);
2438
+ if (newly > 0) flightAnswered();
2439
+ let remaining = 0, protectedEntries = 0, protectedAcked = 0;
2440
+ for (let i = 0; i < ctx.currentFlight.length; i++) {
2441
+ let f = ctx.currentFlight[i];
2442
+ // Plaintext epoch-0 records (ClientHello, ServerHello) are never
2443
+ // ACKed — the peer acknowledges them implicitly by answering — so
2444
+ // they do not keep the flight open on their own.
2445
+ if (f.epoch === 0) continue;
2446
+ protectedEntries++;
2447
+ if (f.acked) protectedAcked++; else remaining++;
2448
+ }
2449
+ // A flight of plaintext alone (a ClientHello) is answered, never
2450
+ // ACKed: an ACK — an empty one, from a peer that could not read
2451
+ // something of ours — confirms none of it, and ended its
2452
+ // retransmission (the timer cancelled below), the ClientHello lost
2453
+ // for good.
2454
+ if (protectedEntries === 0) return;
2455
+ if (remaining > 0) {
2456
+ // RFC 9147 §7.2: "Upon receipt of an ACK that leaves it with only
2457
+ // some messages from a flight having been acknowledged, an
2458
+ // implementation SHOULD retransmit the unacknowledged messages or
2459
+ // fragments." The peer ACKs a partial flight when it sees a gap, or
2460
+ // after a quarter of its timer with the rest still missing — either
2461
+ // way the rest is lost, and waiting for our own timer (doubled by
2462
+ // every earlier loss: 8 s and more) stalled the handshake past its
2463
+ // deadline (dtls_loss_test, 30% loss).
2464
+ //
2465
+ // At once when this ACK confirmed something new. One that confirms
2466
+ // nothing new — a repeat, or an empty one (wolfSSL sends one for
2467
+ // each record it cannot read: with our ServerHello lost, all of
2468
+ // them) — at most once a quarter of the initial timer: resending on
2469
+ // each, both sides fed each other 15 000–34 000 datagrams a second.
2470
+ // Epoch-0 records go too while the peer has ACKed none of the
2471
+ // protected ones — it may lack our ServerHello, and so the keys;
2472
+ // once it has ACKed one, it has them.
2473
+ let now = Date.now();
2474
+ if (newly > 0 || now - ctx.lastAckResend >= ackDelay(currentRto())) {
2475
+ ctx.lastAckResend = now;
2476
+ ctx.flightResent = true;
2477
+ transmitUnacked(ctx.currentFlight, 'partial-ack', protectedAcked > 0);
2478
+ // The records just resent start their wait again: the timer, left
2479
+ // as it was, fired soon after and sent the same records a second
2480
+ // time — wasted on a real path, and packed with the first copy
2481
+ // where both go out in one pass (BoGo DTLS-Retransmit-Server-
2482
+ // ACKMatchingEpoch-TLS13: "excess data"). Its value is kept: this
2483
+ // was no timeout.
2484
+ startRetransmitTimer();
2485
+ }
2486
+ return;
2487
+ }
2488
+ if (newly === 0 && !ctx.finalFlightPending) return; // nothing this ACK confirmed
1647
2489
  }
1648
2490
 
1649
- // Everything else keeps the original behaviour verbatim. In particular the
1650
- // ACK for the client's final flight (which RFC 9147 §7.1 requires the peer
1651
- // to send explicitly, and which arrives at the application epoch) must
1652
- // still cancel that flight's retransmit timer.
2491
+ // The whole flight is acknowledged — or, the ACK for the client's final
2492
+ // flight (which RFC 9147 §7.1 requires the peer to send explicitly, and
2493
+ // which arrives at the application epoch): cancel the retransmit timer.
2494
+ ctx.finalFlightPending = false;
1653
2495
  cancelRetransmit();
1654
2496
  startNewFlight();
1655
2497
  }
@@ -1677,8 +2519,7 @@ function DTLSSession(options) {
1677
2519
  if (!postHandshake && (ctx.state === 'connected' || ctx.state === 'closed')) return;
1678
2520
  if (ctx.state === 'closed') return;
1679
2521
  let payload = buildDtlsAck(recordsToAck);
1680
- let record = buildRecord(epoch, CT.ACK, payload);
1681
- _ldbgTx(record, 'L1334'); ev.emit('packet', record);
2522
+ queueOut({ kind: 'raw', epoch: epoch, contentType: CT.ACK, payload: payload, label: 'ack' });
1682
2523
  }
1683
2524
 
1684
2525
 
@@ -1696,12 +2537,33 @@ function DTLSSession(options) {
1696
2537
  ev.emit('clienthello', data, message);
1697
2538
  });
1698
2539
 
2540
+ // Agreed (the hello), or moved by the peer's NewConnectionId: the record
2541
+ // layer's from now on, and the application's to know.
2542
+ tls.on('connectionId', function(ids) { ctx.cid = ids; ev.emit('connectionId', getConnectionIds()); });
2543
+
1699
2544
  tls.on('secureConnect', function() {
1700
2545
  _ldbg('handshake complete — secureConnect (version=0x' +
1701
2546
  ((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
1702
2547
  ctx.state = 'connected';
1703
- cancelRetransmit();
1704
- startNewFlight();
2548
+ // DTLS 1.3 client (RFC 9147 §5.8.3, §7.1): our final flight (…Finished)
2549
+ // is answered by nothing but an ACK, so it must stay under the
2550
+ // retransmit timer until that ACK — or anything else from the peer at
2551
+ // the application epoch — shows it arrived. Dropping it here meant one
2552
+ // lost Finished left us "connected" and the peer waiting for good.
2553
+ if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && haveFlight()) {
2554
+ ctx.finalFlightPending = true;
2555
+ startRetransmitTimer();
2556
+ } else {
2557
+ // DTLS 1.2: if the last flight is ours — sent after the peer's last
2558
+ // message, so nothing of the peer's acknowledges it — it is kept for
2559
+ // the peer's retransmissions (dtls12AnswerRetransmission). No timer:
2560
+ // RFC 6347 §4.2.4's FINISHED state answers, it does not repeat.
2561
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && !ctx.newFlightPending && haveFlight()) {
2562
+ ctx.lastFlight12 = ctx.currentFlight;
2563
+ }
2564
+ cancelRetransmit();
2565
+ startNewFlight();
2566
+ }
1705
2567
  ev.emit('connect');
1706
2568
  });
1707
2569
 
@@ -1711,12 +2573,14 @@ function DTLSSession(options) {
1711
2573
  // only symptom is a connection that dies after a handshake that
1712
2574
  // looked complete, which is exactly the DTLS 1.3 interop case.
1713
2575
  _ldbg('ERROR ' + (e && e.message ? e.message : e) +
1714
- (e && e.alertDescription != null ? ' alertDesc=' + e.alertDescription : '') +
2576
+ (e && e.alertDesc != null ? ' alertDesc=' + e.alertDesc : '') +
1715
2577
  (e && e.code ? ' code=' + e.code : ''));
1716
- ev.emit('error', e);
2578
+ // TLSSession has sent its alert already.
2579
+ fail(e);
1717
2580
  });
1718
2581
 
1719
2582
  tls.on('session', function(ticket) {
2583
+ ctx.lastSession = ticket;
1720
2584
  ev.emit('session', ticket);
1721
2585
  });
1722
2586
 
@@ -1731,12 +2595,20 @@ function DTLSSession(options) {
1731
2595
  return;
1732
2596
  }
1733
2597
  if (typeof data === 'string') data = new TextEncoder().encode(data);
2598
+ // One send, one record — a datagram's message is not split. Larger than
2599
+ // a record may carry (2^14, or the peer's record_size_limit): refused,
2600
+ // not sent as a record the peer must refuse.
2601
+ if (data.length > tls.recordSendLimit()) {
2602
+ let big = new RangeError('DTLS: ' + data.length + ' bytes in one record, more than ' + tls.recordSendLimit() + ' (the record limit)');
2603
+ big.code = 'ERR_DTLS_RECORD_TOO_LARGE';
2604
+ ev.emit('error', big);
2605
+ return;
2606
+ }
1734
2607
 
1735
2608
  let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
1736
- ? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
2609
+ ? appEpoch()
1737
2610
  : 1;
1738
- let record = buildRecord(epoch, CT.APPLICATION_DATA, data);
1739
- _ldbgTx(record, 'L1383'); ev.emit('packet', record);
2611
+ queueOut({ kind: 'raw', epoch: epoch, contentType: CT.APPLICATION_DATA, payload: data, label: 'data' });
1740
2612
  }
1741
2613
 
1742
2614
 
@@ -1753,23 +2625,10 @@ function DTLSSession(options) {
1753
2625
  // the peer has switched to reading protected records, and a plaintext
1754
2626
  // close_notify arrives with a classic DTLSPlaintext header where the peer
1755
2627
  // requires the RFC 9147 §4 unified header. It then reports a bad record
1756
- // type instead of a clean shutdown. Mirror the ACK path, which already
1757
- // picks the highest epoch we hold write keys for.
1758
- let epoch;
1759
- if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
1760
- // After a KeyUpdate the live epoch is 4, 5, ... — the peer has moved on
1761
- // and no longer reads epoch 3, so a close_notify sent there is dropped
1762
- // and the shutdown looks like a timeout instead of a clean close.
1763
- epoch = (ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch])
1764
- ? ctx.writeEpoch
1765
- : (ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0));
1766
- } else {
1767
- epoch = ctx.keys[1] ? 1 : 0;
1768
- }
1769
- sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
1770
- ctx.state = 'closed';
1771
- cancelRetransmit();
1772
- cancelKeyUpdateRetransmit();
2628
+ // type instead of a clean shutdown. alertEpoch() decides, as for every
2629
+ // alert we send.
2630
+ sendAlertRecord(alertEpoch(), new Uint8Array([1, 0])); // warning, close_notify
2631
+ shutDown();
1773
2632
  ev.emit('close');
1774
2633
  }
1775
2634
 
@@ -1876,6 +2735,15 @@ function DTLSSession(options) {
1876
2735
 
1877
2736
  /** Negotiated ALPN. */
1878
2737
  getALPN: function() { return tls.getALPN(); },
2738
+ // Encrypted Client Hello, as TLSSocket's echStatus / echBackend.
2739
+ getEchStatus: function() { return tls.getEchStatus(); },
2740
+ isEchBackend: function() { return tls.isEchBackend(); },
2741
+ // External PSK: the identity this connection was authenticated with.
2742
+ getPskIdentity: function() { return tls.getPskIdentity(); },
2743
+ /** Connection IDs (RFC 9146), once agreed: { local, peer } — local, in
2744
+ * the records we receive; peer, in those we send (NewConnectionId may
2745
+ * have changed it since the handshake). Null otherwise. */
2746
+ getConnectionIds: getConnectionIds,
1879
2747
 
1880
2748
  /** Peer certificate. */
1881
2749
  getPeerCertificate: function() { return tls.getPeerCertificate(); },
@@ -1897,6 +2765,31 @@ function DTLSSession(options) {
1897
2765
 
1898
2766
  /** Whether this connection resumed an earlier session. */
1899
2767
  isSessionReused: function() { return !!tls.isResumed; },
2768
+
2769
+ /** The last session (a Buffer for `session` next time), as TLSSocket#getSession; undefined until one. */
2770
+ getSession: function() { return ctx.lastSession; },
2771
+
2772
+ /**
2773
+ * The path MTU: the largest datagram this session writes (every record
2774
+ * is one datagram). Takes effect for the next message fragmented —
2775
+ * a flight already sent is resent as it was cut. 128..65507 bytes.
2776
+ */
2777
+ setMTU: function(mtu) { ctx.mtu = checkedMtu(mtu); },
2778
+ getMTU: function() { return ctx.mtu; },
2779
+
2780
+ /**
2781
+ * Where each flight's retransmission timer starts (it doubles on every
2782
+ * timeout, to 60 s). RFC 9147 §5.8.2 recommends 1 s; a known short
2783
+ * path can go lower. 10..60000 ms. The flight in progress keeps its own.
2784
+ */
2785
+ setInitialRetransmitTimeout: function(ms) {
2786
+ ctx.initialRetransmitTimeout = checkedRetransmitTimeout(ms);
2787
+ // Said explicitly: the schedule from now on — every flight's timer
2788
+ // starts there, whatever round trip is measured (currentRto).
2789
+ ctx.fixedRto = true;
2790
+ ctx.srtt = null; ctx.rttvar = null;
2791
+ if (ctx.retransmitTimer === null) ctx.retransmitTimeout = ctx.initialRetransmitTimeout;
2792
+ },
1900
2793
  };
1901
2794
 
1902
2795
  for (let k in api) {
@@ -1912,4 +2805,5 @@ function DTLSSession(options) {
1912
2805
  return this;
1913
2806
  }
1914
2807
 
1915
- export default DTLSSession;
2808
+ export default DTLSSession;
2809
+ export { SERVER_CID_LEN };