@amritk/nish-x86_64-linux 0.16.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.
Files changed (58) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +50 -13
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +911 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +462 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -0
@@ -0,0 +1,462 @@
1
+ /**
2
+ * `nish/net/quic-conn-params` — QUIC's transport parameters (RFC 9000 §18),
3
+ * the bytes each endpoint carries in TLS's `quic_transport_parameters`
4
+ * extension (RFC 9001 §8.2), encoded and parsed.
5
+ *
6
+ * import { QuicTransportParameters, quicEncodeTransportParameters,
7
+ * quicParseTransportParameters } from "nish/net/quic-conn-params";
8
+ *
9
+ * const mine = new QuicTransportParameters();
10
+ * mine.initialMaxData = 65536;
11
+ * const bytes: u8[] = quicEncodeTransportParameters(mine); // for TlsServerConfig
12
+ * const theirs = quicParseTransportParameters(clientBytes, false);
13
+ * if (theirs.error !== 0) { … close with theirs.error … }
14
+ *
15
+ * A `QuicTransportParameters` starts at every parameter's default (§18.2),
16
+ * so a parse leaves a parameter the peer did not send at the value the RFC
17
+ * says it then has. The encoder writes a parameter only when it differs from
18
+ * that default, plus the connection IDs, which have none.
19
+ *
20
+ * **What a parse refuses**, each a TRANSPORT_PARAMETER_ERROR (§7.4, §18.2):
21
+ * an ID or a length that does not fit, a parameter running past the bytes, a
22
+ * known parameter sent twice, a varint-valued parameter whose value is not
23
+ * exactly one varint, `disable_active_migration` with a value, a server-only
24
+ * parameter from a client (`original_destination_connection_id`,
25
+ * `preferred_address`, `retry_source_connection_id`,
26
+ * `stateless_reset_token`), a stateless reset token that is not 16 bytes, a
27
+ * connection ID over 20 bytes, `max_udp_payload_size` below 1200,
28
+ * `ack_delay_exponent` above 20, `max_ack_delay` at 2^14 or above,
29
+ * `active_connection_id_limit` below 2, and a stream count above 2^60.
30
+ * Unknown parameters, the reserved ones (31 × N + 27) among them, are
31
+ * skipped, as §18.1 asks; a second copy of an unknown one is not noticed.
32
+ * Whether `initial_source_connection_id` is present and right is the
33
+ * connection's check, because it needs the packet the ID came in.
34
+ *
35
+ * Written from RFC 9000 §18, in this module's own structure; nothing here is
36
+ * ported from another implementation. Private names carry the
37
+ * `quicParams` prefix (`docs/wp26-stdlib.md` §3e).
38
+ */
39
+ import {
40
+ QUIC_MAX_CID_LENGTH,
41
+ quicVarintLength,
42
+ quicVarintPush,
43
+ quicVarintRead,
44
+ quicVarintSize,
45
+ } from "nish/net/quic-packet"
46
+ import {
47
+ QUIC_ERROR_NO_ERROR,
48
+ QUIC_ERROR_TRANSPORT_PARAMETER,
49
+ QUIC_RESET_TOKEN_SIZE,
50
+ } from "nish/net/quic-frame"
51
+
52
+ // ---- Parameter IDs (RFC 9000 §18.2) ---------------------------------------------
53
+
54
+ export const QUIC_TP_ORIGINAL_DCID: i32 = 0x00
55
+ export const QUIC_TP_MAX_IDLE_TIMEOUT: i32 = 0x01
56
+ export const QUIC_TP_STATELESS_RESET_TOKEN: i32 = 0x02
57
+ export const QUIC_TP_MAX_UDP_PAYLOAD_SIZE: i32 = 0x03
58
+ export const QUIC_TP_INITIAL_MAX_DATA: i32 = 0x04
59
+ export const QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: i32 = 0x05
60
+ export const QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: i32 = 0x06
61
+ export const QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: i32 = 0x07
62
+ export const QUIC_TP_INITIAL_MAX_STREAMS_BIDI: i32 = 0x08
63
+ export const QUIC_TP_INITIAL_MAX_STREAMS_UNI: i32 = 0x09
64
+ export const QUIC_TP_ACK_DELAY_EXPONENT: i32 = 0x0a
65
+ export const QUIC_TP_MAX_ACK_DELAY: i32 = 0x0b
66
+ export const QUIC_TP_DISABLE_ACTIVE_MIGRATION: i32 = 0x0c
67
+ export const QUIC_TP_PREFERRED_ADDRESS: i32 = 0x0d
68
+ export const QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: i32 = 0x0e
69
+ export const QUIC_TP_INITIAL_SCID: i32 = 0x0f
70
+ export const QUIC_TP_RETRY_SCID: i32 = 0x10
71
+ /**
72
+ * `max_datagram_frame_size` (RFC 9221 §3): the largest DATAGRAM frame, type
73
+ * and Length included, the sender takes; absent or 0, it takes none.
74
+ */
75
+ export const QUIC_TP_MAX_DATAGRAM_FRAME_SIZE: i32 = 0x20
76
+
77
+ /** The smallest `max_udp_payload_size` allowed, and every QUIC path's minimum datagram (RFC 9000 §14). */
78
+ export const QUIC_MIN_UDP_PAYLOAD: i64 = 1200
79
+
80
+ /** A stream count's ceiling, 2^60 (§18.2). A product, because an `i64` literal past 2^53 is refused. */
81
+ const QUIC_PARAMS_MAX_STREAMS: i64 = 1073741824 * 1073741824
82
+
83
+ /**
84
+ * One endpoint's transport parameters. Every field starts at the default
85
+ * §18.2 gives it, and the connection IDs and token start empty with their
86
+ * `has*` flag false. `preferredAddress` is kept as the bytes the server sent,
87
+ * since this side never acts on it. `error` is what a parse answers:
88
+ * `QUIC_ERROR_NO_ERROR`, or `QUIC_ERROR_TRANSPORT_PARAMETER` with
89
+ * `errorParameter` the ID of the parameter at fault (-1 when the bytes did not
90
+ * frame).
91
+ */
92
+ export class QuicTransportParameters {
93
+ error: i64 = 0
94
+ maxIdleTimeout: i64 = 0
95
+ maxUdpPayloadSize: i64 = 65527
96
+ initialMaxData: i64 = 0
97
+ initialMaxStreamDataBidiLocal: i64 = 0
98
+ initialMaxStreamDataBidiRemote: i64 = 0
99
+ initialMaxStreamDataUni: i64 = 0
100
+ initialMaxStreamsBidi: i64 = 0
101
+ initialMaxStreamsUni: i64 = 0
102
+ ackDelayExponent: i64 = 3
103
+ maxAckDelay: i64 = 25
104
+ activeConnectionIdLimit: i64 = 2
105
+ /** RFC 9221's `max_datagram_frame_size`: 0, the default, for no DATAGRAM frames. */
106
+ maxDatagramFrameSize: i64 = 0
107
+ errorParameter: i64 = 0
108
+ originalDcid: u8[]
109
+ statelessResetToken: u8[]
110
+ preferredAddress: u8[]
111
+ initialScid: u8[]
112
+ retryScid: u8[]
113
+ hasOriginalDcid: boolean = false
114
+ hasStatelessResetToken: boolean = false
115
+ hasPreferredAddress: boolean = false
116
+ hasInitialScid: boolean = false
117
+ hasRetryScid: boolean = false
118
+ disableActiveMigration: boolean = false
119
+
120
+ constructor() {
121
+ this.originalDcid = []
122
+ this.statelessResetToken = []
123
+ this.preferredAddress = []
124
+ this.initialScid = []
125
+ this.retryScid = []
126
+ }
127
+ }
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
+
165
+ /** Appends one parameter whose value is a varint, when it differs from `fallback`, its default. */
166
+ const quicParamsPushVarint = (out: u8[], id: i32, value: i64, fallback: i64): void => {
167
+ if (value === fallback || quicVarintSize(value) === 0) {
168
+ return
169
+ }
170
+ quicVarintPush(out, toI64(id))
171
+ quicVarintPush(out, toI64(quicVarintSize(value)))
172
+ quicVarintPush(out, value)
173
+ }
174
+
175
+ /** Appends one parameter whose value is `bytes`, when `present`. */
176
+ const quicParamsPushBytes = (out: u8[], id: i32, bytes: u8[], present: boolean): void => {
177
+ if (!present) {
178
+ return
179
+ }
180
+ quicVarintPush(out, toI64(id))
181
+ quicVarintPush(out, toI64(toI32(bytes.length)))
182
+ for (const b of bytes) {
183
+ out.push(b)
184
+ }
185
+ }
186
+
187
+ /**
188
+ * The transport parameters `p` as the `quic_transport_parameters`
189
+ * extension's body (§18): each parameter as its ID, its length and its
190
+ * value, in ID order, every varint-valued one only when it is not its
191
+ * default, every byte-valued one only when its `has*` flag is set, and
192
+ * `disable_active_migration` only when true. Nothing is checked: these are
193
+ * this endpoint's own values, and a value no varint holds is left out.
194
+ */
195
+ export const quicEncodeTransportParameters = (p: QuicTransportParameters): u8[] => {
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)
209
+ quicParamsPushBytes(out, QUIC_TP_ORIGINAL_DCID, p.originalDcid, p.hasOriginalDcid)
210
+ quicParamsPushVarint(out, QUIC_TP_MAX_IDLE_TIMEOUT, p.maxIdleTimeout, 0)
211
+ quicParamsPushBytes(out, QUIC_TP_STATELESS_RESET_TOKEN, p.statelessResetToken, p.hasStatelessResetToken)
212
+ quicParamsPushVarint(out, QUIC_TP_MAX_UDP_PAYLOAD_SIZE, p.maxUdpPayloadSize, 65527)
213
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_DATA, p.initialMaxData, 0)
214
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, p.initialMaxStreamDataBidiLocal, 0)
215
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, p.initialMaxStreamDataBidiRemote, 0)
216
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, p.initialMaxStreamDataUni, 0)
217
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAMS_BIDI, p.initialMaxStreamsBidi, 0)
218
+ quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAMS_UNI, p.initialMaxStreamsUni, 0)
219
+ quicParamsPushVarint(out, QUIC_TP_ACK_DELAY_EXPONENT, p.ackDelayExponent, 3)
220
+ quicParamsPushVarint(out, QUIC_TP_MAX_ACK_DELAY, p.maxAckDelay, 25)
221
+ const none: u8[] = []
222
+ quicParamsPushBytes(out, QUIC_TP_DISABLE_ACTIVE_MIGRATION, none, p.disableActiveMigration)
223
+ quicParamsPushBytes(out, QUIC_TP_PREFERRED_ADDRESS, p.preferredAddress, p.hasPreferredAddress)
224
+ quicParamsPushVarint(out, QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, p.activeConnectionIdLimit, 2)
225
+ quicParamsPushBytes(out, QUIC_TP_INITIAL_SCID, p.initialScid, p.hasInitialScid)
226
+ quicParamsPushBytes(out, QUIC_TP_RETRY_SCID, p.retryScid, p.hasRetryScid)
227
+ quicParamsPushVarint(out, QUIC_TP_MAX_DATAGRAM_FRAME_SIZE, p.maxDatagramFrameSize, 0)
228
+ }
229
+
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) {
238
+ if (from + k >= 0 && from + k < toI32(data.length)) {
239
+ to.push(data[from + k])
240
+ }
241
+ }
242
+ }
243
+
244
+ /** Fails `p` over parameter `id` and answers it. */
245
+ const quicParamsRefuse = (p: QuicTransportParameters, id: i64): QuicTransportParameters => {
246
+ p.error = QUIC_ERROR_TRANSPORT_PARAMETER
247
+ p.errorParameter = id
248
+ return p
249
+ }
250
+
251
+ /**
252
+ * The value of a varint-valued parameter in `bytes[at .. at + length)`: it
253
+ * must be exactly one varint filling the value. Answers -1, which no varint
254
+ * holds, otherwise.
255
+ */
256
+ const quicParamsVarintValue = (bytes: u8[], at: i32, length: i32): i64 => {
257
+ const value: i64 = quicVarintRead(bytes, at, at + length)
258
+ if (value < 0 || quicVarintLength(bytes, at) !== length) {
259
+ return -1
260
+ }
261
+ return value
262
+ }
263
+
264
+ /**
265
+ * Whether the varint-valued parameter `id` may hold `value` (§18.2): the
266
+ * ranges the RFC sets, and any varint for the rest.
267
+ */
268
+ const quicParamsValueFits = (id: i32, value: i64): boolean => {
269
+ switch (id) {
270
+ case QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
271
+ return value >= QUIC_MIN_UDP_PAYLOAD
272
+ case QUIC_TP_ACK_DELAY_EXPONENT:
273
+ return value <= 20
274
+ case QUIC_TP_MAX_ACK_DELAY:
275
+ return value < 16384
276
+ case QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
277
+ return value >= 2
278
+ case QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
279
+ return value <= QUIC_PARAMS_MAX_STREAMS
280
+ case QUIC_TP_INITIAL_MAX_STREAMS_UNI:
281
+ return value <= QUIC_PARAMS_MAX_STREAMS
282
+ default:
283
+ return true
284
+ }
285
+ }
286
+
287
+ /** Stores the varint-valued parameter `id`; the caller has checked that it is one. */
288
+ const quicParamsStoreVarint = (p: QuicTransportParameters, id: i32, value: i64): void => {
289
+ switch (id) {
290
+ case QUIC_TP_MAX_IDLE_TIMEOUT:
291
+ p.maxIdleTimeout = value
292
+ break
293
+ case QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
294
+ p.maxUdpPayloadSize = value
295
+ break
296
+ case QUIC_TP_INITIAL_MAX_DATA:
297
+ p.initialMaxData = value
298
+ break
299
+ case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
300
+ p.initialMaxStreamDataBidiLocal = value
301
+ break
302
+ case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
303
+ p.initialMaxStreamDataBidiRemote = value
304
+ break
305
+ case QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
306
+ p.initialMaxStreamDataUni = value
307
+ break
308
+ case QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
309
+ p.initialMaxStreamsBidi = value
310
+ break
311
+ case QUIC_TP_INITIAL_MAX_STREAMS_UNI:
312
+ p.initialMaxStreamsUni = value
313
+ break
314
+ case QUIC_TP_ACK_DELAY_EXPONENT:
315
+ p.ackDelayExponent = value
316
+ break
317
+ case QUIC_TP_MAX_ACK_DELAY:
318
+ p.maxAckDelay = value
319
+ break
320
+ default:
321
+ p.activeConnectionIdLimit = value
322
+ break
323
+ }
324
+ }
325
+
326
+ /** Whether `id` is a parameter only a server may send (§18.2). */
327
+ const quicParamsServerOnly = (id: i32): boolean =>
328
+ id === QUIC_TP_ORIGINAL_DCID ||
329
+ id === QUIC_TP_STATELESS_RESET_TOKEN ||
330
+ id === QUIC_TP_PREFERRED_ADDRESS ||
331
+ id === QUIC_TP_RETRY_SCID
332
+
333
+ /**
334
+ * Stores the byte-valued parameter `id` from `bytes[at .. at + length)`.
335
+ * Answers whether its length is one the parameter allows: 16 for the reset
336
+ * token, at most 20 for a connection ID, 0 for `disable_active_migration`.
337
+ */
338
+ const quicParamsStoreBytes = (
339
+ p: QuicTransportParameters,
340
+ id: i32,
341
+ bytes: u8[],
342
+ at: i32,
343
+ length: i32
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
350
+ switch (id) {
351
+ case QUIC_TP_ORIGINAL_DCID:
352
+ quicParamsCopyInto(p.originalDcid, bytes, at, cidFits ? length : 0)
353
+ p.hasOriginalDcid = true
354
+ return cidFits
355
+ case QUIC_TP_STATELESS_RESET_TOKEN:
356
+ quicParamsCopyInto(p.statelessResetToken, bytes, at, length === QUIC_RESET_TOKEN_SIZE ? length : 0)
357
+ p.hasStatelessResetToken = true
358
+ return length === QUIC_RESET_TOKEN_SIZE
359
+ case QUIC_TP_DISABLE_ACTIVE_MIGRATION:
360
+ p.disableActiveMigration = true
361
+ return length === 0
362
+ case QUIC_TP_PREFERRED_ADDRESS:
363
+ quicParamsCopyInto(p.preferredAddress, bytes, at, length)
364
+ p.hasPreferredAddress = true
365
+ return true
366
+ case QUIC_TP_INITIAL_SCID:
367
+ quicParamsCopyInto(p.initialScid, bytes, at, cidFits ? length : 0)
368
+ p.hasInitialScid = true
369
+ return cidFits
370
+ default:
371
+ quicParamsCopyInto(p.retryScid, bytes, at, cidFits ? length : 0)
372
+ p.hasRetryScid = true
373
+ return cidFits
374
+ }
375
+ }
376
+
377
+ /** Whether `id` is one of the parameters whose value is a varint. */
378
+ const quicParamsIsVarint = (id: i32): boolean =>
379
+ id === QUIC_TP_MAX_IDLE_TIMEOUT ||
380
+ (id >= QUIC_TP_MAX_UDP_PAYLOAD_SIZE && id <= QUIC_TP_MAX_ACK_DELAY) ||
381
+ id === QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT
382
+
383
+ /**
384
+ * Parses the body of a `quic_transport_parameters` extension. `fromServer`
385
+ * says who sent it, which decides whether the server-only parameters are
386
+ * allowed. The answer's `error` is `QUIC_ERROR_NO_ERROR`, or
387
+ * `QUIC_ERROR_TRANSPORT_PARAMETER` for any of the breaks the module header
388
+ * lists, with `errorParameter` naming the parameter; on an error the other
389
+ * fields hold what was read so far and are not to be used.
390
+ */
391
+ export const quicParseTransportParameters = (bytes: u8[], fromServer: boolean): QuicTransportParameters => {
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)
409
+ const end: i32 = toI32(bytes.length)
410
+ // The known IDs are 0 to 16, so one bit each of an i32 records which were
411
+ // seen; RFC 9221's 0x20 has a flag of its own.
412
+ let seen: i32 = 0
413
+ let seenDatagram: boolean = false
414
+ let at: i32 = 0
415
+ while (at < end) {
416
+ const id: i64 = quicVarintRead(bytes, at, end)
417
+ if (id < 0) {
418
+ return quicParamsRefuse(p, -1)
419
+ }
420
+ at = at + quicVarintLength(bytes, at)
421
+ const length: i64 = quicVarintRead(bytes, at, end)
422
+ if (length < 0) {
423
+ return quicParamsRefuse(p, id)
424
+ }
425
+ at = at + quicVarintLength(bytes, at)
426
+ if (length > toI64(end - at)) {
427
+ return quicParamsRefuse(p, id)
428
+ }
429
+ const valueAt: i32 = at
430
+ const valueLength: i32 = toI32(length)
431
+ at = at + valueLength
432
+ if (id === toI64(QUIC_TP_MAX_DATAGRAM_FRAME_SIZE)) {
433
+ const value: i64 = quicParamsVarintValue(bytes, valueAt, valueLength)
434
+ if (seenDatagram || value < 0) {
435
+ return quicParamsRefuse(p, id)
436
+ }
437
+ seenDatagram = true
438
+ p.maxDatagramFrameSize = value
439
+ continue
440
+ }
441
+ if (id > toI64(QUIC_TP_RETRY_SCID)) {
442
+ continue
443
+ }
444
+ const known: i32 = toI32(id)
445
+ const bit: i32 = 1 << known
446
+ if ((seen & bit) !== 0 || (!fromServer && quicParamsServerOnly(known))) {
447
+ return quicParamsRefuse(p, id)
448
+ }
449
+ seen = seen | bit
450
+ if (quicParamsIsVarint(known)) {
451
+ const value: i64 = quicParamsVarintValue(bytes, valueAt, valueLength)
452
+ if (value < 0 || !quicParamsValueFits(known, value)) {
453
+ return quicParamsRefuse(p, id)
454
+ }
455
+ quicParamsStoreVarint(p, known, value)
456
+ } else if (!quicParamsStoreBytes(p, known, bytes, valueAt, valueLength)) {
457
+ return quicParamsRefuse(p, id)
458
+ }
459
+ }
460
+ p.error = QUIC_ERROR_NO_ERROR
461
+ return p
462
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * `nish/net/quic-datagram` — a fixed ring of QUIC DATAGRAM payloads (RFC
3
+ * 9221), one each way per connection: what the application queued to send,
4
+ * waiting for room in a packet and in the congestion window, and what the
5
+ * peer sent, waiting for the application to read it. Sans-IO, like the rest
6
+ * of `nish/net`; `nish/net/quic`'s `QuicConnection` owns both rings and the
7
+ * application reaches them through its `sendDatagram` and `readDatagram`.
8
+ *
9
+ * **Fixed.** A ring is `entries` payloads of at most `entrySize` bytes,
10
+ * allocated once; a payload is copied in and out, and nothing allocates
11
+ * after the ring is made. A datagram is unreliable by definition (RFC 9221
12
+ * §5), so a full ring refuses the next rather than growing, and what the
13
+ * peer sends past a full receive ring, or past `entrySize`, is dropped and
14
+ * counted in `dropped`.
15
+ *
16
+ * Written from RFC 9221, in this module's own structure; nothing here is
17
+ * ported from another implementation. Private names carry the
18
+ * `quicDatagram` prefix (`docs/wp26-stdlib.md` §3e).
19
+ */
20
+ import { quicPutDatagram } from "nish/net/quic-frame"
21
+ import { quicPacketCopy } from "nish/net/quic-packet"
22
+
23
+ /** A ring of `entries` datagram payloads of at most `entrySize` bytes each, oldest first. */
24
+ export class QuicDatagramQueue {
25
+ /** Entry `k`'s bytes are `data[k * entrySize ..]`, `lengths[k]` of them. */
26
+ data: u8[]
27
+ lengths: i32[]
28
+ entrySize: i32 = 0
29
+ head: i32 = 0
30
+ count: i32 = 0
31
+ /** Datagrams refused for want of room or for their size, which the peer's side counts as dropped. */
32
+ dropped: i32 = 0
33
+
34
+ constructor(entries: i32, entrySize: i32) {
35
+ this.data = new Array<u8>(entries * entrySize)
36
+ this.lengths = new Array<i32>(entries)
37
+ this.entrySize = entrySize
38
+ }
39
+
40
+ /** Empties the ring, for a connection slot reused for another peer. */
41
+ reset(): void {
42
+ this.head = 0
43
+ this.count = 0
44
+ this.dropped = 0
45
+ }
46
+
47
+ /** How many datagrams the ring holds at most. */
48
+ capacity(): i32 {
49
+ return toI32(this.lengths.length)
50
+ }
51
+
52
+ /**
53
+ * Copies `buf[from .. from + length)` in as the newest datagram. Answers
54
+ * whether it could: not when the ring is full, nor for a payload past
55
+ * `entrySize` or a window outside `buf`, each of which counts in `dropped`.
56
+ */
57
+ push(buf: u8[], from: i32, length: i32): boolean {
58
+ const entries: i32 = this.capacity()
59
+ if (
60
+ this.count >= entries ||
61
+ length < 0 ||
62
+ length > this.entrySize ||
63
+ from < 0 ||
64
+ from > toI32(buf.length) - length
65
+ ) {
66
+ if (this.dropped < 2147483647) {
67
+ this.dropped = this.dropped + 1
68
+ }
69
+ return false
70
+ }
71
+ const k: i32 = (this.head + this.count) % entries
72
+ const base: i32 = k * this.entrySize
73
+ quicPacketCopy(this.data, base, buf, from, length)
74
+ if (k >= 0 && k < entries) {
75
+ this.lengths[k] = length
76
+ }
77
+ this.count = this.count + 1
78
+ return true
79
+ }
80
+
81
+ /** The oldest datagram's length, or -1 when the ring is empty. */
82
+ peekLength(): i32 {
83
+ const k: i32 = this.head
84
+ return this.count > 0 && k >= 0 && k < toI32(this.lengths.length) ? this.lengths[k] : -1
85
+ }
86
+
87
+ /** Forgets the oldest datagram. */
88
+ drop(): void {
89
+ if (this.count > 0) {
90
+ this.head = (this.head + 1) % this.capacity()
91
+ this.count = this.count - 1
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Copies the oldest datagram into `buf` at `at` and forgets it. Answers
97
+ * its length, -1 when the ring is empty, or -2, keeping it, when it does
98
+ * not fit in `cap` bytes from `at` inside `buf`.
99
+ */
100
+ pop(buf: u8[], at: i32, cap: i32): i32 {
101
+ const length: i32 = this.peekLength()
102
+ if (length < 0) {
103
+ return -1
104
+ }
105
+ if (at < 0 || cap < length || at > toI32(buf.length) - length) {
106
+ return -2
107
+ }
108
+ quicPacketCopy(buf, at, this.data, this.head * this.entrySize, length)
109
+ this.drop()
110
+ return length
111
+ }
112
+
113
+ /**
114
+ * Writes the oldest datagram as a DATAGRAM frame into `buf[at .. end)` and
115
+ * forgets it. Answers the offset past the frame, or `at` when the ring is
116
+ * empty or the frame does not fit, keeping it for the next packet.
117
+ */
118
+ putFrame(buf: u8[], at: i32, end: i32): i32 {
119
+ const length: i32 = this.peekLength()
120
+ if (length < 0) {
121
+ return at
122
+ }
123
+ const next: i32 = quicPutDatagram(buf, at, end, this.data, this.head * this.entrySize, length)
124
+ if (next < 0) {
125
+ return at
126
+ }
127
+ this.drop()
128
+ return next
129
+ }
130
+ }