@amritk/nish-x86_64-linux 0.16.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.
Files changed (55) 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 +30 -7
  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-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -0
@@ -0,0 +1,823 @@
1
+ /**
2
+ * `nish/net/websocket` — RFC 6455 WebSocket framing and the opening
3
+ * handshake's accept key, sans-IO: bytes in, events and bytes out.
4
+ *
5
+ * **The handshake.** `nish/net/http1` reads the client's request;
6
+ * `websocketRequestKey(parser)` answers its `Sec-WebSocket-Key` when the head
7
+ * is a valid opening handshake (§4.2.1), and `websocketUpgradeResponse(key,
8
+ * protocol)` writes the 101 that accepts it, with `Sec-WebSocket-Accept` from
9
+ * `websocketAcceptKey` (§4.2.2): the base64 of the SHA-1 of the key and a
10
+ * fixed GUID. What `HTTP1_UPGRADE`'s `upgradeBytes()` answers is the first
11
+ * input for the decoder.
12
+ *
13
+ * **The decoder** is a push-pull machine like `Http1Parser`: `feed` any slice,
14
+ * then call `next()` until it answers `WS_NEED_MORE`.
15
+ *
16
+ * const d = new WsDecoder(true, maxMessage); // a server: frames must be masked
17
+ * d.feed(received, 0, n);
18
+ * let event: i32 = d.next();
19
+ * while (event !== WS_NEED_MORE) {
20
+ * // WS_MESSAGE: d.opcode is WS_OP_TEXT or WS_OP_BINARY, the message
21
+ * // is d.data[0 .. d.dataLen), valid until the next call
22
+ * // WS_PING / WS_PONG: the payload, the same way
23
+ * // WS_CLOSE: d.closeCode and d.closeReason; answer with a close
24
+ * // WS_ERROR: fail the connection, sending a close with d.closeCode
25
+ * event = d.next();
26
+ * }
27
+ *
28
+ * Fragments are joined: `WS_MESSAGE` comes once, with the whole message, and
29
+ * control frames between the fragments of a message come out in their place
30
+ * (§5.4). `WS_CLOSE` and `WS_ERROR` are final and repeat on every later call;
31
+ * input after them is dropped.
32
+ *
33
+ * **What it refuses** (`WS_ERROR`, and the status code to close with):
34
+ *
35
+ * - 1002, a protocol error: a reserved bit set, since no extension is
36
+ * negotiated (§5.2); a reserved opcode; a control frame that is fragmented or
37
+ * longer than 125 bytes (§5.5); a frame masked when the decoder expects it
38
+ * not to be, or the reverse (§5.1); a length not in its minimal form, or a
39
+ * 64-bit length with its top bit set (§5.2); a continuation with no message
40
+ * to continue, or a new message while one is unfinished (§5.4); a close
41
+ * frame with a one-byte payload, or a status code that may not be sent
42
+ * (§5.5.1, §7.4).
43
+ * - 1007: a text message, or a close reason, that is not UTF-8 (§8.1). A text
44
+ * message is checked as each fragment arrives, so a bad byte is refused
45
+ * without waiting for the rest.
46
+ * - 1009: a message longer than `maxMessage` (§7.4.1), refused from the frame
47
+ * header that would pass it, before its payload arrives.
48
+ *
49
+ * **The encoder** is `websocketFrame`, which writes one frame with the mask a
50
+ * client supplies (the mask key is randomness, so it is an argument here), and
51
+ * `websocketClosePayload` for a close frame's code and reason. Both refuse
52
+ * (`null`) what the decoder would.
53
+ *
54
+ * Private helpers share the importing program's flat symbol namespace
55
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
56
+ *
57
+ * Written from RFC 6455, not ported from another implementation.
58
+ */
59
+ import { base64urlDecode, base64urlEncode } from "nish/crypto/base64url"
60
+ import { sha1 } from "nish/crypto/sha1"
61
+ import { HTTP1_NO_BODY, Http1Parser, http1ResponseHead } from "nish/net/http1"
62
+
63
+ /** The GUID RFC 6455 §1.3 appends to the client's key before hashing it. */
64
+ export const WS_GUID: string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
65
+
66
+ // Opcodes (§5.2).
67
+ export const WS_OP_CONTINUATION: i32 = 0
68
+ export const WS_OP_TEXT: i32 = 1
69
+ export const WS_OP_BINARY: i32 = 2
70
+ export const WS_OP_CLOSE: i32 = 8
71
+ export const WS_OP_PING: i32 = 9
72
+ export const WS_OP_PONG: i32 = 10
73
+
74
+ // Close status codes (§7.4.1).
75
+ export const WS_CLOSE_NORMAL: i32 = 1000
76
+ export const WS_CLOSE_GOING_AWAY: i32 = 1001
77
+ export const WS_CLOSE_PROTOCOL_ERROR: i32 = 1002
78
+ export const WS_CLOSE_UNSUPPORTED: i32 = 1003
79
+ /** Reported for a close frame with no status; never sent. */
80
+ export const WS_CLOSE_NO_STATUS: i32 = 1005
81
+ export const WS_CLOSE_INVALID_DATA: i32 = 1007
82
+ export const WS_CLOSE_POLICY: i32 = 1008
83
+ export const WS_CLOSE_TOO_BIG: i32 = 1009
84
+ export const WS_CLOSE_INTERNAL_ERROR: i32 = 1011
85
+
86
+ /** The longest payload a control frame may carry (§5.5). */
87
+ export const WS_MAX_CONTROL: i32 = 125
88
+
89
+ // `next()`'s events.
90
+ /** The input so far holds no further event; feed more. */
91
+ export const WS_NEED_MORE: i32 = 0
92
+ /** A whole text or binary message is in `data[0 .. dataLen)`. */
93
+ export const WS_MESSAGE: i32 = 1
94
+ /** A ping, its payload in `data[0 .. dataLen)`; answer with a pong carrying it. */
95
+ export const WS_PING: i32 = 2
96
+ /** A pong, its payload in `data[0 .. dataLen)`. */
97
+ export const WS_PONG: i32 = 3
98
+ /** From now on: the peer closed, with `closeCode` and `closeReason`. */
99
+ export const WS_CLOSE: i32 = 4
100
+ /** From now on: the input is refused; close with `closeCode`. */
101
+ export const WS_ERROR: i32 = 5
102
+
103
+ // The decoder's states.
104
+ const WS_S_FRAMES: i32 = 0
105
+ const WS_S_CLOSED: i32 = 1
106
+ const WS_S_ERROR: i32 = 2
107
+
108
+ /**
109
+ * Where a UTF-8 check stands between two runs of bytes: the continuation
110
+ * bytes still owed, and the range the next one must fall in (Unicode §3.9,
111
+ * Table 3-7), so that a sequence cut between two fragments is checked whole.
112
+ */
113
+ class WsUtf8 {
114
+ need: i32 = 0
115
+ lo: i32 = 0x80
116
+ hi: i32 = 0xbf
117
+ }
118
+
119
+ /** Puts `state` back where a check starts: no sequence open. */
120
+ const websocketUtf8Reset = (state: WsUtf8): void => {
121
+ state.need = 0
122
+ state.lo = 0x80
123
+ state.hi = 0xbf
124
+ }
125
+
126
+ /** Whether RFC 6455 §5.2 defines `opcode`: 0 to 2 for data, 8 to 10 for control. */
127
+ const websocketIsKnownOpcode = (opcode: i32): boolean =>
128
+ (opcode >= 0 && opcode <= 2) || (opcode >= 8 && opcode <= 10)
129
+
130
+ /**
131
+ * `websocketFrameEvent`'s answer for a fragment that does not end its
132
+ * message: its frame is consumed, and the loop reads the next one. Never an
133
+ * event.
134
+ */
135
+ const WS_CONTINUE: i32 = -1
136
+
137
+ /**
138
+ * Runs `buf[off .. off + len)` through the check `state` holds. Answers false
139
+ * at the first byte that cannot continue well-formed UTF-8 (RFC 3629 §4: no
140
+ * stray continuation byte, no overlong form, no surrogate, nothing past
141
+ * U+10FFFF); a sequence still open at the end is left in `state`. The window
142
+ * is the caller's, already checked against `buf`.
143
+ */
144
+ const websocketUtf8Run = (state: WsUtf8, buf: u8[], off: i32, len: i32): boolean => {
145
+ let need: i32 = state.need
146
+ let lo: i32 = state.lo
147
+ let hi: i32 = state.hi
148
+ for (let k: i32 = off; k >= 0 && k < off + len && k < toI32(buf.length); k += 1) {
149
+ const b: i32 = toI32(buf[k])
150
+ if (need > 0) {
151
+ if (b < lo || b > hi) {
152
+ return false
153
+ }
154
+ need -= 1
155
+ lo = 0x80
156
+ hi = 0xbf
157
+ } else if (b < 0x80) {
158
+ // ASCII, NUL included: U+0000 is a character.
159
+ } else if (b >= 0xc2 && b <= 0xdf) {
160
+ need = 1
161
+ } else if (b >= 0xe0 && b <= 0xef) {
162
+ need = 2
163
+ lo = b === 0xe0 ? 0xa0 : 0x80
164
+ hi = b === 0xed ? 0x9f : 0xbf
165
+ } else if (b >= 0xf0 && b <= 0xf4) {
166
+ need = 3
167
+ lo = b === 0xf0 ? 0x90 : 0x80
168
+ hi = b === 0xf4 ? 0x8f : 0xbf
169
+ } else {
170
+ return false
171
+ }
172
+ }
173
+ state.need = need
174
+ state.lo = lo
175
+ state.hi = hi
176
+ return true
177
+ }
178
+
179
+ /** Whether `buf[off .. off + len)` is well-formed UTF-8. A window outside `buf` panics. */
180
+ export const websocketIsUtf8 = (buf: u8[], off: i32, len: i32): boolean => {
181
+ const size: i32 = toI32(buf.length)
182
+ if (off < 0 || len < 0 || off > size || len > size - off) {
183
+ panic("websocketIsUtf8: the window is outside the buffer")
184
+ }
185
+ const state: WsUtf8 = new WsUtf8()
186
+ return websocketUtf8Run(state, buf, off, len) && state.need === 0
187
+ }
188
+
189
+ /**
190
+ * Whether `code` may be sent in a close frame, and so accepted in one
191
+ * (§7.4): the codes RFC 6455 defines for the wire, 1012 to 1014 as IANA has
192
+ * since registered them, and 3000 to 4999 for libraries and applications.
193
+ * 1004, 1005, 1006 and 1015 are reserved or report-only.
194
+ */
195
+ const websocketIsSendableCode = (code: i32): boolean =>
196
+ (code >= 1000 && code <= 1003) || (code >= 1007 && code <= 1014) || (code >= 3000 && code <= 4999)
197
+
198
+ /**
199
+ * One connection's frame decoder. `expectMasked` is true on a server, whose
200
+ * client must mask every frame, and false on a client, whose server must
201
+ * mask none (§5.1). `maxMessage` bounds a message, all its fragments together.
202
+ */
203
+ export class WsDecoder {
204
+ // References first, then numbers, then flags: no padding in the struct.
205
+ /** The last event's payload is `data[0 .. dataLen)`, valid until the next `next()`. */
206
+ data: u8[]
207
+ /** The peer's close reason after `WS_CLOSE`, or what was wrong after `WS_ERROR`. */
208
+ closeReason: string = ""
209
+ /** The bytes fed and not yet consumed are `buf[start .. end)`. */
210
+ buf: u8[]
211
+ /** The message being assembled; its first `messageLen` bytes are live. */
212
+ message: u8[]
213
+ /** A control frame's payload, kept apart so a ping cannot disturb a message in progress. */
214
+ control: u8[]
215
+ /** The UTF-8 check of the text message being assembled. */
216
+ utf8: WsUtf8
217
+
218
+ maxMessage: i32 = 0
219
+ dataLen: i32 = 0
220
+ /** The last event's opcode: `WS_OP_TEXT` or `WS_OP_BINARY` for a message, the control opcode otherwise. */
221
+ opcode: i32 = 0
222
+ /** The peer's status code after `WS_CLOSE` (`WS_CLOSE_NO_STATUS` when it sent none), or the one to send after `WS_ERROR`. */
223
+ closeCode: i32 = 0
224
+ start: i32 = 0
225
+ end: i32 = 0
226
+ state: i32 = 0
227
+ /** The opcode of the message whose fragments are arriving, or 0 between messages. */
228
+ messageOpcode: i32 = 0
229
+ messageLen: i32 = 0
230
+
231
+ expectMasked: boolean = false
232
+
233
+ constructor(expectMasked: boolean, maxMessage: i32) {
234
+ this.expectMasked = expectMasked
235
+ // Below zero allows only empty messages, as zero does.
236
+ this.maxMessage = maxMessage
237
+ if (maxMessage < 0) {
238
+ this.maxMessage = 0
239
+ }
240
+ this.buf = new Array<u8>(1024)
241
+ this.message = new Array<u8>(256)
242
+ this.control = new Array<u8>(WS_MAX_CONTROL)
243
+ this.data = this.control
244
+ this.utf8 = new WsUtf8()
245
+ this.reset()
246
+ }
247
+
248
+ /**
249
+ * Appends `data[off .. off + len)` to the input. A window outside `data`
250
+ * panics; after `WS_CLOSE` or `WS_ERROR` the input is dropped.
251
+ */
252
+ feed(data: u8[], off: i32, len: i32): void {
253
+ const size: i32 = toI32(data.length)
254
+ if (off < 0 || len < 0 || off > size || len > size - off) {
255
+ panic("WsDecoder: the window is outside the buffer")
256
+ }
257
+ if (this.state !== WS_S_FRAMES) {
258
+ return
259
+ }
260
+ websocketReserve(this, len)
261
+ const buf: u8[] = this.buf
262
+ const at: i32 = this.end
263
+ // A whole array is one `set`; `set` takes no window of its source.
264
+ if (off === 0 && len === size) {
265
+ buf.set(data, at)
266
+ this.end = at + len
267
+ return
268
+ }
269
+ for (let k: i32 = 0; k < len; k += 1) {
270
+ const to: i32 = at + k
271
+ if (to >= 0 && to < toI32(buf.length)) {
272
+ buf[to] = data[off + k]
273
+ }
274
+ }
275
+ this.end = at + len
276
+ }
277
+
278
+ /** The next event the input holds; see the module comment. */
279
+ next(): i32 {
280
+ return websocketNext(this)
281
+ }
282
+
283
+ /**
284
+ * Puts the decoder back as the constructor left it, for the next
285
+ * connection, keeping the buffers it has grown: a slot that is reused
286
+ * allocates nothing to start over.
287
+ */
288
+ reset(): void {
289
+ this.state = WS_S_FRAMES
290
+ this.start = 0
291
+ this.end = 0
292
+ this.dataLen = 0
293
+ this.opcode = 0
294
+ this.closeCode = 0
295
+ this.closeReason = ""
296
+ this.messageOpcode = 0
297
+ this.messageLen = 0
298
+ this.data = this.control
299
+ websocketUtf8Reset(this.utf8)
300
+ }
301
+ }
302
+
303
+ /** Makes room for `len` more input bytes at `d.end`, moving the live bytes down first. */
304
+ const websocketReserve = (d: WsDecoder, len: i32): void => {
305
+ const old: u8[] = d.buf
306
+ const capacity: i32 = toI32(old.length)
307
+ if (len <= capacity - d.end) {
308
+ return
309
+ }
310
+ const live: i32 = d.end - d.start
311
+ let size: i32 = capacity
312
+ while (size - live < len) {
313
+ if (size > 1073741823) {
314
+ panic("WsDecoder: more than 2^31 - 1 bytes buffered")
315
+ }
316
+ size = size * 2
317
+ }
318
+ const buf: u8[] = size === capacity ? old : new Array<u8>(size)
319
+ const from: i32 = d.start
320
+ for (let k: i32 = 0; k < live && k < toI32(buf.length); k += 1) {
321
+ buf[k] = old[from + k]
322
+ }
323
+ d.buf = buf
324
+ d.start = 0
325
+ d.end = live
326
+ }
327
+
328
+ /** Refuses the input; the connection is to be closed with `code`. Answers `WS_ERROR`. */
329
+ const websocketFail = (d: WsDecoder, code: i32, reason: string): i32 => {
330
+ d.state = WS_S_ERROR
331
+ d.closeCode = code
332
+ d.closeReason = reason
333
+ d.dataLen = 0
334
+ return WS_ERROR
335
+ }
336
+
337
+ /**
338
+ * Copies the frame payload at `buf[from .. from + len)` into `to[at ..)`,
339
+ * unmasking it with the key at `buf[maskAt .. maskAt + 4)` when `masked`
340
+ * (§5.3: byte i is XORed with key byte i mod 4).
341
+ */
342
+ const websocketUnmaskInto = (
343
+ buf: u8[],
344
+ from: i32,
345
+ len: i32,
346
+ masked: boolean,
347
+ maskAt: i32,
348
+ to: u8[],
349
+ at: i32
350
+ ): void => {
351
+ // The four key bytes in one word, high byte first, rotated a byte left
352
+ // after each payload byte, so its top byte is always key byte k mod 4.
353
+ let key: u32 = 0
354
+ if (masked) {
355
+ key =
356
+ (toU32(buf[maskAt]) << 24) |
357
+ (toU32(buf[maskAt + 1]) << 16) |
358
+ (toU32(buf[maskAt + 2]) << 8) |
359
+ toU32(buf[maskAt + 3])
360
+ }
361
+ for (let k: i32 = 0; k < len; k += 1) {
362
+ to[at + k] = buf[from + k] ^ toU8(key >>> 24)
363
+ key = (key << 8) | (key >>> 24)
364
+ }
365
+ }
366
+
367
+ /** Grows `d.message` so that `need` bytes fit, keeping its live bytes. */
368
+ const websocketMessageRoom = (d: WsDecoder, need: i32): void => {
369
+ const old: u8[] = d.message
370
+ let size: i32 = toI32(old.length)
371
+ if (need <= size) {
372
+ return
373
+ }
374
+ while (size < need) {
375
+ size = size > 1073741823 ? 2147483647 : size * 2
376
+ }
377
+ const grown: u8[] = new Array<u8>(size)
378
+ for (let k: i32 = 0; k < d.messageLen && k < toI32(grown.length) && k < toI32(old.length); k += 1) {
379
+ grown[k] = old[k]
380
+ }
381
+ d.message = grown
382
+ }
383
+
384
+ /**
385
+ * Reads the close frame payload now in `d.control` (§5.5.1): no payload is
386
+ * `WS_CLOSE_NO_STATUS`, one byte is refused, and otherwise a sendable status
387
+ * code and a UTF-8 reason. Answers `WS_CLOSE` or the refusal.
388
+ */
389
+ const websocketReadClose = (d: WsDecoder, len: i32): i32 => {
390
+ const payload: u8[] = d.control
391
+ if (len === 0) {
392
+ d.closeCode = WS_CLOSE_NO_STATUS
393
+ d.closeReason = ""
394
+ d.state = WS_S_CLOSED
395
+ return WS_CLOSE
396
+ }
397
+ if (len === 1) {
398
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a close frame with a one-byte payload")
399
+ }
400
+ const code: i32 = (toI32(payload[0]) << 8) | toI32(payload[1])
401
+ if (!websocketIsSendableCode(code)) {
402
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a close status code that may not be sent")
403
+ }
404
+ const reasonStart: i32 = 2
405
+ if (!websocketIsUtf8(payload, reasonStart, len - 2)) {
406
+ return websocketFail(d, WS_CLOSE_INVALID_DATA, "a close reason that is not UTF-8")
407
+ }
408
+ const parts: string[] = []
409
+ for (let k: i32 = 2; k < len && k < toI32(payload.length); k += 1) {
410
+ parts.push(String.fromCharCode(toI32(payload[k])))
411
+ }
412
+ d.closeCode = code
413
+ d.closeReason = parts.join("")
414
+ d.state = WS_S_CLOSED
415
+ return WS_CLOSE
416
+ }
417
+
418
+ /**
419
+ * Reads one frame from the input, if a whole one is there, and answers its
420
+ * event: `WS_NEED_MORE` for no whole frame and for a fragment that does not
421
+ * end its message, or the frame's event, or the refusal. Every check that the
422
+ * header alone decides is made before the payload is waited for.
423
+ */
424
+ const websocketFrameEvent = (d: WsDecoder): i32 => {
425
+ const buf: u8[] = d.buf
426
+ const at: i32 = d.start
427
+ const ready: i32 = d.end - at
428
+ if (ready < 2 || at < 0 || at + 1 >= toI32(buf.length)) {
429
+ return WS_NEED_MORE
430
+ }
431
+ const b0: i32 = toI32(buf[at])
432
+ const b1: i32 = toI32(buf[at + 1])
433
+ const fin: boolean = (b0 & 0x80) !== 0
434
+ const opcode: i32 = b0 & 0x0f
435
+ const masked: boolean = (b1 & 0x80) !== 0
436
+ const short: i32 = b1 & 0x7f
437
+ // The bytes of the extended length: two after 126, eight after 127.
438
+ let extended: i32 = 0
439
+ if (short === 126) {
440
+ extended = 2
441
+ } else if (short === 127) {
442
+ extended = 8
443
+ }
444
+ const headerLength: i32 = 2 + extended + (masked ? 4 : 0)
445
+ if (ready < headerLength) {
446
+ return WS_NEED_MORE
447
+ }
448
+ if ((b0 & 0x70) !== 0) {
449
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a reserved bit set with no extension negotiated")
450
+ }
451
+ const control: boolean = opcode >= 8
452
+ if (!websocketIsKnownOpcode(opcode)) {
453
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a reserved opcode")
454
+ }
455
+ if (masked !== d.expectMasked) {
456
+ return websocketFail(
457
+ d,
458
+ WS_CLOSE_PROTOCOL_ERROR,
459
+ d.expectMasked ? "an unmasked frame from a client" : "a masked frame from a server"
460
+ )
461
+ }
462
+ // The payload length (§5.2), held to `i32` and refused before it is used.
463
+ let length: i32 = short
464
+ if (extended === 2) {
465
+ length = (toI32(buf[at + 2]) << 8) | toI32(buf[at + 3])
466
+ if (length < 126) {
467
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a 16-bit length under 126")
468
+ }
469
+ } else if (extended === 8) {
470
+ if ((toI32(buf[at + 2]) & 0x80) !== 0) {
471
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a 64-bit length with its top bit set")
472
+ }
473
+ // Anything in the high four bytes, or a top bit in the low four, is more
474
+ // than an `i32` holds and so more than any `maxMessage`.
475
+ let high: i32 = 0
476
+ for (let k: i32 = 2; k < 6; k += 1) {
477
+ high = high | toI32(buf[at + k])
478
+ }
479
+ if (high !== 0 || (toI32(buf[at + 6]) & 0x80) !== 0) {
480
+ return websocketFail(d, WS_CLOSE_TOO_BIG, "a frame longer than 2^31 - 1 bytes")
481
+ }
482
+ length =
483
+ (toI32(buf[at + 6]) << 24) | (toI32(buf[at + 7]) << 16) | (toI32(buf[at + 8]) << 8) | toI32(buf[at + 9])
484
+ if (length < 65536) {
485
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a 64-bit length under 65536")
486
+ }
487
+ }
488
+ if (control) {
489
+ if (!fin) {
490
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a fragmented control frame")
491
+ }
492
+ if (length > WS_MAX_CONTROL) {
493
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a control frame longer than 125 bytes")
494
+ }
495
+ } else {
496
+ if (opcode === WS_OP_CONTINUATION && d.messageOpcode === 0) {
497
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a continuation with no message to continue")
498
+ }
499
+ if (opcode !== WS_OP_CONTINUATION && d.messageOpcode !== 0) {
500
+ return websocketFail(d, WS_CLOSE_PROTOCOL_ERROR, "a new message before the last one ended")
501
+ }
502
+ if (length > d.maxMessage - d.messageLen) {
503
+ return websocketFail(d, WS_CLOSE_TOO_BIG, "a message longer than the limit")
504
+ }
505
+ }
506
+ if (ready - headerLength < length) {
507
+ return WS_NEED_MORE
508
+ }
509
+ // The whole frame is here: take it out of the input.
510
+ const payloadAt: i32 = at + headerLength
511
+ const maskAt: i32 = payloadAt - 4
512
+ d.start = payloadAt + length
513
+ if (control) {
514
+ websocketUnmaskInto(buf, payloadAt, length, masked, maskAt, d.control, 0)
515
+ d.opcode = opcode
516
+ d.data = d.control
517
+ d.dataLen = length
518
+ if (opcode === WS_OP_CLOSE) {
519
+ return websocketReadClose(d, length)
520
+ }
521
+ return opcode === WS_OP_PING ? WS_PING : WS_PONG
522
+ }
523
+ if (opcode !== WS_OP_CONTINUATION) {
524
+ d.messageOpcode = opcode
525
+ // A new message starts its UTF-8 check afresh, in place: nothing is
526
+ // allocated per message.
527
+ websocketUtf8Reset(d.utf8)
528
+ }
529
+ const from: i32 = d.messageLen
530
+ websocketMessageRoom(d, from + length)
531
+ websocketUnmaskInto(buf, payloadAt, length, masked, maskAt, d.message, from)
532
+ d.messageLen = from + length
533
+ if (d.messageOpcode === WS_OP_TEXT && !websocketUtf8Run(d.utf8, d.message, from, length)) {
534
+ return websocketFail(d, WS_CLOSE_INVALID_DATA, "a text message that is not UTF-8")
535
+ }
536
+ if (!fin) {
537
+ return WS_CONTINUE
538
+ }
539
+ if (d.messageOpcode === WS_OP_TEXT && d.utf8.need !== 0) {
540
+ return websocketFail(d, WS_CLOSE_INVALID_DATA, "a text message that ends inside a character")
541
+ }
542
+ // The message is handed out whole, and the next data frame starts a new one
543
+ // at the front of the same buffer, so `data` holds until the next call.
544
+ d.opcode = d.messageOpcode
545
+ d.messageOpcode = 0
546
+ d.data = d.message
547
+ d.dataLen = d.messageLen
548
+ d.messageLen = 0
549
+ return WS_MESSAGE
550
+ }
551
+
552
+ /** The loop behind `next()`: frames until one has an event or the input runs out. */
553
+ const websocketNext = (d: WsDecoder): i32 => {
554
+ while (d.state === WS_S_FRAMES) {
555
+ const event: i32 = websocketFrameEvent(d)
556
+ if (event !== WS_CONTINUE) {
557
+ return event
558
+ }
559
+ }
560
+ return d.state === WS_S_CLOSED ? WS_CLOSE : WS_ERROR
561
+ }
562
+
563
+ // --- The encoder -------------------------------------------------------------
564
+
565
+ /** The longest payload a frame may carry here: its header, at most 14 bytes, still fits an `i32` length. */
566
+ const WS_MAX_FRAME_PAYLOAD: i32 = 2147483633
567
+
568
+ /** `websocketWriteFrame`: the frame does not fit at `at`. */
569
+ export const WS_NO_ROOM: i32 = -1
570
+ /** `websocketWriteFrame`: a frame the encoder refuses to write. */
571
+ export const WS_REFUSED: i32 = -2
572
+
573
+ /** How many bytes a frame of `len` payload bytes takes, its header included. */
574
+ export const websocketFrameSize = (len: i32, masked: boolean): i32 => {
575
+ let extended: i32 = 8
576
+ if (len < 126) {
577
+ extended = 0
578
+ } else if (len < 65536) {
579
+ extended = 2
580
+ }
581
+ return 2 + extended + (masked ? 4 : 0) + len
582
+ }
583
+
584
+ /**
585
+ * Writes one frame (§5.2) into `out` at `at`: FIN, the opcode, the payload
586
+ * `payload[off .. off + len)` and, when `mask` is not null, masked with that
587
+ * 4-byte key. A client masks every frame with a fresh key from
588
+ * `crypto.getRandomValues` (§5.3); a server passes null. The length takes the
589
+ * shortest of its three forms. Answers where the frame ends, `WS_NO_ROOM` when
590
+ * it does not fit, or `WS_REFUSED` for an opcode RFC 6455 does not define, a
591
+ * control frame that is not FIN or is longer than 125 bytes, and a mask that
592
+ * is not 4 bytes; on either refusal `out` is untouched. A window outside
593
+ * `payload` panics. A text frame's payload is not checked here, because a
594
+ * fragment may end inside a character; `websocketIsUtf8` checks a whole
595
+ * message.
596
+ */
597
+ export const websocketWriteFrame = (
598
+ out: u8[],
599
+ at: i32,
600
+ fin: boolean,
601
+ opcode: i32,
602
+ payload: u8[],
603
+ off: i32,
604
+ len: i32,
605
+ mask: u8[] | null
606
+ ): i32 => {
607
+ const size: i32 = toI32(payload.length)
608
+ if (off < 0 || len < 0 || off > size || len > size - off) {
609
+ panic("websocketWriteFrame: the window is outside the buffer")
610
+ }
611
+ if (!websocketIsKnownOpcode(opcode)) {
612
+ return WS_REFUSED
613
+ }
614
+ const control: boolean = opcode >= 8
615
+ if (control && (!fin || len > WS_MAX_CONTROL)) {
616
+ return WS_REFUSED
617
+ }
618
+ if (mask !== null && toI32(mask.length) !== 4) {
619
+ return WS_REFUSED
620
+ }
621
+ if (len > WS_MAX_FRAME_PAYLOAD) {
622
+ panic("websocketWriteFrame: a frame longer than 2^31 - 1 bytes")
623
+ }
624
+ const frameSize: i32 = websocketFrameSize(len, mask !== null)
625
+ const outLength: i32 = toI32(out.length)
626
+ if (at < 0 || at > outLength || frameSize > outLength - at) {
627
+ return WS_NO_ROOM
628
+ }
629
+ let extended: i32 = 8
630
+ if (len < 126) {
631
+ extended = 0
632
+ } else if (len < 65536) {
633
+ extended = 2
634
+ }
635
+ const headerLength: i32 = frameSize - len
636
+ const finBit: i32 = fin ? 0x80 : 0
637
+ out[at] = toU8(finBit | opcode)
638
+ const maskBit: i32 = mask === null ? 0 : 0x80
639
+ if (extended === 0) {
640
+ out[at + 1] = toU8(maskBit | len)
641
+ } else if (extended === 2) {
642
+ out[at + 1] = toU8(maskBit | 126)
643
+ out[at + 2] = toU8(len >> 8)
644
+ out[at + 3] = toU8(len)
645
+ } else {
646
+ out[at + 1] = toU8(maskBit | 127)
647
+ // The top four bytes are zero: `len` is an `i32`.
648
+ for (let k: i32 = 2; k < 6; k += 1) {
649
+ out[at + k] = 0
650
+ }
651
+ out[at + 6] = toU8(len >> 24)
652
+ out[at + 7] = toU8(len >> 16)
653
+ out[at + 8] = toU8(len >> 8)
654
+ out[at + 9] = toU8(len)
655
+ }
656
+ const payloadAt: i32 = at + headerLength
657
+ for (let k: i32 = 0; k < len && payloadAt + k < outLength; k += 1) {
658
+ out[payloadAt + k] = payload[off + k]
659
+ }
660
+ if (mask !== null) {
661
+ const keyAt: i32 = at + 2 + extended
662
+ for (let k: i32 = 0; k < 4 && k < toI32(mask.length); k += 1) {
663
+ out[keyAt + k] = mask[k]
664
+ }
665
+ // Masking and unmasking are the same XOR, so the copied payload goes
666
+ // through the decoder's own loop, reading the key just written.
667
+ websocketUnmaskInto(out, payloadAt, len, true, keyAt, out, payloadAt)
668
+ }
669
+ return payloadAt + len
670
+ }
671
+
672
+ /**
673
+ * One frame, as `websocketWriteFrame` writes it, in an array of its own; the
674
+ * same refusals answer `null`. A window outside `payload` panics.
675
+ */
676
+ export const websocketFrame = (
677
+ fin: boolean,
678
+ opcode: i32,
679
+ payload: u8[],
680
+ off: i32,
681
+ len: i32,
682
+ mask: u8[] | null
683
+ ): u8[] | null => {
684
+ const size: i32 = toI32(payload.length)
685
+ if (off < 0 || len < 0 || off > size || len > size - off) {
686
+ panic("websocketFrame: the window is outside the buffer")
687
+ }
688
+ if (len > WS_MAX_FRAME_PAYLOAD) {
689
+ panic("websocketFrame: a frame longer than 2^31 - 1 bytes")
690
+ }
691
+ const out: u8[] = new Array<u8>(websocketFrameSize(len, mask !== null))
692
+ const at: i32 = 0
693
+ return websocketWriteFrame(out, at, fin, opcode, payload, off, len, mask) < 0 ? null : out
694
+ }
695
+
696
+ /**
697
+ * The payload of a close frame (§5.5.1): `code` in two bytes, then `reason`.
698
+ * Answers `null` for a code that may not be sent (1005, 1006 and 1015 among
699
+ * them; §7.4), a reason that is not UTF-8, and one longer than 123 bytes,
700
+ * which would take the frame past 125.
701
+ */
702
+ export const websocketClosePayload = (code: i32, reason: string): u8[] | null => {
703
+ const n: i32 = toI32(reason.length)
704
+ if (!websocketIsSendableCode(code) || n > WS_MAX_CONTROL - 2) {
705
+ return null
706
+ }
707
+ const out: u8[] = new Array<u8>(n + 2)
708
+ const outLength: i32 = toI32(out.length)
709
+ out[0] = toU8(code >> 8)
710
+ out[1] = toU8(code)
711
+ for (let k: i32 = 0; k < n && k + 2 < outLength; k += 1) {
712
+ out[k + 2] = toU8(reason.charCodeAt(k))
713
+ }
714
+ const reasonStart: i32 = 2
715
+ if (!websocketIsUtf8(out, reasonStart, n)) {
716
+ return null
717
+ }
718
+ return out
719
+ }
720
+
721
+ // --- The opening handshake ----------------------------------------------------
722
+
723
+ /** The bytes of `text`, as the language holds a string. */
724
+ const websocketBytesOf = (text: string): u8[] => {
725
+ const n: i32 = toI32(text.length)
726
+ const out: u8[] = new Array<u8>(n)
727
+ const outLength: i32 = toI32(out.length)
728
+ for (let k: i32 = 0; k < outLength && k < n; k += 1) {
729
+ out[k] = toU8(text.charCodeAt(k))
730
+ }
731
+ return out
732
+ }
733
+
734
+ /**
735
+ * `text` with one byte replaced by another throughout: how a base64url
736
+ * spelling becomes standard base64 (`-` and `_` for `+` and `/`) and back.
737
+ */
738
+ const websocketSwapBytes = (text: string, from1: i32, to1: i32, from2: i32, to2: i32): string => {
739
+ const parts: string[] = []
740
+ const n: i32 = toI32(text.length)
741
+ for (let k: i32 = 0; k < n; k += 1) {
742
+ let c: i32 = toI32(text.charCodeAt(k))
743
+ if (c === from1) {
744
+ c = to1
745
+ } else if (c === from2) {
746
+ c = to2
747
+ }
748
+ parts.push(String.fromCharCode(c))
749
+ }
750
+ return parts.join("")
751
+ }
752
+
753
+ /**
754
+ * `Sec-WebSocket-Accept` for the client's `Sec-WebSocket-Key` (§4.2.2 item
755
+ * 5.4): the SHA-1 of the key and `WS_GUID`, in padded standard base64.
756
+ * `nish/crypto/base64url` writes the base64; the URL alphabet's two last
757
+ * characters are swapped for the standard ones, and a 20-byte digest takes one
758
+ * `=` of padding.
759
+ */
760
+ export const websocketAcceptKey = (key: string): string => {
761
+ const digest: u8[] = sha1(websocketBytesOf(`${key}${WS_GUID}`))
762
+ return `${websocketSwapBytes(base64urlEncode(digest), 45, 43, 95, 47)}=`
763
+ }
764
+
765
+ /**
766
+ * Whether `key` is a valid `Sec-WebSocket-Key` (§4.2.1 item 5): padded
767
+ * standard base64 that decodes to exactly 16 bytes, in its one canonical
768
+ * spelling.
769
+ */
770
+ export const websocketKeyIsValid = (key: string): boolean => {
771
+ if (toI32(key.length) !== 24 || !key.endsWith("==")) {
772
+ return false
773
+ }
774
+ const body: string = key.substring(0, 22)
775
+ // A `-` or `_` is not standard base64, so it may not survive the swap as
776
+ // if it were; `=` inside the body is refused by the decoder.
777
+ if (toI32(body.indexOf("-")) >= 0 || toI32(body.indexOf("_")) >= 0) {
778
+ return false
779
+ }
780
+ const bytes: u8[] | null = base64urlDecode(websocketSwapBytes(body, 43, 45, 47, 95))
781
+ return bytes !== null && toI32(bytes.length) === 16
782
+ }
783
+
784
+ /**
785
+ * The client's key when the head `p` has just read is a valid opening
786
+ * handshake (§4.2.1): a GET in HTTP/1.1, asking to upgrade to `websocket`
787
+ * (alone, in any case), at version 13, with a valid key. Otherwise `null`, and
788
+ * the server answers 400 (or 426 with `Sec-WebSocket-Version: 13` when only the
789
+ * version is wrong, §4.4).
790
+ */
791
+ export const websocketRequestKey = (p: Http1Parser): string | null => {
792
+ if (!p.methodIs("GET") || p.minor !== 1 || !p.upgradeIs("websocket")) {
793
+ return null
794
+ }
795
+ if (!p.headerIs("sec-websocket-version", "13")) {
796
+ return null
797
+ }
798
+ const key: string | null = p.header("sec-websocket-key")
799
+ if (key === null || !websocketKeyIsValid(key)) {
800
+ return null
801
+ }
802
+ return key
803
+ }
804
+
805
+ /**
806
+ * The server's 101 response to an opening handshake (§4.2.2): `Upgrade`,
807
+ * `Connection`, `Sec-WebSocket-Accept`, and `Sec-WebSocket-Protocol` when
808
+ * `protocol` is not empty (the caller picks it from the client's list).
809
+ * Answers `null` for an invalid key or a protocol that is not a token.
810
+ */
811
+ export const websocketUpgradeResponse = (key: string, protocol: string): u8[] | null => {
812
+ if (!websocketKeyIsValid(key)) {
813
+ return null
814
+ }
815
+ const names: string[] = ["Upgrade", "Connection", "Sec-WebSocket-Accept"]
816
+ const values: string[] = ["websocket", "Upgrade", websocketAcceptKey(key)]
817
+ if (toI32(protocol.length) > 0) {
818
+ names.push("Sec-WebSocket-Protocol")
819
+ values.push(protocol)
820
+ }
821
+ const switching: i32 = 101
822
+ return http1ResponseHead(switching, "Switching Protocols", names, values, HTTP1_NO_BODY)
823
+ }