@amritk/nish-x86_64-linux 0.15.0 → 0.17.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/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
|
@@ -0,0 +1,392 @@
|
|
|
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
|
+
/** Appends one parameter whose value is a varint, when it differs from `fallback`, its default. */
|
|
130
|
+
const quicParamsPushVarint = (out: u8[], id: i32, value: i64, fallback: i64): void => {
|
|
131
|
+
if (value === fallback || quicVarintSize(value) === 0) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
quicVarintPush(out, toI64(id))
|
|
135
|
+
quicVarintPush(out, toI64(quicVarintSize(value)))
|
|
136
|
+
quicVarintPush(out, value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Appends one parameter whose value is `bytes`, when `present`. */
|
|
140
|
+
const quicParamsPushBytes = (out: u8[], id: i32, bytes: u8[], present: boolean): void => {
|
|
141
|
+
if (!present) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
quicVarintPush(out, toI64(id))
|
|
145
|
+
quicVarintPush(out, toI64(toI32(bytes.length)))
|
|
146
|
+
for (const b of bytes) {
|
|
147
|
+
out.push(b)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The transport parameters `p` as the `quic_transport_parameters`
|
|
153
|
+
* extension's body (§18): each parameter as its ID, its length and its
|
|
154
|
+
* value, in ID order, every varint-valued one only when it is not its
|
|
155
|
+
* default, every byte-valued one only when its `has*` flag is set, and
|
|
156
|
+
* `disable_active_migration` only when true. Nothing is checked: these are
|
|
157
|
+
* this endpoint's own values, and a value no varint holds is left out.
|
|
158
|
+
*/
|
|
159
|
+
export const quicEncodeTransportParameters = (p: QuicTransportParameters): u8[] => {
|
|
160
|
+
const out: u8[] = []
|
|
161
|
+
quicParamsPushBytes(out, QUIC_TP_ORIGINAL_DCID, p.originalDcid, p.hasOriginalDcid)
|
|
162
|
+
quicParamsPushVarint(out, QUIC_TP_MAX_IDLE_TIMEOUT, p.maxIdleTimeout, 0)
|
|
163
|
+
quicParamsPushBytes(out, QUIC_TP_STATELESS_RESET_TOKEN, p.statelessResetToken, p.hasStatelessResetToken)
|
|
164
|
+
quicParamsPushVarint(out, QUIC_TP_MAX_UDP_PAYLOAD_SIZE, p.maxUdpPayloadSize, 65527)
|
|
165
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_DATA, p.initialMaxData, 0)
|
|
166
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, p.initialMaxStreamDataBidiLocal, 0)
|
|
167
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, p.initialMaxStreamDataBidiRemote, 0)
|
|
168
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, p.initialMaxStreamDataUni, 0)
|
|
169
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAMS_BIDI, p.initialMaxStreamsBidi, 0)
|
|
170
|
+
quicParamsPushVarint(out, QUIC_TP_INITIAL_MAX_STREAMS_UNI, p.initialMaxStreamsUni, 0)
|
|
171
|
+
quicParamsPushVarint(out, QUIC_TP_ACK_DELAY_EXPONENT, p.ackDelayExponent, 3)
|
|
172
|
+
quicParamsPushVarint(out, QUIC_TP_MAX_ACK_DELAY, p.maxAckDelay, 25)
|
|
173
|
+
const none: u8[] = []
|
|
174
|
+
quicParamsPushBytes(out, QUIC_TP_DISABLE_ACTIVE_MIGRATION, none, p.disableActiveMigration)
|
|
175
|
+
quicParamsPushBytes(out, QUIC_TP_PREFERRED_ADDRESS, p.preferredAddress, p.hasPreferredAddress)
|
|
176
|
+
quicParamsPushVarint(out, QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, p.activeConnectionIdLimit, 2)
|
|
177
|
+
quicParamsPushBytes(out, QUIC_TP_INITIAL_SCID, p.initialScid, p.hasInitialScid)
|
|
178
|
+
quicParamsPushBytes(out, QUIC_TP_RETRY_SCID, p.retryScid, p.hasRetryScid)
|
|
179
|
+
quicParamsPushVarint(out, QUIC_TP_MAX_DATAGRAM_FRAME_SIZE, p.maxDatagramFrameSize, 0)
|
|
180
|
+
return out
|
|
181
|
+
}
|
|
182
|
+
|
|
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) {
|
|
188
|
+
if (from + k >= 0 && from + k < toI32(data.length)) {
|
|
189
|
+
out[k] = data[from + k]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Fails `p` over parameter `id` and answers it. */
|
|
196
|
+
const quicParamsRefuse = (p: QuicTransportParameters, id: i64): QuicTransportParameters => {
|
|
197
|
+
p.error = QUIC_ERROR_TRANSPORT_PARAMETER
|
|
198
|
+
p.errorParameter = id
|
|
199
|
+
return p
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The value of a varint-valued parameter in `bytes[at .. at + length)`: it
|
|
204
|
+
* must be exactly one varint filling the value. Answers -1, which no varint
|
|
205
|
+
* holds, otherwise.
|
|
206
|
+
*/
|
|
207
|
+
const quicParamsVarintValue = (bytes: u8[], at: i32, length: i32): i64 => {
|
|
208
|
+
const value: i64 = quicVarintRead(bytes, at, at + length)
|
|
209
|
+
if (value < 0 || quicVarintLength(bytes, at) !== length) {
|
|
210
|
+
return -1
|
|
211
|
+
}
|
|
212
|
+
return value
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Whether the varint-valued parameter `id` may hold `value` (§18.2): the
|
|
217
|
+
* ranges the RFC sets, and any varint for the rest.
|
|
218
|
+
*/
|
|
219
|
+
const quicParamsValueFits = (id: i32, value: i64): boolean => {
|
|
220
|
+
switch (id) {
|
|
221
|
+
case QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
|
|
222
|
+
return value >= QUIC_MIN_UDP_PAYLOAD
|
|
223
|
+
case QUIC_TP_ACK_DELAY_EXPONENT:
|
|
224
|
+
return value <= 20
|
|
225
|
+
case QUIC_TP_MAX_ACK_DELAY:
|
|
226
|
+
return value < 16384
|
|
227
|
+
case QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
|
|
228
|
+
return value >= 2
|
|
229
|
+
case QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
|
|
230
|
+
return value <= QUIC_PARAMS_MAX_STREAMS
|
|
231
|
+
case QUIC_TP_INITIAL_MAX_STREAMS_UNI:
|
|
232
|
+
return value <= QUIC_PARAMS_MAX_STREAMS
|
|
233
|
+
default:
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Stores the varint-valued parameter `id`; the caller has checked that it is one. */
|
|
239
|
+
const quicParamsStoreVarint = (p: QuicTransportParameters, id: i32, value: i64): void => {
|
|
240
|
+
switch (id) {
|
|
241
|
+
case QUIC_TP_MAX_IDLE_TIMEOUT:
|
|
242
|
+
p.maxIdleTimeout = value
|
|
243
|
+
break
|
|
244
|
+
case QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
|
|
245
|
+
p.maxUdpPayloadSize = value
|
|
246
|
+
break
|
|
247
|
+
case QUIC_TP_INITIAL_MAX_DATA:
|
|
248
|
+
p.initialMaxData = value
|
|
249
|
+
break
|
|
250
|
+
case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
|
|
251
|
+
p.initialMaxStreamDataBidiLocal = value
|
|
252
|
+
break
|
|
253
|
+
case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
|
|
254
|
+
p.initialMaxStreamDataBidiRemote = value
|
|
255
|
+
break
|
|
256
|
+
case QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
|
|
257
|
+
p.initialMaxStreamDataUni = value
|
|
258
|
+
break
|
|
259
|
+
case QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
|
|
260
|
+
p.initialMaxStreamsBidi = value
|
|
261
|
+
break
|
|
262
|
+
case QUIC_TP_INITIAL_MAX_STREAMS_UNI:
|
|
263
|
+
p.initialMaxStreamsUni = value
|
|
264
|
+
break
|
|
265
|
+
case QUIC_TP_ACK_DELAY_EXPONENT:
|
|
266
|
+
p.ackDelayExponent = value
|
|
267
|
+
break
|
|
268
|
+
case QUIC_TP_MAX_ACK_DELAY:
|
|
269
|
+
p.maxAckDelay = value
|
|
270
|
+
break
|
|
271
|
+
default:
|
|
272
|
+
p.activeConnectionIdLimit = value
|
|
273
|
+
break
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Whether `id` is a parameter only a server may send (§18.2). */
|
|
278
|
+
const quicParamsServerOnly = (id: i32): boolean =>
|
|
279
|
+
id === QUIC_TP_ORIGINAL_DCID ||
|
|
280
|
+
id === QUIC_TP_STATELESS_RESET_TOKEN ||
|
|
281
|
+
id === QUIC_TP_PREFERRED_ADDRESS ||
|
|
282
|
+
id === QUIC_TP_RETRY_SCID
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Stores the byte-valued parameter `id` from `bytes[at .. at + length)`.
|
|
286
|
+
* Answers whether its length is one the parameter allows: 16 for the reset
|
|
287
|
+
* token, at most 20 for a connection ID, 0 for `disable_active_migration`.
|
|
288
|
+
*/
|
|
289
|
+
const quicParamsStoreBytes = (
|
|
290
|
+
p: QuicTransportParameters,
|
|
291
|
+
id: i32,
|
|
292
|
+
bytes: u8[],
|
|
293
|
+
at: i32,
|
|
294
|
+
length: i32
|
|
295
|
+
): boolean => {
|
|
296
|
+
switch (id) {
|
|
297
|
+
case QUIC_TP_ORIGINAL_DCID:
|
|
298
|
+
p.originalDcid = quicParamsCopy(bytes, at, length)
|
|
299
|
+
p.hasOriginalDcid = true
|
|
300
|
+
return length <= QUIC_MAX_CID_LENGTH
|
|
301
|
+
case QUIC_TP_STATELESS_RESET_TOKEN:
|
|
302
|
+
p.statelessResetToken = quicParamsCopy(bytes, at, length)
|
|
303
|
+
p.hasStatelessResetToken = true
|
|
304
|
+
return length === QUIC_RESET_TOKEN_SIZE
|
|
305
|
+
case QUIC_TP_DISABLE_ACTIVE_MIGRATION:
|
|
306
|
+
p.disableActiveMigration = true
|
|
307
|
+
return length === 0
|
|
308
|
+
case QUIC_TP_PREFERRED_ADDRESS:
|
|
309
|
+
p.preferredAddress = quicParamsCopy(bytes, at, length)
|
|
310
|
+
p.hasPreferredAddress = true
|
|
311
|
+
return true
|
|
312
|
+
case QUIC_TP_INITIAL_SCID:
|
|
313
|
+
p.initialScid = quicParamsCopy(bytes, at, length)
|
|
314
|
+
p.hasInitialScid = true
|
|
315
|
+
return length <= QUIC_MAX_CID_LENGTH
|
|
316
|
+
default:
|
|
317
|
+
p.retryScid = quicParamsCopy(bytes, at, length)
|
|
318
|
+
p.hasRetryScid = true
|
|
319
|
+
return length <= QUIC_MAX_CID_LENGTH
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Whether `id` is one of the parameters whose value is a varint. */
|
|
324
|
+
const quicParamsIsVarint = (id: i32): boolean =>
|
|
325
|
+
id === QUIC_TP_MAX_IDLE_TIMEOUT ||
|
|
326
|
+
(id >= QUIC_TP_MAX_UDP_PAYLOAD_SIZE && id <= QUIC_TP_MAX_ACK_DELAY) ||
|
|
327
|
+
id === QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Parses the body of a `quic_transport_parameters` extension. `fromServer`
|
|
331
|
+
* says who sent it, which decides whether the server-only parameters are
|
|
332
|
+
* allowed. The answer's `error` is `QUIC_ERROR_NO_ERROR`, or
|
|
333
|
+
* `QUIC_ERROR_TRANSPORT_PARAMETER` for any of the breaks the module header
|
|
334
|
+
* lists, with `errorParameter` naming the parameter; on an error the other
|
|
335
|
+
* fields hold what was read so far and are not to be used.
|
|
336
|
+
*/
|
|
337
|
+
export const quicParseTransportParameters = (bytes: u8[], fromServer: boolean): QuicTransportParameters => {
|
|
338
|
+
const p: QuicTransportParameters = new QuicTransportParameters()
|
|
339
|
+
const end: i32 = toI32(bytes.length)
|
|
340
|
+
// The known IDs are 0 to 16, so one bit each of an i32 records which were
|
|
341
|
+
// seen; RFC 9221's 0x20 has a flag of its own.
|
|
342
|
+
let seen: i32 = 0
|
|
343
|
+
let seenDatagram: boolean = false
|
|
344
|
+
let at: i32 = 0
|
|
345
|
+
while (at < end) {
|
|
346
|
+
const id: i64 = quicVarintRead(bytes, at, end)
|
|
347
|
+
if (id < 0) {
|
|
348
|
+
return quicParamsRefuse(p, -1)
|
|
349
|
+
}
|
|
350
|
+
at = at + quicVarintLength(bytes, at)
|
|
351
|
+
const length: i64 = quicVarintRead(bytes, at, end)
|
|
352
|
+
if (length < 0) {
|
|
353
|
+
return quicParamsRefuse(p, id)
|
|
354
|
+
}
|
|
355
|
+
at = at + quicVarintLength(bytes, at)
|
|
356
|
+
if (length > toI64(end - at)) {
|
|
357
|
+
return quicParamsRefuse(p, id)
|
|
358
|
+
}
|
|
359
|
+
const valueAt: i32 = at
|
|
360
|
+
const valueLength: i32 = toI32(length)
|
|
361
|
+
at = at + valueLength
|
|
362
|
+
if (id === toI64(QUIC_TP_MAX_DATAGRAM_FRAME_SIZE)) {
|
|
363
|
+
const value: i64 = quicParamsVarintValue(bytes, valueAt, valueLength)
|
|
364
|
+
if (seenDatagram || value < 0) {
|
|
365
|
+
return quicParamsRefuse(p, id)
|
|
366
|
+
}
|
|
367
|
+
seenDatagram = true
|
|
368
|
+
p.maxDatagramFrameSize = value
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
if (id > toI64(QUIC_TP_RETRY_SCID)) {
|
|
372
|
+
continue
|
|
373
|
+
}
|
|
374
|
+
const known: i32 = toI32(id)
|
|
375
|
+
const bit: i32 = 1 << known
|
|
376
|
+
if ((seen & bit) !== 0 || (!fromServer && quicParamsServerOnly(known))) {
|
|
377
|
+
return quicParamsRefuse(p, id)
|
|
378
|
+
}
|
|
379
|
+
seen = seen | bit
|
|
380
|
+
if (quicParamsIsVarint(known)) {
|
|
381
|
+
const value: i64 = quicParamsVarintValue(bytes, valueAt, valueLength)
|
|
382
|
+
if (value < 0 || !quicParamsValueFits(known, value)) {
|
|
383
|
+
return quicParamsRefuse(p, id)
|
|
384
|
+
}
|
|
385
|
+
quicParamsStoreVarint(p, known, value)
|
|
386
|
+
} else if (!quicParamsStoreBytes(p, known, bytes, valueAt, valueLength)) {
|
|
387
|
+
return quicParamsRefuse(p, id)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
p.error = QUIC_ERROR_NO_ERROR
|
|
391
|
+
return p
|
|
392
|
+
}
|
|
@@ -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
|
+
}
|