@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/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.h +20 -6
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +11 -0
- package/runtime/runtime.c +20 -10
- package/scripts/build.sh +13 -9
- package/std/README.md +9 -8
- package/std/crypto/p256.ts +473 -10
- package/std/json.ts +297 -41
- package/std/net/http3-server.ts +22 -16
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +489 -213
- package/std/net/quic-packet.ts +226 -2
- package/std/net/quic-stream.ts +10 -4
- package/std/net/quic.ts +484 -120
- package/std/net/tls-tcp.ts +8 -4
- package/std/net/tls.ts +114 -45
- package/std/text.ts +77 -0
package/std/net/quic-listener.ts
CHANGED
|
@@ -7,19 +7,27 @@
|
|
|
7
7
|
* (§10.3). Sans-IO, like the rest of `nish/net`, and stateless in the sense
|
|
8
8
|
* the RFC means: nothing it answers depends on any connection.
|
|
9
9
|
*
|
|
10
|
-
* import { QuicListener, QuicListenerAnswer, QUIC_LISTEN_ACCEPT } from "nish/net/quic-listener";
|
|
10
|
+
* import { QuicListener, QuicListenerAnswer, QUIC_LISTEN_ACCEPT, QUIC_LISTENER_ANSWER_SIZE } from "nish/net/quic-listener";
|
|
11
11
|
*
|
|
12
12
|
* const listener = new QuicListener(config, entropy); // entropy: QUIC_LISTENER_ENTROPY_SIZE random bytes
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* if (
|
|
13
|
+
* const answer = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE); // once, beside the listener
|
|
14
|
+
* // For a datagram buf[at .. at + len) whose first packet no connection owns:
|
|
15
|
+
* if (listener.handleWindow(buf, at, len, fromAddress, now, answer) === QUIC_LISTEN_ACCEPT) {
|
|
16
16
|
* const conn = new QuicConnection(config, connectionEntropy);
|
|
17
17
|
* if (answer.retried) { conn.acceptRetry(answer.originalDcid, answer.retryScid); }
|
|
18
|
-
* conn.
|
|
18
|
+
* conn.receiveWindow(buf, at, len, now);
|
|
19
19
|
* } else if (toI32(answer.reply.length) > 0) {
|
|
20
20
|
* … send answer.reply back to fromAddress …
|
|
21
21
|
* }
|
|
22
22
|
*
|
|
23
|
+
* **Nothing is kept per datagram** (H3-3 in `docs/security/http3.md`).
|
|
24
|
+
* `handleWindow` reads the datagram where it lies, its header parsed in
|
|
25
|
+
* place, and writes every answer into the caller's `QuicListenerAnswer`,
|
|
26
|
+
* whose arrays were given their room once; every MAC, key and seal is
|
|
27
|
+
* computed in scratch the listener made at start-up. `handle` is the same
|
|
28
|
+
* over a whole array, into an answer of its own each time, for a caller
|
|
29
|
+
* that keeps them.
|
|
30
|
+
*
|
|
23
31
|
* **Routing is the caller's.** The listener sees only datagrams the caller
|
|
24
32
|
* could not hand to a live connection, and it must see only those: a stateless
|
|
25
33
|
* reset carries the token for the packet's connection ID, and handing the
|
|
@@ -39,7 +47,7 @@
|
|
|
39
47
|
* server issued, is treated as no token at all (§8.1.3).
|
|
40
48
|
*
|
|
41
49
|
* **Stateless resets** (§10.3) end in the token `quicStatelessResetToken`
|
|
42
|
-
* derives from the configuration's static key and the packet's connection
|
|
50
|
+
* derives (computed in place, by `quicConnResetTokenInto`) from the configuration's static key and the packet's connection
|
|
43
51
|
* ID, so a server that restarted with the same key can end the connections
|
|
44
52
|
* it lost. RFC 9000 §10.3 and §10.3.3 bound them, and each bound is held
|
|
45
53
|
* here. A reset is sent only for a short header, and is always shorter than
|
|
@@ -95,8 +103,10 @@
|
|
|
95
103
|
* ported from another implementation. Private names carry the
|
|
96
104
|
* `quicListener` prefix (`docs/wp26-stdlib.md` §3e).
|
|
97
105
|
*/
|
|
98
|
-
import {
|
|
99
|
-
import {
|
|
106
|
+
import { AesKey, aesGcmSeal, aesKeyInto } from "nish/crypto/aes"
|
|
107
|
+
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
108
|
+
import { HkdfScratch } from "nish/crypto/hkdf"
|
|
109
|
+
import { HmacSha256Scratch } from "nish/crypto/hmac"
|
|
100
110
|
import {
|
|
101
111
|
QUIC_AEAD_AES_128_GCM,
|
|
102
112
|
QUIC_ERR_VERSION,
|
|
@@ -104,26 +114,29 @@ import {
|
|
|
104
114
|
QUIC_PACKET_INITIAL,
|
|
105
115
|
QUIC_PACKET_OK,
|
|
106
116
|
QUIC_PACKET_SHORT,
|
|
117
|
+
QUIC_RETRY_TAG_SIZE,
|
|
107
118
|
QuicHeader,
|
|
108
|
-
QuicInitialSecrets,
|
|
109
119
|
QuicKeys,
|
|
120
|
+
QuicKeysSlot,
|
|
110
121
|
QuicPacket,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
quicDecryptPayload,
|
|
123
|
+
quicInitialSecretsInto,
|
|
124
|
+
quicKeysInto,
|
|
125
|
+
quicLongHeaderSize,
|
|
126
|
+
quicPacketCopy,
|
|
127
|
+
quicParseHeaderInto,
|
|
128
|
+
quicPutLongHeader,
|
|
129
|
+
quicSealInPlace,
|
|
130
|
+
quicUnprotectHeader,
|
|
118
131
|
} from "nish/net/quic-packet"
|
|
119
|
-
import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE,
|
|
132
|
+
import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE, quicPutConnectionClose } from "nish/net/quic-frame"
|
|
120
133
|
import {
|
|
121
134
|
QUIC_CONN_CID_LENGTH,
|
|
122
135
|
QUIC_CONN_DATAGRAM_SIZE,
|
|
123
136
|
QUIC_CONN_STATIC_KEY_SIZE,
|
|
124
137
|
QuicConnection,
|
|
125
138
|
QuicServerConfig,
|
|
126
|
-
|
|
139
|
+
quicConnResetTokenInto,
|
|
127
140
|
} from "nish/net/quic"
|
|
128
141
|
|
|
129
142
|
// ---- What `handle` answers ----------------------------------------------------
|
|
@@ -165,12 +178,33 @@ const QUIC_LISTENER_TOKEN_MAC_SIZE: i32 = 16
|
|
|
165
178
|
const QUIC_LISTENER_TOKEN_HEAD: i32 = 10
|
|
166
179
|
/** Up to this datagram length a reset is exactly one byte shorter (RFC 9000 §10.3). */
|
|
167
180
|
const QUIC_LISTENER_SHORT_TRIGGER: i32 = 43
|
|
181
|
+
/** The generator's block, one HMAC-SHA256 tag: the bytes `refill` makes at a time. */
|
|
182
|
+
const QUIC_LISTENER_POOL: i32 = 32
|
|
183
|
+
/** The generator's message: its counter, eight bytes. */
|
|
184
|
+
const QUIC_LISTENER_BLOCK: i32 = 8
|
|
185
|
+
/** A typed 0, the start of a whole array, since a bare literal is an `f64` under `--number-mode f64`. */
|
|
186
|
+
const QUIC_LISTENER_FROM: i32 = 0
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The largest answer the listener sends, and so the room `QuicListenerAnswer`
|
|
190
|
+
* makes for `reply`: a stateless reset is at most a full datagram (RFC 9000
|
|
191
|
+
* §10.3); a Version Negotiation packet, which echoes two IDs of up to 255
|
|
192
|
+
* bytes, a Retry and the INVALID_TOKEN close are all shorter.
|
|
193
|
+
*/
|
|
194
|
+
export const QUIC_LISTENER_ANSWER_SIZE: i32 = QUIC_CONN_DATAGRAM_SIZE
|
|
168
195
|
|
|
169
196
|
/**
|
|
170
|
-
* What `QuicListener.
|
|
171
|
-
* `QUIC_LISTEN_*`, and `reply` the datagram to send back, empty when there
|
|
172
|
-
* none. For `QUIC_LISTEN_ACCEPT` after a Retry, `retried` is set and
|
|
197
|
+
* What `QuicListener.handleWindow` decided about one datagram: `kind` is a
|
|
198
|
+
* `QUIC_LISTEN_*`, and `reply` the datagram to send back, empty when there
|
|
199
|
+
* is none. For `QUIC_LISTEN_ACCEPT` after a Retry, `retried` is set and
|
|
173
200
|
* `originalDcid` and `retryScid` are what `QuicConnection.acceptRetry` takes.
|
|
201
|
+
*
|
|
202
|
+
* Make one once, with `QUIC_LISTENER_ANSWER_SIZE` as its `room`, and hand it
|
|
203
|
+
* to every `handleWindow`: the constructor gives `reply` that room and each
|
|
204
|
+
* ID 20 bytes, and each answer refills them in it, its length the answer's,
|
|
205
|
+
* so answering allocates nothing (H3-3). A smaller room still works, and
|
|
206
|
+
* grows the first time an answer needs more; `handle` makes each answer
|
|
207
|
+
* with exactly the room its reply takes.
|
|
174
208
|
*/
|
|
175
209
|
export class QuicListenerAnswer {
|
|
176
210
|
reply: u8[]
|
|
@@ -179,59 +213,66 @@ export class QuicListenerAnswer {
|
|
|
179
213
|
kind: i32 = 0
|
|
180
214
|
retried: boolean = false
|
|
181
215
|
|
|
182
|
-
constructor() {
|
|
183
|
-
this.reply =
|
|
184
|
-
this.originalDcid =
|
|
185
|
-
this.retryScid =
|
|
216
|
+
constructor(room: i32) {
|
|
217
|
+
this.reply = quicListenerRoom(room > 0 ? room : 0)
|
|
218
|
+
this.originalDcid = quicListenerRoom(QUIC_MAX_CID_LENGTH)
|
|
219
|
+
this.retryScid = quicListenerRoom(QUIC_MAX_CID_LENGTH)
|
|
186
220
|
}
|
|
187
221
|
}
|
|
188
222
|
|
|
189
|
-
/**
|
|
190
|
-
const
|
|
191
|
-
const out: u8[] = new Array<u8>(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (from + k >= 0 && from + k < toI32(bytes.length)) {
|
|
195
|
-
out[k] = bytes[from + k]
|
|
196
|
-
}
|
|
223
|
+
/** An empty array with room for `size` bytes, so pushing up to that many allocates nothing. */
|
|
224
|
+
const quicListenerRoom = (size: i32): u8[] => {
|
|
225
|
+
const out: u8[] = new Array<u8>(size)
|
|
226
|
+
while (out.length > 0) {
|
|
227
|
+
out.pop()
|
|
197
228
|
}
|
|
198
229
|
return out
|
|
199
230
|
}
|
|
200
231
|
|
|
201
|
-
/**
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
/** Sets `bytes`' length to `length` in the room it already has: popped, or pushed with zeros. */
|
|
233
|
+
const quicListenerResize = (bytes: u8[], length: i32): void => {
|
|
234
|
+
while (toI32(bytes.length) > length) {
|
|
235
|
+
bytes.pop()
|
|
205
236
|
}
|
|
237
|
+
while (toI32(bytes.length) < length) {
|
|
238
|
+
bytes.push(toU8(0))
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Puts `from[at .. at + length)` in `to`, resized to `length` in its own room. */
|
|
243
|
+
const quicListenerRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
|
|
244
|
+
quicListenerResize(to, length)
|
|
245
|
+
quicPacketCopy(to, 0, from, at, length)
|
|
206
246
|
}
|
|
207
247
|
|
|
208
|
-
/**
|
|
209
|
-
const
|
|
210
|
-
for (let k: i32 =
|
|
211
|
-
|
|
248
|
+
/** Writes `value`'s low `size` bytes into `buf` at `at`, big-endian. */
|
|
249
|
+
const quicListenerPutNumber = (buf: u8[], at: i32, value: i64, size: i32): void => {
|
|
250
|
+
for (let k: i32 = 0; k < size; k += 1) {
|
|
251
|
+
if (at + k >= 0 && at + k < toI32(buf.length)) {
|
|
252
|
+
buf[at + k] = toU8(toI32((value >> (toI64(size - 1 - k) * 8)) & 255))
|
|
253
|
+
}
|
|
212
254
|
}
|
|
213
255
|
}
|
|
214
256
|
|
|
215
257
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
258
|
+
* RFC 9001 §5.8's fixed AES-128-GCM key and nonce for version 1's Retry
|
|
259
|
+
* integrity tag: the listener keeps the key expanded, so a Retry's tag is
|
|
260
|
+
* computed without `quicRetryIntegrityTag`'s expansion, which stores what it
|
|
261
|
+
* allocates and so cannot run in an arena block.
|
|
220
262
|
*
|
|
221
|
-
* TODO(WP34 Q1):
|
|
222
|
-
*
|
|
263
|
+
* TODO(WP34 Q1): `nish/net/quic-packet` holds the same two constants and
|
|
264
|
+
* should offer the tag over a kept key; this stage may not edit it.
|
|
223
265
|
*/
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
266
|
+
const quicListenerRetryKey = (): u8[] => [
|
|
267
|
+
0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e,
|
|
268
|
+
]
|
|
269
|
+
const quicListenerRetryNonce = (): u8[] => [
|
|
270
|
+
0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
/** Whether `[at, at + len)` is a window inside an array of `length` elements: `p256WindowFits`' test. */
|
|
274
|
+
const quicListenerWindowFits = (length: i32, at: i32, len: i32): boolean =>
|
|
275
|
+
at >= 0 && len >= 0 && at <= length - len
|
|
235
276
|
|
|
236
277
|
/**
|
|
237
278
|
* The server's answers to datagrams no connection owns, under one
|
|
@@ -243,12 +284,42 @@ const quicListenerVersionNegotiation = (dcid: u8[], scid: u8[], unused: i32): u8
|
|
|
243
284
|
* `QUIC_CONN_STATIC_KEY_SIZE` bytes, or whose `retryTokenLifetime` is outside
|
|
244
285
|
* 1 to `QUIC_LISTENER_MAX_TOKEN_LIFETIME`, or entropy of another length, make
|
|
245
286
|
* a listener that drops everything.
|
|
287
|
+
*
|
|
288
|
+
* Everything an answer is computed in is made here, once: the header a
|
|
289
|
+
* datagram is parsed into, the HMAC and HKDF scratch, the Initial secrets
|
|
290
|
+
* and key slots of the INVALID_TOKEN close, and the generator's block. So
|
|
291
|
+
* `handleWindow` reads the datagram where it lies and keeps nothing.
|
|
246
292
|
*/
|
|
247
293
|
export class QuicListener {
|
|
248
294
|
config: QuicServerConfig
|
|
249
295
|
seed: u8[]
|
|
250
296
|
/** Unpredictable bytes not yet used, from `poolHead`, and the counter the next block is made from. */
|
|
251
297
|
pool: u8[]
|
|
298
|
+
/** The counter's eight bytes, the generator's HMAC message. */
|
|
299
|
+
block: u8[]
|
|
300
|
+
/** What `handleWindow` parses a datagram's header into, in place. */
|
|
301
|
+
header: QuicHeader
|
|
302
|
+
/** Every HMAC and HKDF the listener computes runs here; its `sha256` makes tokens, resets and the generator's blocks. */
|
|
303
|
+
kdf: HkdfScratch
|
|
304
|
+
/** A full HMAC-SHA256 tag, wiped once its 16 bytes are used. */
|
|
305
|
+
tag: u8[]
|
|
306
|
+
/** A Retry token MAC's length fields: the Retry SCID's one byte, the address's four. */
|
|
307
|
+
lengths: u8[]
|
|
308
|
+
/** The Initial secrets of an INVALID_TOKEN close, and its keys, both directions. */
|
|
309
|
+
initialClient: u8[]
|
|
310
|
+
initialServer: u8[]
|
|
311
|
+
readSlot: QuicKeysSlot
|
|
312
|
+
writeSlot: QuicKeysSlot
|
|
313
|
+
packet: QuicPacket
|
|
314
|
+
/** The client's SCID and the DCID it sent to, copied out for `quicPutLongHeader`, which reads IDs from 0. */
|
|
315
|
+
peerCid: u8[]
|
|
316
|
+
ownCid: u8[]
|
|
317
|
+
/** An empty array: the INVALID_TOKEN close's token and reason. */
|
|
318
|
+
none: u8[]
|
|
319
|
+
/** Version 1's fixed Retry key (RFC 9001 §5.8), expanded once. */
|
|
320
|
+
retryKey: AesKey
|
|
321
|
+
/** What `handle` answers into before it copies the answer out. */
|
|
322
|
+
spare: QuicListenerAnswer
|
|
252
323
|
counter: i64 = 0
|
|
253
324
|
/** The latest time the caller gave, in milliseconds; time never runs backwards here. */
|
|
254
325
|
now: i64 = 0
|
|
@@ -265,8 +336,26 @@ export class QuicListener {
|
|
|
265
336
|
/** A listener under `config`; `entropy`'s `QUIC_LISTENER_ENTROPY_SIZE` bytes are copied, then wiped in the caller's array. */
|
|
266
337
|
constructor(config: QuicServerConfig, entropy: u8[]) {
|
|
267
338
|
this.config = config
|
|
268
|
-
this.seed =
|
|
269
|
-
this.
|
|
339
|
+
this.seed = new Array<u8>(QUIC_LISTENER_ENTROPY_SIZE)
|
|
340
|
+
quicPacketCopy(this.seed, 0, entropy, 0, QUIC_LISTENER_ENTROPY_SIZE)
|
|
341
|
+
this.pool = new Array<u8>(QUIC_LISTENER_POOL)
|
|
342
|
+
this.poolHead = QUIC_LISTENER_POOL
|
|
343
|
+
this.block = new Array<u8>(QUIC_LISTENER_BLOCK)
|
|
344
|
+
this.header = new QuicHeader()
|
|
345
|
+
this.kdf = new HkdfScratch()
|
|
346
|
+
this.tag = new Array<u8>(QUIC_LISTENER_POOL)
|
|
347
|
+
this.lengths = new Array<u8>(5)
|
|
348
|
+
this.initialClient = new Array<u8>(32)
|
|
349
|
+
this.initialServer = new Array<u8>(32)
|
|
350
|
+
this.readSlot = new QuicKeysSlot()
|
|
351
|
+
this.writeSlot = new QuicKeysSlot()
|
|
352
|
+
this.packet = new QuicPacket()
|
|
353
|
+
this.peerCid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
354
|
+
this.ownCid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
355
|
+
this.none = []
|
|
356
|
+
this.spare = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE)
|
|
357
|
+
this.retryKey = new AesKey(10, new Array<u64>(88))
|
|
358
|
+
aesKeyInto(quicListenerRetryKey(), this.retryKey)
|
|
270
359
|
this.usable =
|
|
271
360
|
toI32(entropy.length) === QUIC_LISTENER_ENTROPY_SIZE &&
|
|
272
361
|
toI32(config.statelessResetKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
|
|
@@ -276,218 +365,397 @@ export class QuicListener {
|
|
|
276
365
|
secureZero(entropy)
|
|
277
366
|
}
|
|
278
367
|
|
|
279
|
-
/**
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
368
|
+
/**
|
|
369
|
+
* One unpredictable value in 0 to 255: the next byte of HMAC-SHA256 under
|
|
370
|
+
* the seed of a counter, a block at a time, each byte wiped once taken.
|
|
371
|
+
*/
|
|
372
|
+
randomByte(): i32 {
|
|
373
|
+
if (this.poolHead >= QUIC_LISTENER_POOL) {
|
|
374
|
+
this.refill()
|
|
375
|
+
}
|
|
376
|
+
let b: i32 = 0
|
|
377
|
+
if (this.poolHead >= 0 && this.poolHead < toI32(this.pool.length)) {
|
|
378
|
+
b = toI32(this.pool[this.poolHead])
|
|
379
|
+
this.pool[this.poolHead] = toU8(0)
|
|
380
|
+
}
|
|
381
|
+
this.poolHead = this.poolHead + 1
|
|
382
|
+
return b
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Writes `count` unpredictable bytes into `out` at `at`. */
|
|
386
|
+
randomInto(out: u8[], at: i32, count: i32): void {
|
|
387
|
+
for (let k: i32 = 0; k < count; k += 1) {
|
|
388
|
+
const b: i32 = this.randomByte()
|
|
389
|
+
if (at + k >= 0 && at + k < toI32(out.length)) {
|
|
390
|
+
out[at + k] = toU8(b)
|
|
294
391
|
}
|
|
295
|
-
this.poolHead = this.poolHead + 1
|
|
296
392
|
}
|
|
297
|
-
return out
|
|
298
393
|
}
|
|
299
394
|
|
|
300
|
-
/**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
395
|
+
/** The generator's next block, HMAC-SHA256 under the seed of the counter, into `pool`. */
|
|
396
|
+
refill(): void {
|
|
397
|
+
quicListenerPutNumber(this.block, 0, this.counter, QUIC_LISTENER_BLOCK)
|
|
398
|
+
this.counter = this.counter + 1
|
|
399
|
+
const mac: HmacSha256Scratch = this.kdf.sha256
|
|
400
|
+
{
|
|
401
|
+
using _scope = arena()
|
|
402
|
+
mac.begin(this.seed, QUIC_LISTENER_FROM, QUIC_LISTENER_ENTROPY_SIZE)
|
|
403
|
+
mac.update(this.block, QUIC_LISTENER_FROM, QUIC_LISTENER_BLOCK)
|
|
404
|
+
mac.finishInto(this.pool, QUIC_LISTENER_FROM)
|
|
405
|
+
}
|
|
406
|
+
mac.wipe()
|
|
407
|
+
this.poolHead = 0
|
|
305
408
|
}
|
|
306
409
|
|
|
307
410
|
/**
|
|
308
411
|
* Decides what to answer a datagram whose first packet no connection
|
|
309
|
-
* owns,
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
412
|
+
* owns, `datagram[at .. at + len)`, read where it lies, from `address`
|
|
413
|
+
* (the client's address as the caller's socket reports it, port included)
|
|
414
|
+
* at `now` (the caller's monotonic time in milliseconds), into `answer`,
|
|
415
|
+
* whose arrays are refilled in their own room. Answers `answer.kind`. See
|
|
416
|
+
* the module header for the rules; nothing a peer sends makes it panic,
|
|
417
|
+
* and nothing it does keeps arena memory. A window outside `datagram` is
|
|
418
|
+
* the caller's mistake, and panics.
|
|
313
419
|
*/
|
|
314
|
-
|
|
315
|
-
|
|
420
|
+
handleWindow(datagram: u8[], at: i32, len: i32, address: u8[], now: i64, answer: QuicListenerAnswer): i32 {
|
|
421
|
+
if (!quicListenerWindowFits(toI32(datagram.length), at, len)) {
|
|
422
|
+
panic(
|
|
423
|
+
`QuicListener.handleWindow: the window [${at}, ${at} + ${len}) is outside a datagram of ${toI32(datagram.length)} bytes`
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
answer.kind = QUIC_LISTEN_DROP
|
|
427
|
+
answer.retried = false
|
|
428
|
+
quicListenerResize(answer.reply, 0)
|
|
429
|
+
quicListenerResize(answer.originalDcid, 0)
|
|
430
|
+
quicListenerResize(answer.retryScid, 0)
|
|
316
431
|
if (now > this.now) {
|
|
317
432
|
this.now = now
|
|
318
433
|
}
|
|
319
434
|
if (!this.usable) {
|
|
320
|
-
return answer
|
|
435
|
+
return answer.kind
|
|
321
436
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
437
|
+
const header: QuicHeader = this.header
|
|
438
|
+
quicParseHeaderInto(header, datagram, at, at + len, QUIC_CONN_CID_LENGTH)
|
|
324
439
|
if (header.error === QUIC_ERR_VERSION) {
|
|
325
440
|
// §5.2.2: answer an unsupported version only in a datagram that could
|
|
326
441
|
// start a connection, and §6.1: never a Version Negotiation packet.
|
|
327
|
-
if (header.version !== 0 &&
|
|
328
|
-
answer
|
|
329
|
-
answer.reply = quicListenerVersionNegotiation(header.dcid, header.scid, this.randomByte())
|
|
442
|
+
if (header.version !== 0 && len >= QUIC_CONN_DATAGRAM_SIZE) {
|
|
443
|
+
this.versionNegotiation(answer, datagram, header)
|
|
330
444
|
}
|
|
331
|
-
return answer
|
|
445
|
+
return answer.kind
|
|
332
446
|
}
|
|
333
447
|
if (header.error !== QUIC_PACKET_OK) {
|
|
334
|
-
return answer
|
|
448
|
+
return answer.kind
|
|
335
449
|
}
|
|
336
450
|
if (header.type === QUIC_PACKET_SHORT) {
|
|
337
|
-
|
|
451
|
+
this.statelessReset(answer, len, datagram, header.dcidStart, header.dcidLength)
|
|
452
|
+
return answer.kind
|
|
338
453
|
}
|
|
339
454
|
// §14.1, §7.2, §5.2.2: only an Initial in a full-sized datagram, sent to
|
|
340
455
|
// a DCID of at least 8 bytes, can start a connection; the rest is dropped.
|
|
341
|
-
if (header.type !== QUIC_PACKET_INITIAL ||
|
|
342
|
-
return answer
|
|
456
|
+
if (header.type !== QUIC_PACKET_INITIAL || len < QUIC_CONN_DATAGRAM_SIZE || header.dcidLength < 8) {
|
|
457
|
+
return answer.kind
|
|
343
458
|
}
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
459
|
+
const tokenAt: i32 = header.tokenStart
|
|
460
|
+
if (header.tokenLength === 0 || toI32(datagram[tokenAt]) !== QUIC_LISTENER_TOKEN_MARKER) {
|
|
346
461
|
if (!this.config.retry) {
|
|
347
462
|
answer.kind = QUIC_LISTEN_ACCEPT
|
|
348
|
-
return answer
|
|
463
|
+
return answer.kind
|
|
349
464
|
}
|
|
350
|
-
|
|
465
|
+
this.retry(answer, datagram, header, address)
|
|
466
|
+
return answer.kind
|
|
351
467
|
}
|
|
352
|
-
if (this.tokenValid(
|
|
468
|
+
if (this.tokenValid(datagram, header, address)) {
|
|
353
469
|
answer.kind = QUIC_LISTEN_ACCEPT
|
|
354
470
|
answer.retried = true
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
471
|
+
quicListenerRefill(
|
|
472
|
+
answer.originalDcid,
|
|
473
|
+
datagram,
|
|
474
|
+
tokenAt + QUIC_LISTENER_TOKEN_HEAD,
|
|
475
|
+
toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
|
|
359
476
|
)
|
|
360
|
-
answer.retryScid
|
|
361
|
-
return answer
|
|
477
|
+
quicListenerRefill(answer.retryScid, datagram, header.dcidStart, header.dcidLength)
|
|
478
|
+
return answer.kind
|
|
362
479
|
}
|
|
363
|
-
|
|
480
|
+
this.invalidToken(answer, datagram, header)
|
|
481
|
+
return answer.kind
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* `handleWindow` over the whole of `datagram`, into an answer of its own,
|
|
486
|
+
* made with exactly the room its reply takes: for a caller that keeps each
|
|
487
|
+
* answer, at the cost of one `QuicListenerAnswer` a datagram. A carrier
|
|
488
|
+
* makes one answer and calls `handleWindow`.
|
|
489
|
+
*/
|
|
490
|
+
handle(datagram: u8[], address: u8[], now: i64): QuicListenerAnswer {
|
|
491
|
+
const spare: QuicListenerAnswer = this.spare
|
|
492
|
+
this.handleWindow(datagram, QUIC_LISTENER_FROM, toI32(datagram.length), address, now, spare)
|
|
493
|
+
const answer: QuicListenerAnswer = new QuicListenerAnswer(toI32(spare.reply.length))
|
|
494
|
+
answer.kind = spare.kind
|
|
495
|
+
answer.retried = spare.retried
|
|
496
|
+
quicListenerRefill(answer.reply, spare.reply, QUIC_LISTENER_FROM, toI32(spare.reply.length))
|
|
497
|
+
quicListenerRefill(
|
|
498
|
+
answer.originalDcid,
|
|
499
|
+
spare.originalDcid,
|
|
500
|
+
QUIC_LISTENER_FROM,
|
|
501
|
+
toI32(spare.originalDcid.length)
|
|
502
|
+
)
|
|
503
|
+
quicListenerRefill(answer.retryScid, spare.retryScid, QUIC_LISTENER_FROM, toI32(spare.retryScid.length))
|
|
504
|
+
return answer
|
|
364
505
|
}
|
|
365
506
|
|
|
366
507
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
508
|
+
* A Version Negotiation packet (RFC 9000 §17.2.1) into `answer`, answering
|
|
509
|
+
* a packet from the SCID `header` read to its DCID: the IDs swapped,
|
|
510
|
+
* version 0, and version 1 as the only one supported. A random byte fills
|
|
511
|
+
* the first byte's low six bits, under the 0x40 the RFC asks for so the
|
|
512
|
+
* packet looks like QUIC to a demultiplexer. Each ID is at most 255 bytes,
|
|
513
|
+
* so the packet is at most 521, inside `reply`'s room.
|
|
514
|
+
*
|
|
515
|
+
* TODO(WP34 Q1): every other packet format is `nish/net/quic-packet`'s; this
|
|
516
|
+
* belongs beside `quicRetryPacket` there, which this stage may not edit.
|
|
370
517
|
*/
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
518
|
+
versionNegotiation(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): void {
|
|
519
|
+
const unused: i32 = this.randomByte()
|
|
520
|
+
const dcidLength: i32 = header.dcidLength
|
|
521
|
+
const scidLength: i32 = header.scidLength
|
|
522
|
+
const reply: u8[] = answer.reply
|
|
523
|
+
quicListenerResize(reply, 11 + dcidLength + scidLength)
|
|
524
|
+
reply[0] = toU8((unused & 0x3f) | 0xc0)
|
|
525
|
+
quicListenerPutNumber(reply, 1, 0, 4)
|
|
526
|
+
reply[5] = toU8(scidLength)
|
|
527
|
+
quicPacketCopy(reply, 6, datagram, header.scidStart, scidLength)
|
|
528
|
+
const dcidAt: i32 = 6 + scidLength
|
|
529
|
+
quicListenerPutNumber(reply, dcidAt, toI64(dcidLength), 1)
|
|
530
|
+
quicPacketCopy(reply, dcidAt + 1, datagram, header.dcidStart, dcidLength)
|
|
531
|
+
quicListenerPutNumber(reply, dcidAt + 1 + dcidLength, 1, 4)
|
|
532
|
+
answer.kind = QUIC_LISTEN_VERSION_NEGOTIATION
|
|
382
533
|
}
|
|
383
534
|
|
|
384
|
-
/**
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
535
|
+
/**
|
|
536
|
+
* The token MAC over `head[headAt .. headAt + headLength)`, the token's
|
|
537
|
+
* own bytes before its MAC, the ID the client is to send to
|
|
538
|
+
* (`scid[scidAt .. scidAt + scidLength)`) and its address, into `tag`:
|
|
539
|
+
* what makes a token good for one address and one connection only. Its
|
|
540
|
+
* first `QUIC_LISTENER_TOKEN_MAC_SIZE` bytes are the token's MAC; the
|
|
541
|
+
* caller wipes `tag` once it has used them.
|
|
542
|
+
*/
|
|
543
|
+
tokenMac(
|
|
544
|
+
head: u8[],
|
|
545
|
+
headAt: i32,
|
|
546
|
+
headLength: i32,
|
|
547
|
+
scid: u8[],
|
|
548
|
+
scidAt: i32,
|
|
549
|
+
scidLength: i32,
|
|
550
|
+
address: u8[]
|
|
551
|
+
): void {
|
|
552
|
+
const mac: HmacSha256Scratch = this.kdf.sha256
|
|
553
|
+
const lengths: u8[] = this.lengths
|
|
554
|
+
const one: i32 = 1
|
|
555
|
+
const four: i32 = 4
|
|
556
|
+
quicListenerPutNumber(lengths, QUIC_LISTENER_FROM, toI64(scidLength), one)
|
|
557
|
+
quicListenerPutNumber(lengths, one, toI64(toI32(address.length)), four)
|
|
558
|
+
{
|
|
559
|
+
using _scope = arena()
|
|
560
|
+
mac.begin(this.config.retryTokenKey, QUIC_LISTENER_FROM, toI32(this.config.retryTokenKey.length))
|
|
561
|
+
mac.update(head, headAt, headLength)
|
|
562
|
+
mac.update(lengths, QUIC_LISTENER_FROM, one)
|
|
563
|
+
mac.update(scid, scidAt, scidLength)
|
|
564
|
+
mac.update(lengths, one, four)
|
|
565
|
+
mac.update(address, QUIC_LISTENER_FROM, toI32(address.length))
|
|
566
|
+
mac.finishInto(this.tag, QUIC_LISTENER_FROM)
|
|
567
|
+
}
|
|
568
|
+
mac.wipe()
|
|
393
569
|
}
|
|
394
570
|
|
|
395
571
|
/**
|
|
396
|
-
* Whether
|
|
397
|
-
* that was told to send to
|
|
398
|
-
* lengths add up, the MAC matches
|
|
399
|
-
* issued no later than now and
|
|
572
|
+
* Whether the token `header` read is one this listener issued for the
|
|
573
|
+
* client at `address` that was told to send to the DCID `header` read,
|
|
574
|
+
* and still within its lifetime: the lengths add up, the MAC matches
|
|
575
|
+
* (compared in constant time), and it was issued no later than now and
|
|
576
|
+
* less than `retryTokenLifetime` ago. The token is read in `datagram`.
|
|
400
577
|
*/
|
|
401
|
-
tokenValid(
|
|
402
|
-
const
|
|
578
|
+
tokenValid(datagram: u8[], header: QuicHeader, address: u8[]): boolean {
|
|
579
|
+
const tokenAt: i32 = header.tokenStart
|
|
580
|
+
const length: i32 = header.tokenLength
|
|
403
581
|
if (length < QUIC_LISTENER_TOKEN_HEAD + QUIC_LISTENER_TOKEN_MAC_SIZE) {
|
|
404
582
|
return false
|
|
405
583
|
}
|
|
406
|
-
const odcidLength: i32 = toI32(
|
|
584
|
+
const odcidLength: i32 = toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
|
|
407
585
|
const head: i32 = QUIC_LISTENER_TOKEN_HEAD + odcidLength
|
|
408
586
|
if (odcidLength > QUIC_MAX_CID_LENGTH || head + QUIC_LISTENER_TOKEN_MAC_SIZE !== length) {
|
|
409
587
|
return false
|
|
410
588
|
}
|
|
411
|
-
|
|
412
|
-
|
|
589
|
+
this.tokenMac(datagram, tokenAt, head, datagram, header.dcidStart, header.dcidLength, address)
|
|
590
|
+
const same: boolean = timingSafeEqualAt(
|
|
591
|
+
this.tag,
|
|
592
|
+
0,
|
|
593
|
+
datagram,
|
|
594
|
+
tokenAt + head,
|
|
595
|
+
QUIC_LISTENER_TOKEN_MAC_SIZE
|
|
596
|
+
)
|
|
597
|
+
secureZero(this.tag)
|
|
598
|
+
if (!same) {
|
|
413
599
|
return false
|
|
414
600
|
}
|
|
415
601
|
let issued: i64 = 0
|
|
416
602
|
for (let k: i32 = 1; k < QUIC_LISTENER_TOKEN_HEAD - 1; k += 1) {
|
|
417
|
-
|
|
418
|
-
issued = (issued << 8) | toI64(token[k])
|
|
419
|
-
}
|
|
603
|
+
issued = (issued << 8) | toI64(datagram[tokenAt + k])
|
|
420
604
|
}
|
|
421
605
|
return issued <= this.now && this.now - issued < this.config.retryTokenLifetime
|
|
422
606
|
}
|
|
423
607
|
|
|
424
608
|
/**
|
|
425
|
-
* A Retry for the client's first Initial (RFC 9000 §8.1.2)
|
|
426
|
-
* connection ID for the client to send to, and a token
|
|
427
|
-
* original DCID and the client's address to now
|
|
609
|
+
* A Retry for the client's first Initial (RFC 9000 §8.1.2, §17.2.5) into
|
|
610
|
+
* `answer`: a new connection ID for the client to send to, and a token
|
|
611
|
+
* binding it, the original DCID and the client's address to now — a
|
|
612
|
+
* marker byte, the time, the DCID, then the MAC — under the integrity tag
|
|
613
|
+
* of RFC 9001 §5.8. Each part is written where it goes in `reply`.
|
|
428
614
|
*/
|
|
429
|
-
retry(answer: QuicListenerAnswer, header: QuicHeader, address: u8[]):
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
)
|
|
439
|
-
|
|
615
|
+
retry(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader, address: u8[]): void {
|
|
616
|
+
const scidLength: i32 = header.scidLength
|
|
617
|
+
const odcidLength: i32 = header.dcidLength
|
|
618
|
+
const retryScidAt: i32 = 7 + scidLength
|
|
619
|
+
const tokenAt: i32 = retryScidAt + QUIC_CONN_CID_LENGTH
|
|
620
|
+
const macAt: i32 = tokenAt + QUIC_LISTENER_TOKEN_HEAD + odcidLength
|
|
621
|
+
const tagAt: i32 = macAt + QUIC_LISTENER_TOKEN_MAC_SIZE
|
|
622
|
+
const reply: u8[] = answer.reply
|
|
623
|
+
quicListenerResize(reply, tagAt + QUIC_RETRY_TAG_SIZE)
|
|
624
|
+
this.randomInto(reply, retryScidAt, QUIC_CONN_CID_LENGTH)
|
|
625
|
+
reply[tokenAt] = toU8(QUIC_LISTENER_TOKEN_MARKER)
|
|
626
|
+
quicListenerPutNumber(reply, tokenAt + 1, this.now, 8)
|
|
627
|
+
reply[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1] = toU8(odcidLength)
|
|
628
|
+
quicPacketCopy(reply, tokenAt + QUIC_LISTENER_TOKEN_HEAD, datagram, header.dcidStart, odcidLength)
|
|
629
|
+
this.tokenMac(reply, tokenAt, macAt - tokenAt, reply, retryScidAt, QUIC_CONN_CID_LENGTH, address)
|
|
630
|
+
quicPacketCopy(reply, macAt, this.tag, 0, QUIC_LISTENER_TOKEN_MAC_SIZE)
|
|
631
|
+
secureZero(this.tag)
|
|
632
|
+
reply[0] = toU8((this.randomByte() & 15) | 0xf0)
|
|
633
|
+
quicListenerPutNumber(reply, 1, 1, 4)
|
|
634
|
+
reply[5] = toU8(scidLength)
|
|
635
|
+
quicPacketCopy(reply, 6, datagram, header.scidStart, scidLength)
|
|
636
|
+
reply[retryScidAt - 1] = toU8(QUIC_CONN_CID_LENGTH)
|
|
637
|
+
if (this.retryTag(reply, tagAt, datagram, header.dcidStart, odcidLength)) {
|
|
440
638
|
answer.kind = QUIC_LISTEN_RETRY
|
|
441
|
-
|
|
639
|
+
} else {
|
|
640
|
+
quicListenerResize(reply, 0)
|
|
442
641
|
}
|
|
443
|
-
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* The Retry integrity tag (RFC 9001 §5.8) over the pseudo-packet — the
|
|
646
|
+
* original DCID `datagram[odcidAt .. odcidAt + odcidLength)`, its length
|
|
647
|
+
* first, then `reply[0 .. tagAt)` — written at `reply[tagAt]`: AES-128-GCM
|
|
648
|
+
* under the listener's copy of version 1's fixed key, over nothing. The
|
|
649
|
+
* pseudo-packet and the AEAD's temporaries go with the arena block.
|
|
650
|
+
* Answers whether it was made.
|
|
651
|
+
*/
|
|
652
|
+
retryTag(reply: u8[], tagAt: i32, datagram: u8[], odcidAt: i32, odcidLength: i32): boolean {
|
|
653
|
+
using _scope = arena()
|
|
654
|
+
const pseudo: u8[] = new Array<u8>(1 + odcidLength + tagAt)
|
|
655
|
+
quicListenerPutNumber(pseudo, 0, toI64(odcidLength), 1)
|
|
656
|
+
quicPacketCopy(pseudo, 1, datagram, odcidAt, odcidLength)
|
|
657
|
+
quicPacketCopy(pseudo, 1 + odcidLength, reply, 0, tagAt)
|
|
658
|
+
const plain: u8[] = []
|
|
659
|
+
const tag: u8[] | null = aesGcmSeal(this.retryKey, quicListenerRetryNonce(), pseudo, plain)
|
|
660
|
+
if (tag === null || toI32(tag.length) !== QUIC_RETRY_TAG_SIZE) {
|
|
661
|
+
return false
|
|
662
|
+
}
|
|
663
|
+
quicPacketCopy(reply, tagAt, tag, 0, QUIC_RETRY_TAG_SIZE)
|
|
664
|
+
return true
|
|
444
665
|
}
|
|
445
666
|
|
|
446
667
|
/**
|
|
447
668
|
* The answer to an Initial carrying an invalid Retry token (RFC 9000
|
|
448
|
-
* §8.1.2)
|
|
449
|
-
* Initial keys of the DCID the client sent to,
|
|
450
|
-
* only when the client's Initial
|
|
451
|
-
* not noise; it is far smaller
|
|
669
|
+
* §8.1.2), into `answer`: an Initial with CONNECTION_CLOSE and
|
|
670
|
+
* INVALID_TOKEN, under the Initial keys of the DCID the client sent to,
|
|
671
|
+
* from that DCID. It is sent only when the client's Initial
|
|
672
|
+
* authenticates, so it answers a client and not noise; it is far smaller
|
|
673
|
+
* than the datagram it answers. The keys are derived into the listener's
|
|
674
|
+
* own slots, the Initial is opened in an arena block, and the close is
|
|
675
|
+
* written and sealed in `reply`.
|
|
452
676
|
*/
|
|
453
|
-
invalidToken(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader):
|
|
454
|
-
const secrets:
|
|
455
|
-
|
|
456
|
-
|
|
677
|
+
invalidToken(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): void {
|
|
678
|
+
const secrets: boolean = quicInitialSecretsInto(
|
|
679
|
+
this.kdf,
|
|
680
|
+
datagram,
|
|
681
|
+
header.dcidStart,
|
|
682
|
+
header.dcidLength,
|
|
683
|
+
this.initialClient,
|
|
684
|
+
this.initialServer
|
|
685
|
+
)
|
|
686
|
+
if (!secrets) {
|
|
687
|
+
return
|
|
457
688
|
}
|
|
458
|
-
const read: QuicKeys | null =
|
|
459
|
-
|
|
460
|
-
|
|
689
|
+
const read: QuicKeys | null = quicKeysInto(
|
|
690
|
+
this.kdf,
|
|
691
|
+
this.readSlot,
|
|
692
|
+
QUIC_AEAD_AES_128_GCM,
|
|
693
|
+
this.initialClient
|
|
694
|
+
)
|
|
695
|
+
if (read === null || !this.opens(read, datagram, header)) {
|
|
696
|
+
return
|
|
461
697
|
}
|
|
462
|
-
const opened: QuicPacket = quicOpenPacket(read, datagram, header, -1)
|
|
463
698
|
// The write keys are derived only for an Initial that opened, so a forged
|
|
464
699
|
// token on noise costs one derivation, not two.
|
|
465
|
-
const write: QuicKeys | null =
|
|
466
|
-
|
|
700
|
+
const write: QuicKeys | null = quicKeysInto(
|
|
701
|
+
this.kdf,
|
|
702
|
+
this.writeSlot,
|
|
703
|
+
QUIC_AEAD_AES_128_GCM,
|
|
704
|
+
this.initialServer
|
|
705
|
+
)
|
|
467
706
|
if (write === null) {
|
|
468
|
-
return
|
|
469
|
-
}
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
const peer: i32 = header.scidLength
|
|
710
|
+
const own: i32 = header.dcidLength
|
|
711
|
+
quicPacketCopy(this.peerCid, 0, datagram, header.scidStart, peer)
|
|
712
|
+
quicPacketCopy(this.ownCid, 0, datagram, header.dcidStart, own)
|
|
713
|
+
const reply: u8[] = answer.reply
|
|
714
|
+
quicListenerResize(reply, QUIC_LISTENER_ANSWER_SIZE)
|
|
715
|
+
const headerLength: i32 = quicLongHeaderSize(QUIC_PACKET_INITIAL, peer, own, 0, 0, 1, 0)
|
|
716
|
+
const frameEnd: i32 = quicPutConnectionClose(
|
|
717
|
+
reply,
|
|
718
|
+
headerLength,
|
|
719
|
+
QUIC_LISTENER_ANSWER_SIZE,
|
|
720
|
+
false,
|
|
721
|
+
QUIC_ERROR_INVALID_TOKEN,
|
|
478
722
|
0,
|
|
479
|
-
|
|
480
|
-
toI32(payload.length)
|
|
723
|
+
this.none
|
|
481
724
|
)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
725
|
+
const payloadLength: i32 = frameEnd - headerLength
|
|
726
|
+
const put: i32 =
|
|
727
|
+
headerLength > 0 && frameEnd > 0
|
|
728
|
+
? quicPutLongHeader(
|
|
729
|
+
reply,
|
|
730
|
+
0,
|
|
731
|
+
QUIC_PACKET_INITIAL,
|
|
732
|
+
this.peerCid,
|
|
733
|
+
peer,
|
|
734
|
+
this.ownCid,
|
|
735
|
+
own,
|
|
736
|
+
this.none,
|
|
737
|
+
0,
|
|
738
|
+
1,
|
|
739
|
+
payloadLength
|
|
740
|
+
)
|
|
741
|
+
: -1
|
|
742
|
+
const end: i32 =
|
|
743
|
+
put === headerLength ? quicSealInPlace(write, reply, 0, headerLength, payloadLength, 0) : -1
|
|
744
|
+
if (end > 0) {
|
|
745
|
+
quicListenerResize(reply, end)
|
|
487
746
|
answer.kind = QUIC_LISTEN_INVALID_TOKEN
|
|
488
|
-
|
|
747
|
+
} else {
|
|
748
|
+
quicListenerResize(reply, 0)
|
|
489
749
|
}
|
|
490
|
-
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/** Whether the Initial `header` read in `datagram` opens under `read`; what opening it allocates goes with the arena block. */
|
|
753
|
+
opens(read: QuicKeys, datagram: u8[], header: QuicHeader): boolean {
|
|
754
|
+
using _scope = arena()
|
|
755
|
+
const packet: QuicPacket = this.packet
|
|
756
|
+
const clear: u8[] = quicUnprotectHeader(read, datagram, header, -1, packet)
|
|
757
|
+
const payload: u8[] | null = quicDecryptPayload(read, datagram, header, clear, packet)
|
|
758
|
+
return payload !== null && packet.error === QUIC_PACKET_OK
|
|
491
759
|
}
|
|
492
760
|
|
|
493
761
|
/** Takes one stateless reset from the rate limit's budget, topping it up first. Answers whether one was there. */
|
|
@@ -512,34 +780,42 @@ export class QuicListener {
|
|
|
512
780
|
}
|
|
513
781
|
|
|
514
782
|
/**
|
|
515
|
-
* A stateless reset answering an `n`-byte datagram sent to
|
|
516
|
-
* 9000 §10.3): a
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
783
|
+
* A stateless reset into `answer`, answering an `n`-byte datagram sent to
|
|
784
|
+
* the ID `datagram[dcidAt .. dcidAt + dcidLength)` (RFC 9000 §10.3): a
|
|
785
|
+
* short-header first byte, unpredictable bytes, and the token for that
|
|
786
|
+
* ID, computed in place by `quicConnResetTokenInto`. It is one byte
|
|
787
|
+
* shorter than a datagram of up to 43 bytes and between 43 bytes and one
|
|
788
|
+
* byte short of the datagram (at most 1200) otherwise, so it is always
|
|
789
|
+
* shorter; a datagram too short for that to leave 21 bytes, or one past
|
|
790
|
+
* the rate limit, gets nothing.
|
|
521
791
|
*/
|
|
522
|
-
statelessReset(answer: QuicListenerAnswer, n: i32,
|
|
792
|
+
statelessReset(answer: QuicListenerAnswer, n: i32, datagram: u8[], dcidAt: i32, dcidLength: i32): void {
|
|
523
793
|
if (n <= QUIC_LISTENER_MIN_RESET || !this.takeReset()) {
|
|
524
|
-
return
|
|
794
|
+
return
|
|
525
795
|
}
|
|
526
796
|
let size: i32 = n - 1
|
|
527
797
|
if (n > QUIC_LISTENER_SHORT_TRIGGER) {
|
|
528
798
|
const most: i32 = n - 1 < QUIC_CONN_DATAGRAM_SIZE ? n - 1 : QUIC_CONN_DATAGRAM_SIZE
|
|
529
799
|
const span: i32 = most - QUIC_LISTENER_SHORT_TRIGGER + 1
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
const r: i32 = toI32(pick.length) > 1 ? (toI32(pick[0]) << 8) | toI32(pick[1]) : 0
|
|
800
|
+
const high: i32 = this.randomByte()
|
|
801
|
+
const r: i32 = (high << 8) | this.randomByte()
|
|
533
802
|
size = QUIC_LISTENER_SHORT_TRIGGER + (span > 0 ? r % span : 0)
|
|
534
803
|
}
|
|
535
|
-
const reply: u8[] =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
804
|
+
const reply: u8[] = answer.reply
|
|
805
|
+
const tokenAt: i32 = size - QUIC_RESET_TOKEN_SIZE
|
|
806
|
+
quicListenerResize(reply, size)
|
|
807
|
+
this.randomInto(reply, QUIC_LISTENER_FROM, tokenAt)
|
|
808
|
+
reply[0] = toU8((toI32(reply[0]) & 0x3f) | 0x40)
|
|
809
|
+
quicConnResetTokenInto(
|
|
810
|
+
this.kdf.sha256,
|
|
811
|
+
this.config.statelessResetKey,
|
|
812
|
+
datagram,
|
|
813
|
+
dcidAt,
|
|
814
|
+
dcidLength,
|
|
815
|
+
reply,
|
|
816
|
+
tokenAt
|
|
817
|
+
)
|
|
540
818
|
answer.kind = QUIC_LISTEN_STATELESS_RESET
|
|
541
|
-
answer.reply = reply
|
|
542
|
-
return answer
|
|
543
819
|
}
|
|
544
820
|
}
|
|
545
821
|
|