@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,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
|
+
}
|