lemon-tls 0.10.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,17 +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
294
366
  finalFlightPending: false, // DTLS 1.3 client: final flight not yet confirmed (see secureConnect)
295
367
 
296
368
  retransmitTimer: null,
297
369
  retransmitCount: 0,
298
- 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.
299
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
300
389
 
301
390
  // DTLS 1.2: CCS tracking
302
391
  localCcsSent: false,
303
- remoteCcsSeen: false,
304
392
 
305
393
  // HelloVerifyRequest (DTLS 1.2 server)
306
394
  hvrCookie: null, // cookie sent in HVR
@@ -313,58 +401,18 @@ function DTLSSession(options) {
313
401
  // ============================================================
314
402
 
315
403
  // Set DTLS versions and cookie (triggers DTLS format in build_hello)
316
- let versions = [];
317
- let minVer = options.minVersion || 'DTLSv1.2';
318
- let maxVer = options.maxVersion || 'DTLSv1.3';
319
- if (maxVer === 'DTLSv1.3' || maxVer === 'TLSv1.3') versions.push(DTLS_VERSION.DTLS1_3);
320
- if (minVer === 'DTLSv1.2' || minVer === 'TLSv1.2' || maxVer === 'DTLSv1.2') versions.push(DTLS_VERSION.DTLS1_2);
321
- if (versions.length === 0) versions = [DTLS_VERSION.DTLS1_3, DTLS_VERSION.DTLS1_2];
322
-
323
- let tlsSetup = {
324
- 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), {
325
414
  dtls_cookie: new Uint8Array(0), // empty cookie — triggers DTLS format
326
- };
327
-
328
- // Pass through cipher/group/alpn preferences
329
- if (options.cipherSuites) tlsSetup.local_supported_cipher_suites = options.cipherSuites;
330
-
331
- // Client cipher defaults: TLSSession auto-populates both TLS 1.3+1.2 ciphers,
332
- // but if we only support DTLS 1.2, we must not offer TLS 1.3 ciphers
333
- // (the server might erroneously select one for a 1.2 connection).
334
- if (!isServer && !options.cipherSuites && versions.indexOf(DTLS_VERSION.DTLS1_3) < 0) {
335
- tlsSetup.local_supported_cipher_suites = [0xC02F, 0xC030, 0xC02B, 0xC02C, 0xCCA8];
336
- }
337
- if (options.groups) tlsSetup.local_supported_groups = options.groups;
338
- if (options.alpnProtocols) tlsSetup.local_supported_alpns = options.alpnProtocols;
339
-
340
- // Default key-exchange groups, BOTH roles. With DTLS 1.3 offered: the
341
- // hybrid post-quantum group first (X25519MLKEM768, when node:crypto has
342
- // ML-KEM — see ecdh.js), then the classical ones. In WebRTC either peer can
343
- // end up the DTLS client, and harvest-now-decrypt-later applies to media
344
- // too (Chrome rolls PQ DTLS out from 149). A client leading with the hybrid
345
- // group sends a classical share beside it (tls_session), so a classical
346
- // server costs nothing extra. DTLS 1.2 only: classical — a KEM does not fit
347
- // its key exchange, and the server skips KEM groups there anyway.
348
- if (!tlsSetup.local_supported_groups) {
349
- // (ECDH_SUPPORTED_GROUPS leads with the hybrid too now; tls_session leaves
350
- // a KEM group out of a hello that does not offer 1.3.)
351
- tlsSetup.local_supported_groups = (versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0
352
- ? SERVER_DEFAULT_GROUPS : ECDH_SUPPORTED_GROUPS).slice();
353
- }
354
-
355
- // Server defaults — TLSSession client auto-populates these, but server expects them set externally
356
- if (isServer) {
357
- if (!tlsSetup.local_supported_cipher_suites) {
358
- // crypto.js owns the default suite list (see default_cipher_suites).
359
- tlsSetup.local_supported_cipher_suites = default_cipher_suites(
360
- versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
361
- versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
362
- );
363
- }
364
- // (key-exchange groups: set above, for both roles)
365
- // signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
366
- tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
367
- }
415
+ });
368
416
 
369
417
  // Server cert/key.
370
418
  //
@@ -373,13 +421,11 @@ function DTLSSession(options) {
373
421
  // pfx and ocsp, and picking out two fields silently dropped all of them —
374
422
  // an encrypted private key could never be decrypted here no matter what the
375
423
  // caller passed. Same defect the TLS paths carried.
376
- if (options.cert && options.key) {
377
- let sctx = createSecureContext(options);
378
- tlsSetup.local_cert_chain = sctx.certificateChain;
379
- tlsSetup.cert_private_key = sctx.privateKey;
380
- }
381
-
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.
382
427
  tls.set_context(tlsSetup);
428
+ if (options.cert && options.key) tls.setKeyCert(createSecureContext(options));
383
429
 
384
430
 
385
431
  // ============================================================
@@ -595,24 +641,22 @@ function DTLSSession(options) {
595
641
  if (ctx.writeEpoch === null || ctx.readEpoch === null) return;
596
642
 
597
643
  if (info.direction === 'send') {
598
- // §8: only one KeyUpdate may be in flight. requestKeyUpdate() below
599
- // refuses up front; reaching here with one pending means the TLS session
600
- // rotated its secret anyway (e.g. an auto-response to the peer's
601
- // update_requested while ours was still unacknowledged), which would
602
- // 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.
603
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
+ }
604
655
  ev.emit('error', new Error(
605
656
  'DTLS: a KeyUpdate is already awaiting acknowledgement (RFC 9147 §8)'));
606
657
  return;
607
658
  }
608
- let next = ctx.writeEpoch + 1;
609
- // Install the new write keys, but keep protecting with the old epoch
610
- // until the peer ACKs (§8). ctx.keys[next] may already carry a .read
611
- // half from the peer's own update — merge, never overwrite.
612
- ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(info.secret) });
613
- ctx.writeSeq[next] = 0;
614
- ctx.pendingWriteEpoch = next;
615
-
659
+ installWriteUpdate(info.secret);
616
660
  } else if (info.direction === 'receive') {
617
661
  let next = ctx.readEpoch + 1;
618
662
  ctx.keys[next] = Object.assign({}, ctx.keys[next], { read: deriveEpochKeys(info.secret) });
@@ -623,40 +667,259 @@ function DTLSSession(options) {
623
667
  }
624
668
  });
625
669
 
626
- /** Arm the post-handshake retransmit for an unacknowledged KeyUpdate. */
627
- function armKeyUpdateRetransmit(payload, epoch) {
628
- cancelKeyUpdateRetransmit();
629
- ctx.keyUpdateFlight = {
630
- payload: payload, epoch: epoch,
631
- 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);
754
+ };
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();
632
759
  };
633
- scheduleKeyUpdateRetransmit();
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
+ }
634
850
  }
635
851
 
636
- function scheduleKeyUpdateRetransmit() {
637
- let f = ctx.keyUpdateFlight;
638
- if (!f) return;
639
- f.timer = setTimeout(function () {
640
- if (!ctx.keyUpdateFlight) return;
641
- if (f.attempts >= ctx.maxRetransmits) {
642
- ev.emit('error', new Error('DTLS: KeyUpdate was never acknowledged'));
643
- 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'));
644
861
  return;
645
862
  }
646
- f.attempts++;
647
- // Rebuild rather than replay: a retransmission is a NEW record and needs
648
- // a fresh sequence number, or the peer's replay window rejects it.
649
- let record = buildRecord(f.epoch, CT.HANDSHAKE, f.payload);
650
- if (record) { _ldbgTx(record, 'keyupdate-rtx'); ev.emit('packet', record); }
651
- f.timeout = Math.min(f.timeout * 2, 60000);
652
- scheduleKeyUpdateRetransmit();
653
- }, f.timeout);
654
- 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();
886
+ }
887
+
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;
655
895
  }
656
896
 
657
- function cancelKeyUpdateRetransmit() {
658
- if (ctx.keyUpdateFlight && ctx.keyUpdateFlight.timer) clearTimeout(ctx.keyUpdateFlight.timer);
659
- ctx.keyUpdateFlight = null;
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
+ }
660
923
  }
661
924
 
662
925
 
@@ -689,28 +952,25 @@ function DTLSSession(options) {
689
952
  // epoch, which is 3 until a KeyUpdate moves it to 4, 5, ... Hardcoding 3
690
953
  // meant every post-handshake message stayed on the pre-rekey epoch.
691
954
  dtlsEpoch = (tlsEpoch === 1) ? 2
692
- : (ctx.writeEpoch !== null ? ctx.writeEpoch : 3);
955
+ : appEpoch();
693
956
  } else {
694
957
  dtlsEpoch = 1; // DTLS 1.2: epoch 1 for all encrypted
695
958
  }
696
959
 
697
960
  if (type === 'alert') {
698
- 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);
699
969
  return;
700
970
  }
701
971
 
702
- // DTLS 1.2: derive keys before first encrypted message
703
- if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1 && !ctx.keys[1]) {
704
- let ts = tls.getTrafficSecrets();
705
- if (ts.masterSecret) {
706
- let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
707
- ctx.keys[1] = {
708
- write: { key: d12.writeKey, iv: d12.writeIv },
709
- read: { key: d12.readKey, iv: d12.readIv },
710
- };
711
- ctx.writeSeq[1] = 0;
712
- }
713
- }
972
+ // DTLS 1.2: keys before the first encrypted message
973
+ if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1) installKeys12();
714
974
 
715
975
  // Flight boundary: first outgoing record after accepting peer data
716
976
  // starts a fresh flight (clears the previous flight's records and
@@ -725,50 +985,32 @@ function DTLSSession(options) {
725
985
  sendCCS();
726
986
  }
727
987
 
728
- // Convert TLS handshake message → DTLS handshake message (add reconstruction header)
729
- let dtlsMsg = build_dtls_handshake(data, seq);
730
-
731
- // Fragment if needed
732
- let frags = fragmentMessage(dtlsMsg, data, seq);
733
-
734
- // Build DTLS records and emit
988
+ // Out as a DTLS handshake message — fragmented as it is packed (flushOut).
735
989
  // RFC 9147 §5.8.4: post-handshake messages do NOT belong to the handshake
736
990
  // flight. Each category gets an independent state machine; folding a
737
991
  // KeyUpdate into currentFlight would make a handshake retransmission
738
992
  // replay it, and a KeyUpdate ACK cancel the handshake's timer.
739
- let isPostHandshakeKU = (type === 'key_update' &&
740
- ctx.selectedVersion === DTLS_VERSION.DTLS1_3);
741
- if (isPostHandshakeKU) {
742
- for (let i = 0; i < frags.length; i++) {
743
- let kuRecord = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
744
- 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;
745
1008
  }
746
- // KeyUpdate must be acknowledged (§8), so it is retransmitted on its own
747
- // timer until an ACK arrives. Only the last fragment is tracked; a
748
- // KeyUpdate body is 1 byte and never fragments in practice.
749
- armKeyUpdateRetransmit(frags[frags.length - 1], dtlsEpoch);
1009
+ sendPostHandshake(type, data, seq, dtlsEpoch);
750
1010
  return;
751
1011
  }
752
1012
 
753
- for (let i = 0; i < frags.length; i++) {
754
- // The flight captures the fragment, not the record — see currentFlight.
755
- let entry = { epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] };
756
- let record = buildFlightRecord(entry);
757
- ctx.currentFlight.push(entry);
758
- if (record) {
759
- _ldbgTx(record, 'L543'); ev.emit('packet', record);
760
- } else {
761
- // buildRecord refused to build (no keys for an encrypted epoch).
762
- // Dropping the record here without a word is how a key-epoch mismatch
763
- // turns into an unexplained peer timeout instead of a diagnosable
764
- // failure: nothing reaches the wire and nothing is reported. Surface
765
- // it — the handshake cannot proceed either way.
766
- ev.emit('error', new Error(
767
- 'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
768
- ' — no keys installed for that epoch'));
769
- return;
770
- }
771
- }
1013
+ queueFlightMessage(data, seq, dtlsEpoch);
772
1014
 
773
1015
  // Start retransmit timer after sending flight messages.
774
1016
  //
@@ -817,67 +1059,151 @@ function DTLSSession(options) {
817
1059
 
818
1060
  if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
819
1061
  // DTLS 1.3 encrypted (unified header)
820
- return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write);
1062
+ return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write, peerCid());
821
1063
  } else {
822
1064
  // DTLS 1.2 encrypted (classic header)
823
- return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write);
1065
+ return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write, peerCid());
824
1066
  }
825
1067
  }
826
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
+
827
1078
  function nextWriteSeq(epoch) {
828
1079
  if (!(epoch in ctx.writeSeq)) ctx.writeSeq[epoch] = 0;
829
1080
  return ctx.writeSeq[epoch]++;
830
1081
  }
831
1082
 
832
1083
  /**
833
- * Fragment a DTLS handshake message if it exceeds MTU.
834
- * 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.
1088
+ */
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.
835
1183
  */
836
- function fragmentMessage(dtlsMsg, tlsMsg, msgSeq) {
837
- // Record-layer overhead we must leave room for inside one datagram/MTU:
838
- // DTLS 1.2: 13-byte classic record header + 16-byte AEAD tag (+8 explicit
839
- // nonce for GCM, but that lives inside the encrypted payload the
840
- // record layer builds, so we keep a small safety margin).
841
- // DTLS 1.3: 5-byte unified header (2-byte seq + length) + 16-byte AEAD tag.
842
- // Using the correct, smaller 1.3 overhead avoids over-fragmenting.
843
- let isDtls13 = ctx.selectedVersion === DTLS_VERSION.DTLS1_3;
844
- let recordOverhead = isDtls13 ? (5 + 16) : (13 + 16 + 8);
845
- let maxFragment = ctx.mtu - recordOverhead;
846
-
847
- // The DTLS handshake message header is 12 bytes; the body is what we chunk.
848
- const HS_HDR = 12;
849
- if (dtlsMsg.length <= maxFragment) return [dtlsMsg];
850
-
851
- // Need to fragment: split the TLS body into chunks
852
- let body = tlsMsg.subarray(4); // skip TLS header (type+length)
853
- let totalLength = body.length;
854
- let fragments = [];
855
- let offset = 0;
856
-
857
- // Each fragment carries its own 12-byte handshake header, so the payload
858
- // budget per fragment is maxFragment - HS_HDR.
859
- let maxChunk = Math.max(1, maxFragment - HS_HDR);
860
- while (offset < totalLength) {
861
- let chunkLen = Math.min(maxChunk, totalLength - offset);
862
- let frag = build_dtls_handshake(tlsMsg, msgSeq, offset, chunkLen);
863
- fragments.push(frag);
864
- offset += chunkLen;
865
- }
866
-
867
- return fragments;
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;
868
1191
  }
869
1192
 
870
1193
  /**
871
1194
  * Send an alert record.
872
1195
  */
873
1196
  function sendAlertRecord(epoch, alertData) {
1197
+ // What is queued goes first, the alert in a datagram of its own (see
1198
+ // flushOut).
1199
+ flushOut();
874
1200
  let record = buildRecord(epoch, CT.ALERT, alertData);
875
1201
  // buildRecord returns null when it refuses to emit a malformed record
876
1202
  // (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
877
1203
  // packet pushes `null` into the transport; drop it instead — we already
878
1204
  // surfaced the reason through 'error'.
879
1205
  if (!record) return;
880
- _ldbgTx(record, 'L664'); ev.emit('packet', record);
1206
+ _ldbgTx(record, 'alert'); ev.emit('packet', record);
881
1207
  }
882
1208
 
883
1209
  /**
@@ -892,7 +1218,6 @@ function DTLSSession(options) {
892
1218
  ctx.newFlightPending = false;
893
1219
  startNewFlight();
894
1220
  }
895
- let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
896
1221
  // RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
897
1222
  // when the flight is retransmitted. Captured as a descriptor (epoch 0,
898
1223
  // plaintext), so resendFlight rebuilds it with a fresh seq like every
@@ -900,8 +1225,16 @@ function DTLSSession(options) {
900
1225
  // retransmitted final flight arrived WITHOUT the cipher-spec change and
901
1226
  // the peer could never decrypt the accompanying Finished — an
902
1227
  // unrecoverable stall under single-datagram loss.
903
- ctx.currentFlight.push({ epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]) });
904
- _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 });
905
1238
  }
906
1239
 
907
1240
 
@@ -909,7 +1242,67 @@ function DTLSSession(options) {
909
1242
  // Incoming: UDP datagram → parse → feed TLSSession
910
1243
  // ============================================================
911
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.
912
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) {
913
1306
  if (ctx.state === 'closed') return;
914
1307
  if (ctx.state === 'idle') ctx.state = 'handshaking';
915
1308
 
@@ -921,7 +1314,7 @@ function DTLSSession(options) {
921
1314
  }
922
1315
  }
923
1316
 
924
- let records = parseDtlsDatagram(data, keysByEpoch);
1317
+ let records = parseDtlsDatagram(data, keysByEpoch, localCid(), localCidRequired());
925
1318
 
926
1319
  if (LEMON_DEBUG) {
927
1320
  var _summ = [];
@@ -935,9 +1328,9 @@ function DTLSSession(options) {
935
1328
  }
936
1329
 
937
1330
  // RFC 9147 §7: collect the record numbers of handshake records we received
938
- // in this datagram so we can ACK them once, after processing. Only for
939
- // DTLS 1.3, and only while we're still handshaking (post-handshake ACKs of
940
- // 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).
941
1334
  let ackable = [];
942
1335
 
943
1336
  // Whether these records arrived AFTER the handshake must be decided here,
@@ -948,8 +1341,27 @@ function DTLSSession(options) {
948
1341
  // the spurious epoch-3 ACK that makes a peer answer decode_error on the
949
1342
  // NEXT record — so a perfectly good SCTP COOKIE_ECHO takes the blame.
950
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;
951
1349
 
952
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;
953
1365
  // A record we could not decrypt when the datagram was parsed. Keys may
954
1366
  // have arrived since — processing an earlier record in THIS datagram
955
1367
  // (the ServerHello) is exactly what installs them. Retry now, in order,
@@ -960,7 +1372,9 @@ function DTLSSession(options) {
960
1372
  let readKeys = epochKeys ? epochKeys.read : null;
961
1373
  if (!readKeys) continue; // still unreadable — let the peer retransmit
962
1374
  let dec = null;
963
- 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; }
964
1378
  if (!dec) continue;
965
1379
  records[i] = {
966
1380
  type: dec.type, epoch: dec.epoch, seq: dec.seq,
@@ -984,65 +1398,122 @@ function DTLSSession(options) {
984
1398
  ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
985
1399
  }
986
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
+ }
987
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();
988
1409
 
989
- // After CCS, keys are newly available — re-decrypt remaining epoch>0 records
990
- 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]) {
991
1413
  let readKeys = ctx.keys[1].read;
992
1414
  for (let j = i + 1; j < records.length; j++) {
993
- if (!records[j].encrypted && records[j].epoch > 0 && readKeys) {
994
- try {
995
- records[j].content = decryptDtls12(records[j].content, readKeys.key, readKeys.iv, records[j].epoch, records[j].seq, records[j].type);
996
- records[j].encrypted = true;
997
- } catch(e) {
998
- }
999
- }
1415
+ if (!records[j].encrypted && records[j].epoch > 0 && readKeys) open12(records[j], readKeys);
1000
1416
  }
1001
1417
  }
1002
1418
  }
1003
1419
 
1004
- // Send a single ACK covering all handshake records received in this datagram.
1005
- // We ACK from the highest epoch for which we already have write keys (so the
1006
- // ACK itself is protected once handshake keys exist; epoch 0 before that).
1007
- if (ackable.length > 0) {
1008
- // ACKs RIDE THE HANDSHAKE EPOCH (RFC 9147 §7): they acknowledge
1009
- // handshake records, so epoch 2 once handshake keys exist and
1010
- // epoch 0 before that — never epoch 3. Picking "the highest epoch
1011
- // we have keys for" sent them under APPLICATION-DATA protection,
1012
- // where the peer decrypts with app keys and finds a content type
1013
- // that does not belong. Chrome answers decode_error.
1014
- // This shows up the moment a ClientHello is large enough to be
1015
- // FRAGMENTED — an SFU with several transceivers crosses the MTU
1016
- // and a two-way call does not, which is exactly why bidirectional
1017
- // worked and party mode did not.
1018
- // Post-handshake (§7.1: "For post-handshake messages, ACKs SHOULD be
1019
- // sent once for each received and processed handshake record") the
1020
- // handshake epoch is gone and §7 requires the HIGHEST available sending
1021
- // epoch. During the handshake it is still epoch 2, or 0 before keys.
1022
- // RFC 9147 §7.1 lists the flights that are IMPLICITLY acknowledged by
1023
- // the responding flight, and the client's final flight is pointedly not
1024
- // among them: "In general, flights MUST be ACKed unless they are
1025
- // implicitly acknowledged", excepting "handshake flights other than the
1026
- // client's final flight of the main handshake".
1027
- //
1028
- // So the two roles need opposite answers here, which is why a
1029
- // state-only test could not get both right:
1030
- // client — the server's flight is implicitly acked by our own final
1031
- // flight, so an explicit ACK is both redundant and (at the
1032
- // application epoch) actively harmful;
1033
- // server — the client's final flight has no responding flight, so
1034
- // without an explicit ACK the client retransmits it forever.
1035
- // §7 then fixes the epoch: after the handshake, the highest available
1036
- // sending epoch.
1037
- let justCompleted = (!wasConnectedOnEntry && ctx.state === 'connected');
1038
- let finalFlightAck = isServer && justCompleted;
1039
- let atAppEpoch = wasConnectedOnEntry || finalFlightAck;
1040
-
1041
- let ackEpoch = atAppEpoch
1042
- ? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
1043
- : (ctx.keys[2] ? 2 : 0);
1044
- 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();
1482
+ }
1483
+ }
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;
1045
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);
1046
1517
  }
1047
1518
 
1048
1519
  function processRecord(record) {
@@ -1060,6 +1531,23 @@ function DTLSSession(options) {
1060
1531
  if (ctx.state === 'connected' && !record.encrypted &&
1061
1532
  (record.type === CT.ALERT || record.type === CT.APPLICATION_DATA)) return;
1062
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
+
1063
1551
  if (record.type === CT.HANDSHAKE) {
1064
1552
  // An epoch>0 record that could not be decrypted (keys not yet
1065
1553
  // installed, AEAD failure) must NOT be fed to the handshake
@@ -1076,42 +1564,81 @@ function DTLSSession(options) {
1076
1564
  processHandshakeRecord(record.content, record.epoch, record.encrypted);
1077
1565
  } else if (record.type === CT.APPLICATION_DATA) {
1078
1566
  if (ctx.state === 'connected') {
1079
- // Protected data from the peer means it completed the handshake,
1080
- // so it has our Finished.
1081
- if (ctx.finalFlightPending) finalFlightDone();
1082
- 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));
1083
1585
  }
1084
1586
  } else if (record.type === CT.CHANGE_CIPHER_SPEC) {
1085
- // DTLS 1.2: peer sent CCS — derive keys if not yet done
1086
- ctx.remoteCcsSeen = true;
1087
- if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && !ctx.keys[1]) {
1088
- let ts = tls.getTrafficSecrets();
1089
- if (ts.masterSecret) {
1090
- let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
1091
- ctx.keys[1] = {
1092
- write: { key: d12.writeKey, iv: d12.writeIv },
1093
- read: { key: d12.readKey, iv: d12.readIv },
1094
- };
1095
- ctx.writeSeq[1] = 0;
1096
- }
1097
- }
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();
1098
1601
  } else if (record.type === CT.ACK) {
1099
- 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);
1100
1609
  } else if (record.type === CT.ALERT) {
1101
- let level = record.content[0];
1102
- 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;
1103
1619
  // Name the alert either way. A close_notify is a CLEAN shutdown and
1104
1620
  // a failure is not, but both present to the application as "the
1105
1621
  // connection ended" — and telling them apart is the difference
1106
1622
  // between "the peer hung up" and "the peer rejected us".
1107
- _ldbg('RX Alert level=' + level + ' desc=' + desc +
1108
- (desc === 0 ? ' (close_notify — clean shutdown)' : ' (failure)'));
1109
- if (desc === 0) {
1110
- // close_notify
1111
- ctx.state = 'closed';
1112
- 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
+ }
1113
1638
  } else {
1114
- 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
1115
1642
  }
1116
1643
  }
1117
1644
  }
@@ -1124,8 +1651,25 @@ function DTLSSession(options) {
1124
1651
  let off = 0;
1125
1652
  let sawMessage = false; // record contained ≥1 parseable handshake message
1126
1653
  let anyNew = false; // ≥1 message advanced our state (not a duplicate)
1127
- while (off + 12 <= data.length) {
1128
- 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; }
1129
1673
 
1130
1674
  let msgSeq = parsed.msg_seq;
1131
1675
  let totalLen = parsed.length;
@@ -1139,10 +1683,10 @@ function DTLSSession(options) {
1139
1683
  // Is this a complete message or a fragment?
1140
1684
  if (fragOffset === 0 && fragLen === totalLen) {
1141
1685
  // Complete message — feed directly
1142
- if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
1686
+ if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq, epoch)) anyNew = true;
1143
1687
  } else {
1144
1688
  // Fragment — reassemble
1145
- if (reassembleFragment(parsed)) anyNew = true;
1689
+ if (reassembleFragment(parsed, epoch)) anyNew = true;
1146
1690
  }
1147
1691
 
1148
1692
  off += 12 + fragLen;
@@ -1167,28 +1711,42 @@ function DTLSSession(options) {
1167
1711
  // of the flight we JUST sent, not on the acknowledged one. The
1168
1712
  // cancel happens at the acceptance points inside
1169
1713
  // deliverHandshakeMessage instead, before the feed.
1170
- if (anyNew) {
1171
- // handled at acceptance time
1172
- } else if (sawMessage && ctx.currentFlight.length > 0 &&
1173
- ctx.state !== 'connected' &&
1174
- ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 &&
1175
- // Damping: a peer retransmits its flight as SEVERAL
1176
- // datagrams; without a guard, EACH duplicate datagram
1177
- // elicited a full resend of our flight — a ×N
1178
- // amplification burst that itself increased loss
1179
- // pressure. One resend per 250ms answers the peer's
1180
- // whole retransmitted flight exactly once.
1181
- (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()) {
1182
1743
  ctx._lastDupElicitedResend = Date.now();
1183
- // Pure duplicate of a previous peer flight while we still have an
1184
- // outstanding flight of our own: the peer is telling us it never
1185
- // got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
1186
- // behavior) rather than waiting out our own backoff. The regular
1187
- // timer keeps running unchanged, so the retransmit budget still
1188
- // bounds the total effort; the resend rate is naturally bounded by
1189
- // the peer's own retransmission schedule. DTLS 1.3 is excluded —
1190
- // 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.
1191
1746
  resendFlight();
1747
+ } else if (ctx.state === 'connected' && ctx.lastFlight12) {
1748
+ ctx._lastDupElicitedResend = Date.now();
1749
+ transmitUnacked(ctx.lastFlight12, 'lastFlight12', false);
1192
1750
  }
1193
1751
  }
1194
1752
 
@@ -1202,7 +1760,7 @@ function DTLSSession(options) {
1202
1760
  * merged byte-ranges, rather than requiring each fragment to start exactly
1203
1761
  * where the previous one ended.
1204
1762
  */
1205
- function reassembleFragment(parsed) {
1763
+ function reassembleFragment(parsed, epoch) {
1206
1764
  let key = parsed.msg_seq;
1207
1765
 
1208
1766
  // Ignore fragments for messages we've already delivered.
@@ -1212,14 +1770,28 @@ function DTLSSession(options) {
1212
1770
  // fragment. Reject anything claiming more than our cap BEFORE allocating,
1213
1771
  // so a peer can't force huge buffers with a single unauthenticated packet.
1214
1772
  if (parsed.length > ctx.maxHandshakeMessageSize) {
1215
- ev.emit('error', new Error('DTLS handshake message exceeds maxHandshakeMessageSize (' +
1216
- parsed.length + ' > ' + ctx.maxHandshakeMessageSize + ')'));
1217
- 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);
1218
1777
  }
1219
1778
 
1220
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
+ }
1221
1792
  if (!frag) {
1222
1793
  frag = ctx.fragments[key] = {
1794
+ epoch: epoch,
1223
1795
  totalLength: parsed.length,
1224
1796
  type: parsed.type,
1225
1797
  buf: new Uint8Array(parsed.length),
@@ -1230,8 +1802,11 @@ function DTLSSession(options) {
1230
1802
  let start = parsed.frag_offset;
1231
1803
  let end = parsed.frag_offset + parsed.frag_length;
1232
1804
 
1233
- // Bounds guard against a malformed fragment claiming to exceed totalLength.
1234
- 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
+ }
1235
1810
 
1236
1811
  // Coverage before this fragment — used to decide whether it contributed
1237
1812
  // anything new (a retransmitted fragment that only re-covers known bytes
@@ -1264,10 +1839,13 @@ function DTLSSession(options) {
1264
1839
  }
1265
1840
 
1266
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
+
1267
1845
  // Complete when a single range covers [0, totalLength).
1268
1846
  if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
1269
1847
  delete ctx.fragments[key];
1270
- return deliverHandshakeMessage(frag.type, frag.buf, key);
1848
+ return deliverHandshakeMessage(frag.type, frag.buf, key, frag.epoch);
1271
1849
  }
1272
1850
  return coveredAfter > coveredBefore;
1273
1851
  }
@@ -1282,12 +1860,19 @@ function DTLSSession(options) {
1282
1860
  * dropped. processHandshakeRecord uses this for the implicit-ACK
1283
1861
  * decision — only genuinely new data may cancel our retransmit timer.
1284
1862
  */
1285
- function deliverHandshakeMessage(type, body, msgSeq) {
1863
+ function deliverHandshakeMessage(type, body, msgSeq, epoch) {
1286
1864
  // Skip if we've already processed this msg_seq (duplicate / retransmit).
1287
1865
  if (msgSeq < ctx.nextReadMsgSeq) return false;
1288
1866
 
1289
1867
  // Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
1290
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;
1291
1876
  ctx.nextReadMsgSeq = msgSeq + 1;
1292
1877
  // The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
1293
1878
  // explicit cancel is needed: triggerClientHelloWithCookie arms the
@@ -1332,10 +1917,9 @@ function DTLSSession(options) {
1332
1917
  // 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
1333
1918
  // EXTENSION; the legacy body field is vestigial and must stay empty.
1334
1919
  if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
1335
- tls.sendAlert(2, 47); // fatal, illegal_parameter
1336
- ev.emit('error', new Error(
1920
+ fail(new Error(
1337
1921
  'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
1338
- 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);
1339
1923
  return false;
1340
1924
  }
1341
1925
 
@@ -1344,7 +1928,12 @@ function DTLSSession(options) {
1344
1928
  // does return-routability with HelloRetryRequest + the cookie extension
1345
1929
  // instead. The old guard tested ctx.selectedVersion, which is null here,
1346
1930
  // so a DTLS 1.3 handshake could still be answered with an HVR.
1347
- 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) {
1348
1937
 
1349
1938
  let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
1350
1939
 
@@ -1393,9 +1982,12 @@ function DTLSSession(options) {
1393
1982
  // and wait for the gap to fill. If it's the next expected one, feed it, then
1394
1983
  // drain any consecutive messages that were buffered earlier.
1395
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;
1396
1988
  // Out of order — hold it (ignore a duplicate already buffered).
1397
1989
  if (!(msgSeq in ctx.pendingMessages)) {
1398
- ctx.pendingMessages[msgSeq] = { type: type, body: body };
1990
+ ctx.pendingMessages[msgSeq] = { type: type, body: body, epoch: epoch };
1399
1991
  return true; // new information, even if not yet deliverable
1400
1992
  }
1401
1993
  return false; // duplicate of an already-buffered message
@@ -1426,6 +2018,8 @@ function DTLSSession(options) {
1426
2018
  // When the feed does respond, the flight boundary (newFlightPending
1427
2019
  // → startNewFlight) resets the retransmit budget and the outgoing
1428
2020
  // path arms the new flight's timer, superseding this re-arm.
2021
+ if (!epochFitsHandshake(epoch, type)) return false;
2022
+ flightAnswered();
1429
2023
  startRetransmitTimer();
1430
2024
  ctx.newFlightPending = true;
1431
2025
  feedHandshakeToTls(type, body, msgSeq);
@@ -1440,6 +2034,9 @@ function DTLSSession(options) {
1440
2034
  while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
1441
2035
  let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
1442
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;
1443
2040
  startRetransmitTimer();
1444
2041
  ctx.newFlightPending = true;
1445
2042
  feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
@@ -1448,11 +2045,60 @@ function DTLSSession(options) {
1448
2045
  return true;
1449
2046
  }
1450
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
+
1451
2094
  /**
1452
2095
  * Feed one in-order handshake message to the TLSSession, converting from DTLS
1453
2096
  * to TLS wire format and driving version detection.
1454
2097
  */
1455
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
+
1456
2102
  // Build TLS-format message: type(1) + length(3) + body
1457
2103
  let tlsMsg = build_message(type, body);
1458
2104
 
@@ -1471,6 +2117,54 @@ function DTLSSession(options) {
1471
2117
  }
1472
2118
  }
1473
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
+
1474
2168
  /**
1475
2169
  * Server: build and send a HelloVerifyRequest with a fresh cookie (DTLS 1.2).
1476
2170
  * The cookie is a CSPRNG value stored on the context; a matching cookie must
@@ -1484,8 +2178,7 @@ function DTLSSession(options) {
1484
2178
 
1485
2179
  // HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
1486
2180
  let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
1487
- let record = buildDtlsPlaintext(CT.HANDSHAKE, 0, nextWriteSeq(0), hvrMsg);
1488
- _ldbgTx(record, 'L1194'); ev.emit('packet', record);
2181
+ queueOut({ kind: 'raw', epoch: 0, contentType: CT.HANDSHAKE, payload: hvrMsg, label: 'hvr' });
1489
2182
  }
1490
2183
 
1491
2184
  /**
@@ -1534,6 +2227,7 @@ function DTLSSession(options) {
1534
2227
  // a wire message sent with seq 1 would fail Finished verification.
1535
2228
  tls.context.message_sent_seq = 1;
1536
2229
  ctx.currentFlight = [];
2230
+ ctx.flightQueued = 0;
1537
2231
  ctx.fragments = {}; // discard any partial reassembly from CH1 flight
1538
2232
  ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
1539
2233
  transcriptMsgSeqs = [];
@@ -1545,11 +2239,9 @@ function DTLSSession(options) {
1545
2239
  tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
1546
2240
  );
1547
2241
 
1548
- // Build DTLS message and send — msg_seq 1 per the RFC figure above.
1549
- let dtlsMsg = build_dtls_handshake(ch2, 1);
1550
- let record = buildRecord(0, CT.HANDSHAKE, dtlsMsg);
1551
- ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
1552
- 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);
1553
2245
 
1554
2246
  tls.context.message_sent_seq = 2;
1555
2247
  startRetransmitTimer();
@@ -1583,25 +2275,44 @@ function DTLSSession(options) {
1583
2275
  cancelRetransmit();
1584
2276
  return;
1585
2277
  }
1586
- for (let i = 0; i < ctx.currentFlight.length; i++) {
1587
- let f = ctx.currentFlight[i];
1588
- // DTLS 1.3 (RFC 9147 §7.1): records the peer has ACKed are not resent.
1589
- if (f.acked) continue;
1590
- let record = buildFlightRecord(f);
1591
- if (record) { _ldbgTx(record, 'L1280'); ev.emit('packet', record); }
1592
- }
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);
1593
2281
  }
1594
2282
 
1595
2283
  /**
1596
- * Build the record for a flight entry and remember its record number
1597
- * (epoch, sequence) — every transmission gets a fresh one, and a DTLS 1.3
1598
- * ACK names the records it covers by those numbers.
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.
1599
2287
  */
1600
- function buildFlightRecord(f) {
1601
- let seq = (f.epoch in ctx.writeSeq) ? ctx.writeSeq[f.epoch] : 0;
1602
- let record = buildRecord(f.epoch, f.contentType, f.payload);
1603
- if (record) (f.sent || (f.sent = [])).push(f.epoch * 0x1000000000000 + seq);
1604
- return record;
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 });
2293
+ }
2294
+ }
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;
1605
2316
  }
1606
2317
 
1607
2318
  function startRetransmitTimer() {
@@ -1613,16 +2324,16 @@ function DTLSSession(options) {
1613
2324
  // connected (or closed) covers every caller, including any added
1614
2325
  // later, without each having to remember the rule.
1615
2326
  if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
2327
+ if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
1616
2328
  ctx.retransmitTimer = setTimeout(function() {
1617
- if (ctx.finalFlightPending && ctx.retransmitCount >= ctx.maxRetransmits) {
2329
+ if (ctx.finalFlightPending && exhausted(ctx.retransmitCount, ctx.flightBegan)) {
1618
2330
  // The connection is up on our side; stop resending without failing it.
1619
2331
  finalFlightDone();
1620
2332
  return;
1621
2333
  }
1622
- if (ctx.retransmitCount >= ctx.maxRetransmits) {
1623
- ev.emit('error', new Error('DTLS handshake timeout — max retransmits exceeded'));
1624
- ctx.state = 'closed';
1625
- ev.emit('close');
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'));
1626
2337
  return;
1627
2338
  }
1628
2339
 
@@ -1651,10 +2362,54 @@ function DTLSSession(options) {
1651
2362
  }
1652
2363
  }
1653
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
+
1654
2372
  function startNewFlight() {
1655
2373
  ctx.currentFlight = [];
2374
+ ctx.flightQueued = 0;
1656
2375
  ctx.retransmitCount = 0;
1657
- 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;
1658
2413
  }
1659
2414
 
1660
2415
 
@@ -1662,20 +2417,13 @@ function DTLSSession(options) {
1662
2417
  // ACK (DTLS 1.3)
1663
2418
  // ============================================================
1664
2419
 
1665
- function processAck(content) {
1666
- let acks = parseDtlsAck(content);
1667
-
1668
- // An outstanding KeyUpdate is what this ACK is for: §8 forbids protecting
1669
- // anything with the new keys until it arrives, so THIS is the moment the
1670
- // new epoch becomes live. Keys were installed when the KeyUpdate was sent;
1671
- // only the switch happens here.
1672
- if (ctx.pendingWriteEpoch !== null) {
1673
- ctx.writeEpoch = ctx.pendingWriteEpoch;
1674
- ctx.pendingWriteEpoch = null;
1675
- cancelKeyUpdateRetransmit();
1676
- ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
1677
- return;
1678
- }
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;
1679
2427
 
1680
2428
  // During the handshake an ACK may cover only PART of our flight (RFC 9147
1681
2429
  // §7.1): the peer lists the records it received. Those are done; the
@@ -1686,22 +2434,58 @@ function DTLSSession(options) {
1686
2434
  // (≈ half of all handshakes at 5% loss, when the lost record was inside
1687
2435
  // the server's flight).
1688
2436
  if ((ctx.state !== 'connected' || ctx.finalFlightPending) && ctx.currentFlight.length > 0) {
1689
- let got = new Set();
1690
- for (let i = 0; i < acks.length; i++) got.add(acks[i].epoch * 0x1000000000000 + acks[i].seq);
1691
- let remaining = 0;
2437
+ let newly = markAcked(ctx.currentFlight, got);
2438
+ if (newly > 0) flightAnswered();
2439
+ let remaining = 0, protectedEntries = 0, protectedAcked = 0;
1692
2440
  for (let i = 0; i < ctx.currentFlight.length; i++) {
1693
2441
  let f = ctx.currentFlight[i];
1694
- if (!f.acked && f.sent) {
1695
- for (let k = 0; k < f.sent.length; k++) {
1696
- if (got.has(f.sent[k])) { f.acked = true; break; }
1697
- }
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();
1698
2485
  }
1699
- // Plaintext epoch-0 records (ServerHello, CCS) are never ACKed — the
1700
- // peer acknowledges them implicitly by answering — so they do not
1701
- // keep the timer alive on their own.
1702
- if (!f.acked && f.epoch !== 0) remaining++;
2486
+ return;
1703
2487
  }
1704
- if (remaining > 0) return;
2488
+ if (newly === 0 && !ctx.finalFlightPending) return; // nothing this ACK confirmed
1705
2489
  }
1706
2490
 
1707
2491
  // The whole flight is acknowledged — or, the ACK for the client's final
@@ -1735,8 +2519,7 @@ function DTLSSession(options) {
1735
2519
  if (!postHandshake && (ctx.state === 'connected' || ctx.state === 'closed')) return;
1736
2520
  if (ctx.state === 'closed') return;
1737
2521
  let payload = buildDtlsAck(recordsToAck);
1738
- let record = buildRecord(epoch, CT.ACK, payload);
1739
- _ldbgTx(record, 'L1334'); ev.emit('packet', record);
2522
+ queueOut({ kind: 'raw', epoch: epoch, contentType: CT.ACK, payload: payload, label: 'ack' });
1740
2523
  }
1741
2524
 
1742
2525
 
@@ -1754,6 +2537,10 @@ function DTLSSession(options) {
1754
2537
  ev.emit('clienthello', data, message);
1755
2538
  });
1756
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
+
1757
2544
  tls.on('secureConnect', function() {
1758
2545
  _ldbg('handshake complete — secureConnect (version=0x' +
1759
2546
  ((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
@@ -1763,10 +2550,17 @@ function DTLSSession(options) {
1763
2550
  // retransmit timer until that ACK — or anything else from the peer at
1764
2551
  // the application epoch — shows it arrived. Dropping it here meant one
1765
2552
  // lost Finished left us "connected" and the peer waiting for good.
1766
- if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && ctx.currentFlight.length > 0) {
2553
+ if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && haveFlight()) {
1767
2554
  ctx.finalFlightPending = true;
1768
2555
  startRetransmitTimer();
1769
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
+ }
1770
2564
  cancelRetransmit();
1771
2565
  startNewFlight();
1772
2566
  }
@@ -1779,12 +2573,14 @@ function DTLSSession(options) {
1779
2573
  // only symptom is a connection that dies after a handshake that
1780
2574
  // looked complete, which is exactly the DTLS 1.3 interop case.
1781
2575
  _ldbg('ERROR ' + (e && e.message ? e.message : e) +
1782
- (e && e.alertDescription != null ? ' alertDesc=' + e.alertDescription : '') +
2576
+ (e && e.alertDesc != null ? ' alertDesc=' + e.alertDesc : '') +
1783
2577
  (e && e.code ? ' code=' + e.code : ''));
1784
- ev.emit('error', e);
2578
+ // TLSSession has sent its alert already.
2579
+ fail(e);
1785
2580
  });
1786
2581
 
1787
2582
  tls.on('session', function(ticket) {
2583
+ ctx.lastSession = ticket;
1788
2584
  ev.emit('session', ticket);
1789
2585
  });
1790
2586
 
@@ -1799,12 +2595,20 @@ function DTLSSession(options) {
1799
2595
  return;
1800
2596
  }
1801
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
+ }
1802
2607
 
1803
2608
  let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
1804
- ? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
2609
+ ? appEpoch()
1805
2610
  : 1;
1806
- let record = buildRecord(epoch, CT.APPLICATION_DATA, data);
1807
- _ldbgTx(record, 'L1383'); ev.emit('packet', record);
2611
+ queueOut({ kind: 'raw', epoch: epoch, contentType: CT.APPLICATION_DATA, payload: data, label: 'data' });
1808
2612
  }
1809
2613
 
1810
2614
 
@@ -1821,23 +2625,10 @@ function DTLSSession(options) {
1821
2625
  // the peer has switched to reading protected records, and a plaintext
1822
2626
  // close_notify arrives with a classic DTLSPlaintext header where the peer
1823
2627
  // requires the RFC 9147 §4 unified header. It then reports a bad record
1824
- // type instead of a clean shutdown. Mirror the ACK path, which already
1825
- // picks the highest epoch we hold write keys for.
1826
- let epoch;
1827
- if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
1828
- // After a KeyUpdate the live epoch is 4, 5, ... — the peer has moved on
1829
- // and no longer reads epoch 3, so a close_notify sent there is dropped
1830
- // and the shutdown looks like a timeout instead of a clean close.
1831
- epoch = (ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch])
1832
- ? ctx.writeEpoch
1833
- : (ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0));
1834
- } else {
1835
- epoch = ctx.keys[1] ? 1 : 0;
1836
- }
1837
- sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
1838
- ctx.state = 'closed';
1839
- cancelRetransmit();
1840
- 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();
1841
2632
  ev.emit('close');
1842
2633
  }
1843
2634
 
@@ -1944,6 +2735,15 @@ function DTLSSession(options) {
1944
2735
 
1945
2736
  /** Negotiated ALPN. */
1946
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,
1947
2747
 
1948
2748
  /** Peer certificate. */
1949
2749
  getPeerCertificate: function() { return tls.getPeerCertificate(); },
@@ -1965,6 +2765,31 @@ function DTLSSession(options) {
1965
2765
 
1966
2766
  /** Whether this connection resumed an earlier session. */
1967
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
+ },
1968
2793
  };
1969
2794
 
1970
2795
  for (let k in api) {
@@ -1980,4 +2805,5 @@ function DTLSSession(options) {
1980
2805
  return this;
1981
2806
  }
1982
2807
 
1983
- export default DTLSSession;
2808
+ export default DTLSSession;
2809
+ export { SERVER_CID_LEN };