@amritk/nish-x86_64-linux 0.17.0 → 0.18.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.
package/std/json.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `std/json` — the value of one field of one flat JSON object.
2
+ * `std/json` — the value of a field, or of several, of one flat JSON object.
3
3
  *
4
4
  * It exists because the compiler's own machine-readable surface is JSON: under
5
5
  * `--json` every diagnostic is one flat object on a line of stdout, whose `code`
@@ -26,10 +26,10 @@
26
26
  * whether the whole line was well-formed is asking a question this does not
27
27
  * answer.
28
28
  *
29
- * There is one exported function on purpose. A `std/` module is compiled into
30
- * the program that imports it and the symbol namespace is flat
29
+ * There are two exported functions, and few on purpose. A `std/` module is
30
+ * compiled into the program that imports it and the symbol namespace is flat
31
31
  * (`std/README.md`), so every name here is a name its importer cannot use: the
32
- * helpers are `json`-prefixed and the reader is the only export. Splitting a
32
+ * helpers are `json`-prefixed and the readers are the only exports. Splitting a
33
33
  * stream into lines and picking the ones that start with `{` is two calls the
34
34
  * caller already has — `splitLines` from `std/text` and `startsWith` — and
35
35
  * duplicating them here would cost more names than it saves.
@@ -46,6 +46,16 @@
46
46
  * }
47
47
  * }
48
48
  *
49
+ * `jsonField` scans from the start of the line on every call, so a caller that
50
+ * wants three fields of each line reads each line three times. `jsonFields`
51
+ * reads it once: `jsonFields(line, ["code", "line", "message"])` answers an
52
+ * array whose slot `k` is exactly what `jsonField(line, names[k])` answers —
53
+ * absent, first-of-a-duplicate and malformed input included — because the two
54
+ * share the step that reads one member (`jsonReadMember`) and stop at the same
55
+ * one. Its array goes back to the caller with the values in it, so a loop that
56
+ * calls it keeps its per-pass arena release, as one calling `jsonField` does;
57
+ * `jsonFields`' own comment says when that holds.
58
+ *
49
59
  * Every offset here is a **byte** offset and every width is spelled, with each
50
60
  * length and byte a builtin answers read through `toI32` — `.length` and
51
61
  * `charCodeAt` answer `number`, which is `f64` under `--number-mode f64`, so the
@@ -53,14 +63,17 @@
53
63
  * (`docs/wp26-stdlib.md` §4).
54
64
  */
55
65
 
66
+ const JSON_BACKSPACE: i32 = 8
56
67
  const JSON_TAB: i32 = 9
57
68
  const JSON_NEWLINE: i32 = 10
69
+ const JSON_FORM_FEED: i32 = 12
58
70
  const JSON_CARRIAGE_RETURN: i32 = 13
59
71
  const JSON_SPACE: i32 = 32
60
72
  const JSON_QUOTE: i32 = 34
61
73
  const JSON_COMMA: i32 = 44
62
74
  const JSON_COLON: i32 = 58
63
75
  const JSON_BACKSLASH: i32 = 92
76
+ const JSON_LETTER_U: i32 = 117
64
77
  const JSON_OPEN_BRACKET: i32 = 91
65
78
  const JSON_CLOSE_BRACKET: i32 = 93
66
79
  const JSON_OPEN_BRACE: i32 = 123
@@ -261,6 +274,37 @@ const jsonUtf8 = (code: i32): string => {
261
274
  return `${lead}${middle}${String.fromCharCode(JSON_UTF8_CONTINUATION + (code & JSON_UTF8_LOW_SIX))}`
262
275
  }
263
276
 
277
+ /** The number of bytes `jsonUtf8(code)` answers. */
278
+ const jsonUtf8Width = (code: i32): i32 => {
279
+ if (code < JSON_UTF8_TWO_BYTE_FLOOR) {
280
+ return 1
281
+ }
282
+ if (code < JSON_UTF8_THREE_BYTE_FLOOR) {
283
+ return 2
284
+ }
285
+ return 3
286
+ }
287
+
288
+ /**
289
+ * Byte `k` of the `width` bytes `jsonUtf8(code)` answers: its arithmetic, one
290
+ * byte at a time, for a caller that compares the bytes rather than keeps them.
291
+ */
292
+ const jsonUtf8Byte = (code: i32, width: i32, k: i32): i32 => {
293
+ if (width === 1) {
294
+ return code
295
+ }
296
+ if (k === 0) {
297
+ if (width === 2) {
298
+ return JSON_UTF8_TWO_BYTE_LEAD + (code >> 6)
299
+ }
300
+ return JSON_UTF8_THREE_BYTE_LEAD + (code >> 12)
301
+ }
302
+ if (k === width - 1) {
303
+ return JSON_UTF8_CONTINUATION + (code & JSON_UTF8_LOW_SIX)
304
+ }
305
+ return JSON_UTF8_CONTINUATION + ((code >> 6) & JSON_UTF8_LOW_SIX)
306
+ }
307
+
264
308
  /**
265
309
  * The bytes of `text[at..end)` with the JSON escapes decoded — the body of a
266
310
  * string literal, without its quotes.
@@ -343,6 +387,190 @@ const jsonUnescape = (text: string, at: i32, end: i32): string => {
343
387
  return parts.join("")
344
388
  }
345
389
 
390
+ /** Whether byte `j` of `name` is `code`; an index past either end is not. */
391
+ const jsonNameHas = (name: string, j: i32, code: i32): boolean =>
392
+ j >= 0 && j < toI32(name.length) && toI32(name.charCodeAt(j)) === code
393
+
394
+ /**
395
+ * Whether `jsonUnescape(text, at, end)` would answer `name`, decided without
396
+ * building it.
397
+ *
398
+ * `jsonField` and `jsonFields` ask this of every key they walk past, and almost
399
+ * every answer is no, so the question is answered the way it is asked: the literal and `name`
400
+ * are walked together, each escape decoded where it stands, and the first byte
401
+ * that differs ends the walk. Every escape reads as `jsonUnescape` reads it —
402
+ * the short forms, a `\u` as the bytes `jsonUtf8` would build, a broken `\u`
403
+ * as the two bytes `\u`, a backslash with no letter after it as itself, and
404
+ * any other escape as the byte it escapes — so a key matches here exactly when
405
+ * its decoded text would.
406
+ */
407
+ const jsonKeyEquals = (text: string, at: i32, end: i32, name: string): boolean => {
408
+ // `jsonUnescape`'s guard, for the same reason: every caller passes the
409
+ // inside of a key `jsonReadMember` found with `jsonEndOfString`.
410
+ if (at < 0 || end > toI32(text.length)) {
411
+ return false
412
+ }
413
+ let i: i32 = at
414
+ let j: i32 = 0
415
+ while (i < end) {
416
+ const code: i32 = toI32(text.charCodeAt(i))
417
+ if (code !== JSON_BACKSLASH) {
418
+ if (!jsonNameHas(name, j, code)) {
419
+ return false
420
+ }
421
+ i += 1
422
+ j += 1
423
+ continue
424
+ }
425
+ const letter: i32 = i + 1 < end ? toI32(text.charCodeAt(i + 1)) : 0
426
+ if (letter === JSON_LETTER_U) {
427
+ const point: i32 = jsonHex4(text, i + 2, end)
428
+ if (point < 0) {
429
+ if (!jsonNameHas(name, j, JSON_BACKSLASH) || !jsonNameHas(name, j + 1, JSON_LETTER_U)) {
430
+ return false
431
+ }
432
+ i += 2
433
+ j += 2
434
+ continue
435
+ }
436
+ const width: i32 = jsonUtf8Width(point)
437
+ let k: i32 = 0
438
+ while (k < width) {
439
+ if (!jsonNameHas(name, j + k, jsonUtf8Byte(point, width, k))) {
440
+ return false
441
+ }
442
+ k += 1
443
+ }
444
+ i += 6
445
+ j += width
446
+ continue
447
+ }
448
+ // Every other escape decodes to one byte.
449
+ let decoded: i32 = letter
450
+ if (letter === 110) {
451
+ decoded = JSON_NEWLINE
452
+ } else if (letter === 116) {
453
+ decoded = JSON_TAB
454
+ } else if (letter === 114) {
455
+ decoded = JSON_CARRIAGE_RETURN
456
+ } else if (letter === 98) {
457
+ decoded = JSON_BACKSPACE
458
+ } else if (letter === 102) {
459
+ decoded = JSON_FORM_FEED
460
+ } else if (letter === 0) {
461
+ decoded = JSON_BACKSLASH
462
+ }
463
+ if (!jsonNameHas(name, j, decoded)) {
464
+ return false
465
+ }
466
+ // A backslash with no letter after it is one byte of the literal and every
467
+ // other escape two. The steps are constants rather than a variable because
468
+ // a cursor moved by a variable is one the bounds proof stops following.
469
+ if (letter === 0) {
470
+ i += 1
471
+ } else {
472
+ i += 2
473
+ }
474
+ j += 1
475
+ }
476
+ return j === toI32(name.length)
477
+ }
478
+
479
+ /**
480
+ * Where one member of an object lies, as `jsonReadMember` found it: the inside
481
+ * of its key literal, its value, and the opening quote of the key after it.
482
+ *
483
+ * It is a class so that one step can answer the four offsets together, and
484
+ * each reader allocates exactly one, which never leaves the reader: that is
485
+ * what keeps it a stack slot rather than an arena allocation.
486
+ */
487
+ class JsonMember {
488
+ keyAt: i32 = 0
489
+ keyEnd: i32 = 0
490
+ valueAt: i32 = 0
491
+ valueEnd: i32 = 0
492
+ /** The opening quote of the next key, or `-1` when no comma follows the value. */
493
+ next: i32 = -1
494
+ }
495
+
496
+ /**
497
+ * The opening quote of the first key of `object`, or `-1` when `object` does
498
+ * not start, past any blanks, with a `{`.
499
+ */
500
+ const jsonFirstKey = (object: string): i32 => {
501
+ const length: i32 = toI32(object.length)
502
+ const i: i32 = jsonSkipBlank(object, 0)
503
+ if (i >= length || toI32(object.charCodeAt(i)) !== JSON_OPEN_BRACE) {
504
+ return -1
505
+ }
506
+ return jsonSkipBlank(object, i + 1)
507
+ }
508
+
509
+ /**
510
+ * Reads the member whose key's opening quote is at `at` into `member`, and
511
+ * answers whether there was one.
512
+ *
513
+ * This is the per-key step `jsonField` and `jsonFields` share, so they cannot
514
+ * disagree about where a member ends: `false` for anything that is not a key,
515
+ * a colon and a whole value in that order, which is the point at which both
516
+ * stop. A value need not be followed by a comma — a truncated object still
517
+ * answers the fields before the cut — so a missing one only sets `next` to
518
+ * `-1`, and the read that follows answers `false`.
519
+ */
520
+ const jsonReadMember = (object: string, at: i32, member: JsonMember): boolean => {
521
+ const length: i32 = toI32(object.length)
522
+ // `jsonSkipBlank` never answers a negative index for a non-negative one, but
523
+ // the bounds proof does not look inside a callee, so every cursor it answers
524
+ // is tested for `i < 0` beside `i >= length`: the pair is what lets each read
525
+ // below go without a check.
526
+ if (at < 0 || at >= length || toI32(object.charCodeAt(at)) !== JSON_QUOTE) {
527
+ return false
528
+ }
529
+ const keyEnd: i32 = jsonEndOfString(object, at)
530
+ if (keyEnd < 0) {
531
+ return false
532
+ }
533
+ const colon: i32 = jsonSkipBlank(object, keyEnd)
534
+ if (colon < 0 || colon >= length || toI32(object.charCodeAt(colon)) !== JSON_COLON) {
535
+ return false
536
+ }
537
+ const valueAt: i32 = jsonSkipBlank(object, colon + 1)
538
+ const valueEnd: i32 = jsonEndOfValue(object, valueAt)
539
+ if (valueEnd < 0) {
540
+ return false
541
+ }
542
+ member.keyAt = at + 1
543
+ member.keyEnd = keyEnd - 1
544
+ member.valueAt = valueAt
545
+ member.valueEnd = valueEnd
546
+ const comma: i32 = jsonSkipBlank(object, valueEnd)
547
+ if (comma < 0 || comma >= length || toI32(object.charCodeAt(comma)) !== JSON_COMMA) {
548
+ member.next = -1
549
+ } else {
550
+ member.next = jsonSkipBlank(object, comma + 1)
551
+ }
552
+ return true
553
+ }
554
+
555
+ /**
556
+ * The text a member's value answers: a string unquoted and unescaped, anything
557
+ * else as the bytes it was written with.
558
+ */
559
+ const jsonValueText = (object: string, member: JsonMember): string => {
560
+ const valueAt: i32 = member.valueAt
561
+ const valueEnd: i32 = member.valueEnd
562
+ // `jsonReadMember` only fills in a value `jsonEndOfValue` ended, which starts
563
+ // inside the object and never ends past it, so this changes no answer: it is
564
+ // the range the value's first byte and its `substring` are proved in.
565
+ if (valueAt < 0 || valueAt >= valueEnd || valueEnd > toI32(object.length)) {
566
+ return ""
567
+ }
568
+ if (toI32(object.charCodeAt(valueAt)) === JSON_QUOTE) {
569
+ return jsonUnescape(object, valueAt + 1, valueEnd - 1)
570
+ }
571
+ return object.substring(valueAt, valueEnd)
572
+ }
573
+
346
574
  /**
347
575
  * The value of `name` in `object`, or `null` when the object does not have that
348
576
  * field.
@@ -358,45 +586,73 @@ const jsonUnescape = (text: string, at: i32, end: i32): string => {
358
586
  * and it is the shape the compiler's `--json` line has.
359
587
  */
360
588
  export const jsonField = (object: string, name: string): string | null => {
361
- const length: i32 = toI32(object.length)
362
- let i: i32 = jsonSkipBlank(object, 0)
363
- if (i >= length || toI32(object.charCodeAt(i)) !== JSON_OPEN_BRACE) {
364
- return null
365
- }
366
- i = jsonSkipBlank(object, i + 1)
367
- // `jsonSkipBlank` never answers a negative index for a non-negative one, but
368
- // the bounds proof does not look inside a callee, so every cursor it answers
369
- // is tested for `i < 0` beside `i >= length`: the pair is what lets each read
370
- // below go without a check.
371
- while (i >= 0 && i < length && toI32(object.charCodeAt(i)) === JSON_QUOTE) {
372
- const keyEnd: i32 = jsonEndOfString(object, i)
373
- if (keyEnd < 0) {
374
- return null
375
- }
376
- const key: string = jsonUnescape(object, i + 1, keyEnd - 1)
377
- i = jsonSkipBlank(object, keyEnd)
378
- if (i < 0 || i >= length || toI32(object.charCodeAt(i)) !== JSON_COLON) {
379
- return null
589
+ const member: JsonMember = new JsonMember()
590
+ let at: i32 = jsonFirstKey(object)
591
+ while (jsonReadMember(object, at, member)) {
592
+ // The key is compared where it stands rather than unescaped into a string
593
+ // first: every key before the one asked for is a no, and a no should not
594
+ // cost an allocation.
595
+ if (jsonKeyEquals(object, member.keyAt, member.keyEnd, name)) {
596
+ return jsonValueText(object, member)
380
597
  }
381
- const valueAt: i32 = jsonSkipBlank(object, i + 1)
382
- const valueEnd: i32 = jsonEndOfValue(object, valueAt)
383
- // `jsonEndOfValue` answers `-1` for a `valueAt` outside the object and never
384
- // answers past its end, so the last three tests change no answer: they are
385
- // the range the value's first byte and its `substring` are proved in.
386
- if (valueEnd < 0 || valueAt < 0 || valueAt >= length || valueEnd > toI32(object.length)) {
387
- return null
388
- }
389
- if (key === name) {
390
- if (toI32(object.charCodeAt(valueAt)) === JSON_QUOTE) {
391
- return jsonUnescape(object, valueAt + 1, valueEnd - 1)
598
+ at = member.next
599
+ }
600
+ return null
601
+ }
602
+
603
+ /**
604
+ * The value of every one of `names` in `object`, in the order of `names`: slot
605
+ * `k` is exactly what `jsonField(object, names[k])` answers.
606
+ *
607
+ * It is for a caller that wants several fields of one line — a diagnostic's
608
+ * `code`, `line` and `message` — and would otherwise scan the line once per
609
+ * field. This scans it once, left to right, and stops as soon as every name has
610
+ * answered: a speed path and nothing more, since a slot is filled only once
611
+ * and reading on could change no answer. Each slot keeps the **first** field of its name, as `jsonField`
612
+ * does; a name asked for twice answers in both slots; and a malformed object
613
+ * answers in each slot what `jsonField` answers for that name, because both
614
+ * stop at the same member.
615
+ *
616
+ * The answer is a fresh array and the only allocation besides the values. Each
617
+ * value is stored into it by a plain `values[k] = text` statement, and the
618
+ * array is only stored into, tested and returned, so the values travel with
619
+ * the array: the call lets nothing else out (LANGUAGE.md, "Memory model", a
620
+ * value stored into an array that is returned with it). A loop calling this
621
+ * keeps its per-pass release on a line older than the pass, as `jsonField`'s
622
+ * does, and a `using a = arena()` block may hold the call. On a line built in
623
+ * the pass neither reader keeps the release (LANGUAGE.md, "Memory model").
624
+ * Reading `values` back, passing it to a function, or storing it anywhere
625
+ * before the return would undo this; `tests/link/std_json` pins the flat
626
+ * arena and the `using` block.
627
+ */
628
+ export const jsonFields = (object: string, names: string[]): (string | null)[] => {
629
+ const count: i32 = toI32(names.length)
630
+ const values: (string | null)[] = new Array<string | null>(count)
631
+ const member: JsonMember = new JsonMember()
632
+ let missing: i32 = count
633
+ let at: i32 = jsonFirstKey(object)
634
+ while (missing > 0 && jsonReadMember(object, at, member)) {
635
+ // A value is built once per member however many names it answers, and
636
+ // only when one of them does.
637
+ let text: string | null = null
638
+ let k: i32 = 0
639
+ // Bounded by both lengths, which are the same, because the bounds proof
640
+ // follows each array by its own length and not by `count`.
641
+ while (k < toI32(values.length) && k < toI32(names.length)) {
642
+ if (values[k] === null && jsonKeyEquals(object, member.keyAt, member.keyEnd, names[k])) {
643
+ if (text === null) {
644
+ text = jsonValueText(object, member)
645
+ }
646
+ // Always true: the loop's own test. A call stands between it and the
647
+ // store, and a call is where the bounds proof forgets a length.
648
+ if (k < toI32(values.length)) {
649
+ values[k] = text
650
+ }
651
+ missing -= 1
392
652
  }
393
- return object.substring(valueAt, valueEnd)
394
- }
395
- i = jsonSkipBlank(object, valueEnd)
396
- if (i < 0 || i >= length || toI32(object.charCodeAt(i)) !== JSON_COMMA) {
397
- return null
653
+ k += 1
398
654
  }
399
- i = jsonSkipBlank(object, i + 1)
655
+ at = member.next
400
656
  }
401
- return null
657
+ return values
402
658
  }
@@ -53,9 +53,9 @@
53
53
  * configuration's streams and buffers and the `Http3Config`'s field section,
54
54
  * body chunk and DATA frame. The constructor makes every slot's connections;
55
55
  * a datagram an established connection reads, and every datagram it sends,
56
- * allocates nothing. What does allocate is a new connection's — the listener's
57
- * work on a datagram no slot owns, and the QUIC handshake (TLS-3) —
58
- * `docs/security/http3.md` lists it.
56
+ * allocates nothing; nor does a datagram no slot owns, which the listener
57
+ * reads where it lies and answers into scratch made once, nor a handshake,
58
+ * whose state is the slot's — `docs/security/http3.md` (H3-1, H3-3).
59
59
  *
60
60
  * Written from RFC 9114 §3 and RFC 9000 §5.2, not ported from another
61
61
  * implementation. Private names carry the `http3Server` prefix
@@ -76,6 +76,7 @@ import {
76
76
  import { QUIC_MAX_CID_LENGTH } from "nish/net/quic-packet"
77
77
  import {
78
78
  QUIC_LISTEN_ACCEPT,
79
+ QUIC_LISTENER_ANSWER_SIZE,
79
80
  QUIC_LISTENER_FLIGHT_MAX,
80
81
  QUIC_LISTENER_MIN_RESET,
81
82
  QuicFlight,
@@ -84,7 +85,7 @@ import {
84
85
  quicListenerPaceTime,
85
86
  quicListenerTakeFlight,
86
87
  } from "nish/net/quic-listener"
87
- import { tlsSignEcdsaP256 } from "nish/net/tls"
88
+ import { TlsP256Signer } from "nish/net/tls"
88
89
 
89
90
  /** The timer wheel's buckets, one millisecond each: its horizon. */
90
91
  export const H3_SERVER_WHEEL: i32 = 2048
@@ -397,6 +398,8 @@ export class Http3Wheel {
397
398
  export class Http3Server {
398
399
  quicConfig: QuicServerConfig
399
400
  listener: QuicListener
401
+ /** What the listener answers into, made once: its reply and IDs are refilled in their own room. */
402
+ answer: QuicListenerAnswer
400
403
  quics: QuicConnection[]
401
404
  conns: Http3Connection[]
402
405
  index: Http3CidIndex
@@ -410,6 +413,8 @@ export class Http3Server {
410
413
  meta: i32[]
411
414
  entropy: u8[]
412
415
  flight: QuicFlight
416
+ /** Signs every slot's CertificateVerify in `serve`, one for the server, keeping nothing per signature. */
417
+ signer: TlsP256Signer
413
418
  /** Each slot's connection-ID index signature, so it is refiled only when its IDs changed. */
414
419
  signatures: i64[]
415
420
  state: i32[]
@@ -450,6 +455,7 @@ export class Http3Server {
450
455
  this.fd = fd
451
456
  this.index = new Http3CidIndex(size, listenerEntropy)
452
457
  this.listener = new QuicListener(quicConfig, listenerEntropy)
458
+ this.answer = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE)
453
459
  this.wheel = new Http3Wheel(size)
454
460
  this.quics = []
455
461
  this.conns = []
@@ -467,6 +473,7 @@ export class Http3Server {
467
473
  this.from = new Array<u8>(H3_SERVER_ADDRESS)
468
474
  this.meta = [H3_SERVER_ZERO, H3_SERVER_ZERO]
469
475
  this.flight = new QuicFlight()
476
+ this.signer = new TlsP256Signer()
470
477
  this.signatures = new Array<i64>(size)
471
478
  this.state = new Array<i32>(size)
472
479
  this.readyRing = new Array<i32>(size)
@@ -605,17 +612,17 @@ export class Http3Server {
605
612
  this.unowned(buf, at, len, address, now, key)
606
613
  }
607
614
 
608
- /** A datagram no slot owns: the listener decides, and may make a connection in a free slot. */
615
+ /**
616
+ * A datagram no slot owns: the listener decides, reading it where it lies
617
+ * in `buf` and answering into `answer`, and may make a connection in a
618
+ * free slot, which reads the same window. Nothing is copied (H3-3).
619
+ */
609
620
  unowned(buf: u8[], at: i32, len: i32, address: u8[], now: i64, key: Secret<u8[]>): void {
610
621
  if (!this.worthListening(buf, at, len, now)) {
611
622
  return
612
623
  }
613
- const datagram: u8[] = new Array<u8>(len)
614
- for (let k: i32 = 0; k < len && k < toI32(datagram.length) && at + k < toI32(buf.length); k++) {
615
- datagram[k] = buf[at + k]
616
- }
617
- const answer: QuicListenerAnswer = this.listener.handle(datagram, address, now)
618
- if (answer.kind !== QUIC_LISTEN_ACCEPT) {
624
+ const answer: QuicListenerAnswer = this.answer
625
+ if (this.listener.handleWindow(buf, at, len, address, now, answer) !== QUIC_LISTEN_ACCEPT) {
619
626
  const reply: i32 = toI32(answer.reply.length)
620
627
  if (reply > 0) {
621
628
  this.send(answer.reply, H3_SERVER_ZERO, reply, address, H3_SERVER_ZERO)
@@ -646,9 +653,8 @@ export class Http3Server {
646
653
 
647
654
  /**
648
655
  * Whether the listener could answer `buf[at .. at + len)` at all, read
649
- * from its first bytes: what it would drop without a word, it costs
650
- * nothing to drop here, before the copy and the parse the listener makes
651
- * (H3-3). That is a short header too short for a stateless reset; a long
656
+ * from its first bytes: what it would drop without a word is dropped
657
+ * here, before the listener parses it. That is a short header too short for a stateless reset; a long
652
658
  * one shorter than a full Initial, which neither Version Negotiation nor a
653
659
  * new connection answers (RFC 9000 §14.1, §6.1); version 0; a version 1
654
660
  * packet that is not an Initial, or whose DCID is under 8 bytes (§7.2);
@@ -686,7 +692,7 @@ export class Http3Server {
686
692
  * `H3_SERVER_ANSWER_BURST` at once, then one per
687
693
  * `H3_SERVER_ANSWER_INTERVAL` milliseconds — topping it up first. Past it
688
694
  * the datagram is dropped before the listener sees it, so a flood of
689
- * garbage costs nothing (H3-3); counted in `limited`.
695
+ * garbage costs no answer and no MAC past the budget (H3-3); counted in `limited`.
690
696
  */
691
697
  takeAnswer(now: i64): boolean {
692
698
  if (this.answerRefilled < 0 || now < this.answerRefilled) {
@@ -713,7 +719,7 @@ export class Http3Server {
713
719
  quic.receiveWindow(buf, at, len, now)
714
720
  const input: u8[] | null = quic.signatureInput()
715
721
  if (input !== null) {
716
- const signature: u8[] | null = tlsSignEcdsaP256(key, input)
722
+ const signature: u8[] | null = this.signer.sign(key, input)
717
723
  if (signature !== null) {
718
724
  quic.sign(signature)
719
725
  }
@@ -126,6 +126,42 @@ export class QuicTransportParameters {
126
126
  }
127
127
  }
128
128
 
129
+ /** Empties `to`, keeping its room. */
130
+ const quicParamsEmpty = (to: u8[]): void => {
131
+ while (to.length > 0) {
132
+ to.pop()
133
+ }
134
+ }
135
+
136
+ /** Sets `p` back to what a new `QuicTransportParameters` holds, keeping its arrays, emptied. */
137
+ const quicParamsDefaults = (p: QuicTransportParameters): void => {
138
+ p.error = 0
139
+ p.maxIdleTimeout = 0
140
+ p.maxUdpPayloadSize = 65527
141
+ p.initialMaxData = 0
142
+ p.initialMaxStreamDataBidiLocal = 0
143
+ p.initialMaxStreamDataBidiRemote = 0
144
+ p.initialMaxStreamDataUni = 0
145
+ p.initialMaxStreamsBidi = 0
146
+ p.initialMaxStreamsUni = 0
147
+ p.ackDelayExponent = 3
148
+ p.maxAckDelay = 25
149
+ p.activeConnectionIdLimit = 2
150
+ p.maxDatagramFrameSize = 0
151
+ p.errorParameter = 0
152
+ quicParamsEmpty(p.originalDcid)
153
+ quicParamsEmpty(p.statelessResetToken)
154
+ quicParamsEmpty(p.preferredAddress)
155
+ quicParamsEmpty(p.initialScid)
156
+ quicParamsEmpty(p.retryScid)
157
+ p.hasOriginalDcid = false
158
+ p.hasStatelessResetToken = false
159
+ p.hasPreferredAddress = false
160
+ p.hasInitialScid = false
161
+ p.hasRetryScid = false
162
+ p.disableActiveMigration = false
163
+ }
164
+
129
165
  /** Appends one parameter whose value is a varint, when it differs from `fallback`, its default. */
130
166
  const quicParamsPushVarint = (out: u8[], id: i32, value: i64, fallback: i64): void => {
131
167
  if (value === fallback || quicVarintSize(value) === 0) {
@@ -158,6 +194,18 @@ const quicParamsPushBytes = (out: u8[], id: i32, bytes: u8[], present: boolean):
158
194
  */
159
195
  export const quicEncodeTransportParameters = (p: QuicTransportParameters): u8[] => {
160
196
  const out: u8[] = []
197
+ quicEncodeTransportParametersInto(p, out)
198
+ return out
199
+ }
200
+
201
+ /**
202
+ * `quicEncodeTransportParameters` into `out`, which is emptied first: the
203
+ * encoding is pushed into the room the array already has, so a connection
204
+ * slot that keeps one array for its parameters grows it only when they
205
+ * outgrow every earlier connection's.
206
+ */
207
+ export const quicEncodeTransportParametersInto = (p: QuicTransportParameters, out: u8[]): void => {
208
+ quicParamsEmpty(out)
161
209
  quicParamsPushBytes(out, QUIC_TP_ORIGINAL_DCID, p.originalDcid, p.hasOriginalDcid)
162
210
  quicParamsPushVarint(out, QUIC_TP_MAX_IDLE_TIMEOUT, p.maxIdleTimeout, 0)
163
211
  quicParamsPushBytes(out, QUIC_TP_STATELESS_RESET_TOKEN, p.statelessResetToken, p.hasStatelessResetToken)
@@ -177,19 +225,20 @@ export const quicEncodeTransportParameters = (p: QuicTransportParameters): u8[]
177
225
  quicParamsPushBytes(out, QUIC_TP_INITIAL_SCID, p.initialScid, p.hasInitialScid)
178
226
  quicParamsPushBytes(out, QUIC_TP_RETRY_SCID, p.retryScid, p.hasRetryScid)
179
227
  quicParamsPushVarint(out, QUIC_TP_MAX_DATAGRAM_FRAME_SIZE, p.maxDatagramFrameSize, 0)
180
- return out
181
228
  }
182
229
 
183
- /** The bytes `data[from .. from + length)`, copied; the caller has checked the window. */
184
- const quicParamsCopy = (data: u8[], from: i32, length: i32): u8[] => {
185
- const out: u8[] = new Array<u8>(length)
186
- const n: i32 = toI32(out.length)
187
- for (let k: i32 = 0; k < n; k += 1) {
230
+ /**
231
+ * `bytes[from .. from + length)` into `to`, which is emptied first and
232
+ * refilled in the room it has: a parameters object reused for connection
233
+ * after connection keeps its arrays. The caller has checked the window.
234
+ */
235
+ const quicParamsCopyInto = (to: u8[], data: u8[], from: i32, length: i32): void => {
236
+ quicParamsEmpty(to)
237
+ for (let k: i32 = 0; k < length; k += 1) {
188
238
  if (from + k >= 0 && from + k < toI32(data.length)) {
189
- out[k] = data[from + k]
239
+ to.push(data[from + k])
190
240
  }
191
241
  }
192
- return out
193
242
  }
194
243
 
195
244
  /** Fails `p` over parameter `id` and answers it. */
@@ -293,30 +342,35 @@ const quicParamsStoreBytes = (
293
342
  at: i32,
294
343
  length: i32
295
344
  ): boolean => {
345
+ // A connection ID or reset token is copied only once its length is one the
346
+ // parameter allows, so a reused object's arrays never grow past it whatever
347
+ // a peer sends. `preferred_address` is server-only and so never read from a
348
+ // client into a server's slot; its length is the server's own.
349
+ const cidFits: boolean = length <= QUIC_MAX_CID_LENGTH
296
350
  switch (id) {
297
351
  case QUIC_TP_ORIGINAL_DCID:
298
- p.originalDcid = quicParamsCopy(bytes, at, length)
352
+ quicParamsCopyInto(p.originalDcid, bytes, at, cidFits ? length : 0)
299
353
  p.hasOriginalDcid = true
300
- return length <= QUIC_MAX_CID_LENGTH
354
+ return cidFits
301
355
  case QUIC_TP_STATELESS_RESET_TOKEN:
302
- p.statelessResetToken = quicParamsCopy(bytes, at, length)
356
+ quicParamsCopyInto(p.statelessResetToken, bytes, at, length === QUIC_RESET_TOKEN_SIZE ? length : 0)
303
357
  p.hasStatelessResetToken = true
304
358
  return length === QUIC_RESET_TOKEN_SIZE
305
359
  case QUIC_TP_DISABLE_ACTIVE_MIGRATION:
306
360
  p.disableActiveMigration = true
307
361
  return length === 0
308
362
  case QUIC_TP_PREFERRED_ADDRESS:
309
- p.preferredAddress = quicParamsCopy(bytes, at, length)
363
+ quicParamsCopyInto(p.preferredAddress, bytes, at, length)
310
364
  p.hasPreferredAddress = true
311
365
  return true
312
366
  case QUIC_TP_INITIAL_SCID:
313
- p.initialScid = quicParamsCopy(bytes, at, length)
367
+ quicParamsCopyInto(p.initialScid, bytes, at, cidFits ? length : 0)
314
368
  p.hasInitialScid = true
315
- return length <= QUIC_MAX_CID_LENGTH
369
+ return cidFits
316
370
  default:
317
- p.retryScid = quicParamsCopy(bytes, at, length)
371
+ quicParamsCopyInto(p.retryScid, bytes, at, cidFits ? length : 0)
318
372
  p.hasRetryScid = true
319
- return length <= QUIC_MAX_CID_LENGTH
373
+ return cidFits
320
374
  }
321
375
  }
322
376
 
@@ -336,6 +390,22 @@ const quicParamsIsVarint = (id: i32): boolean =>
336
390
  */
337
391
  export const quicParseTransportParameters = (bytes: u8[], fromServer: boolean): QuicTransportParameters => {
338
392
  const p: QuicTransportParameters = new QuicTransportParameters()
393
+ quicParseTransportParametersInto(p, bytes, fromServer)
394
+ return p
395
+ }
396
+
397
+ /**
398
+ * `quicParseTransportParameters` into `p`, which is set back to every
399
+ * default first, its byte-valued fields emptied and refilled in the room
400
+ * their arrays have: a connection slot parses each client's parameters into
401
+ * one object of its own. Answers `p`.
402
+ */
403
+ export const quicParseTransportParametersInto = (
404
+ p: QuicTransportParameters,
405
+ bytes: u8[],
406
+ fromServer: boolean
407
+ ): QuicTransportParameters => {
408
+ quicParamsDefaults(p)
339
409
  const end: i32 = toI32(bytes.length)
340
410
  // The known IDs are 0 to 16, so one bit each of an i32 records which were
341
411
  // seen; RFC 9221's 0x20 has a flag of its own.