@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.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +30 -7
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-wasm.c +1 -0
- package/runtime/runtime.c +46 -8
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +166 -35
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +148 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- 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,745 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/http2-frame` — the frames of HTTP/2 (RFC 9113 §4 and §6): the
|
|
3
|
+
* nine-byte header, every one of the ten frame types read and written, and
|
|
4
|
+
* the size, flag, padding and stream-identifier rules each one carries.
|
|
5
|
+
*
|
|
6
|
+
* It is sans-IO, like every lane of WP34 §5 below the sockets, and it never
|
|
7
|
+
* allocates: a frame is read out of the caller's buffer into an `Http2Frame`
|
|
8
|
+
* the caller reuses, its content left where it lies as a window, and a frame
|
|
9
|
+
* is written into the caller's buffer at an offset.
|
|
10
|
+
*
|
|
11
|
+
* import { Http2Frame, http2ReadHeader, http2ParseFrame, http2WriteData } from "nish/net/http2-frame";
|
|
12
|
+
*
|
|
13
|
+
* const frame = new Http2Frame();
|
|
14
|
+
* if (http2ReadHeader(frame, buf, off, len) && len >= 9 + frame.length) {
|
|
15
|
+
* const error: i32 = http2ParseFrame(frame, buf, off, maxFrameSize);
|
|
16
|
+
* // 0, or the connection error to close with; frame.streamError, the
|
|
17
|
+
* // stream error to reset the stream with; then buf[frame.contentStart ..
|
|
18
|
+
* // frame.contentStart + frame.contentLength) is the DATA, the header
|
|
19
|
+
* // block fragment or the GOAWAY debug data.
|
|
20
|
+
* }
|
|
21
|
+
* const end: i32 = http2WriteData(out, at, streamId, body, 0, n, H2_FLAG_END_STREAM, -1);
|
|
22
|
+
*
|
|
23
|
+
* **What a parse refuses**, as the error code RFC 9113 names, which closes
|
|
24
|
+
* the connection: a frame longer than the receiver's SETTINGS_MAX_FRAME_SIZE
|
|
25
|
+
* (FRAME_SIZE_ERROR, §4.2); DATA, HEADERS, PRIORITY, RST_STREAM,
|
|
26
|
+
* PUSH_PROMISE or CONTINUATION on stream 0, and SETTINGS, PING or GOAWAY on
|
|
27
|
+
* any other (PROTOCOL_ERROR); a frame too short for its fixed fields, a
|
|
28
|
+
* RST_STREAM or WINDOW_UPDATE that is not four bytes, a PING that is not
|
|
29
|
+
* eight, a SETTINGS whose length is not a multiple of six or an ACK with a
|
|
30
|
+
* payload, and a GOAWAY under eight (FRAME_SIZE_ERROR); padding as long as
|
|
31
|
+
* what it pads or longer (PROTOCOL_ERROR, §6.1); and a WINDOW_UPDATE of zero
|
|
32
|
+
* on the connection (PROTOCOL_ERROR, §6.9). Two refusals only reset the
|
|
33
|
+
* stream, and come back in `streamError` with a parse that answers 0: a
|
|
34
|
+
* PRIORITY of the wrong length (FRAME_SIZE_ERROR, §6.3), and a WINDOW_UPDATE
|
|
35
|
+
* of zero on a stream (PROTOCOL_ERROR). A HEADERS or PRIORITY that makes a
|
|
36
|
+
* stream depend on itself is the same (PROTOCOL_ERROR, §5.3.1). A frame of a
|
|
37
|
+
* type this module does not know is read and answers 0: §4.1 says it is
|
|
38
|
+
* ignored, and that is the connection's to do.
|
|
39
|
+
*
|
|
40
|
+
* SETTINGS values are checked one at a time by `http2SettingError`, because
|
|
41
|
+
* which value a refusal names is the reader's business (§6.5.2): an
|
|
42
|
+
* ENABLE_PUSH or ENABLE_CONNECT_PROTOCOL other than 0 or 1, and a
|
|
43
|
+
* MAX_FRAME_SIZE outside 2^14 to 2^24 − 1, are PROTOCOL_ERROR; an
|
|
44
|
+
* INITIAL_WINDOW_SIZE past 2^31 − 1 is FLOW_CONTROL_ERROR.
|
|
45
|
+
*
|
|
46
|
+
* **The writers** answer the offset after the frame, or -1 when it would not
|
|
47
|
+
* fit in the buffer, which is then unchanged; a window outside its source
|
|
48
|
+
* buffer, a stream identifier outside 0 to 2^31 − 1 and padding past 255 are
|
|
49
|
+
* the program's mistakes and panic. They write any frame the reader accepts
|
|
50
|
+
* and none it refuses, so a program cannot send what its peer must close for.
|
|
51
|
+
*
|
|
52
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
53
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
54
|
+
*
|
|
55
|
+
* Written from RFC 9113, not ported from another implementation.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/** The size of a frame header: a 24-bit length, a type, flags and a 31-bit stream identifier (§4.1). */
|
|
59
|
+
export const H2_FRAME_HEADER_SIZE: i32 = 9
|
|
60
|
+
|
|
61
|
+
/** The frame types of §6. */
|
|
62
|
+
export const H2_FRAME_DATA: i32 = 0
|
|
63
|
+
export const H2_FRAME_HEADERS: i32 = 1
|
|
64
|
+
export const H2_FRAME_PRIORITY: i32 = 2
|
|
65
|
+
export const H2_FRAME_RST_STREAM: i32 = 3
|
|
66
|
+
export const H2_FRAME_SETTINGS: i32 = 4
|
|
67
|
+
export const H2_FRAME_PUSH_PROMISE: i32 = 5
|
|
68
|
+
export const H2_FRAME_PING: i32 = 6
|
|
69
|
+
export const H2_FRAME_GOAWAY: i32 = 7
|
|
70
|
+
export const H2_FRAME_WINDOW_UPDATE: i32 = 8
|
|
71
|
+
export const H2_FRAME_CONTINUATION: i32 = 9
|
|
72
|
+
|
|
73
|
+
/** END_STREAM on DATA and HEADERS; ACK on SETTINGS and PING, the same bit. */
|
|
74
|
+
export const H2_FLAG_END_STREAM: i32 = 1
|
|
75
|
+
export const H2_FLAG_ACK: i32 = 1
|
|
76
|
+
/** END_HEADERS on HEADERS, PUSH_PROMISE and CONTINUATION. */
|
|
77
|
+
export const H2_FLAG_END_HEADERS: i32 = 4
|
|
78
|
+
/** PADDED on DATA, HEADERS and PUSH_PROMISE. */
|
|
79
|
+
export const H2_FLAG_PADDED: i32 = 8
|
|
80
|
+
/** PRIORITY on HEADERS. */
|
|
81
|
+
export const H2_FLAG_PRIORITY: i32 = 32
|
|
82
|
+
|
|
83
|
+
/** The error codes of §7. */
|
|
84
|
+
export const H2_NO_ERROR: i32 = 0
|
|
85
|
+
export const H2_PROTOCOL_ERROR: i32 = 1
|
|
86
|
+
export const H2_INTERNAL_ERROR: i32 = 2
|
|
87
|
+
export const H2_FLOW_CONTROL_ERROR: i32 = 3
|
|
88
|
+
export const H2_SETTINGS_TIMEOUT: i32 = 4
|
|
89
|
+
export const H2_STREAM_CLOSED: i32 = 5
|
|
90
|
+
export const H2_FRAME_SIZE_ERROR: i32 = 6
|
|
91
|
+
export const H2_REFUSED_STREAM: i32 = 7
|
|
92
|
+
export const H2_CANCEL: i32 = 8
|
|
93
|
+
export const H2_COMPRESSION_ERROR: i32 = 9
|
|
94
|
+
export const H2_CONNECT_ERROR: i32 = 10
|
|
95
|
+
export const H2_ENHANCE_YOUR_CALM: i32 = 11
|
|
96
|
+
export const H2_INADEQUATE_SECURITY: i32 = 12
|
|
97
|
+
export const H2_HTTP_1_1_REQUIRED: i32 = 13
|
|
98
|
+
|
|
99
|
+
/** The settings of §6.5.2, and RFC 8441's. */
|
|
100
|
+
export const H2_SETTINGS_HEADER_TABLE_SIZE: i32 = 1
|
|
101
|
+
export const H2_SETTINGS_ENABLE_PUSH: i32 = 2
|
|
102
|
+
export const H2_SETTINGS_MAX_CONCURRENT_STREAMS: i32 = 3
|
|
103
|
+
export const H2_SETTINGS_INITIAL_WINDOW_SIZE: i32 = 4
|
|
104
|
+
export const H2_SETTINGS_MAX_FRAME_SIZE: i32 = 5
|
|
105
|
+
export const H2_SETTINGS_MAX_HEADER_LIST_SIZE: i32 = 6
|
|
106
|
+
export const H2_SETTINGS_ENABLE_CONNECT_PROTOCOL: i32 = 8
|
|
107
|
+
|
|
108
|
+
/** A window's initial size, and the largest a window may reach (§6.9.1, §6.9.2). */
|
|
109
|
+
export const H2_DEFAULT_WINDOW: i32 = 65535
|
|
110
|
+
export const H2_MAX_WINDOW: i32 = 2147483647
|
|
111
|
+
|
|
112
|
+
/** SETTINGS_MAX_FRAME_SIZE's initial value, which is also its least, and its greatest (§6.5.2). */
|
|
113
|
+
export const H2_DEFAULT_MAX_FRAME: i32 = 16384
|
|
114
|
+
export const H2_MAX_FRAME_LIMIT: i32 = 16777215
|
|
115
|
+
|
|
116
|
+
/** The size of one setting: a 16-bit identifier and a 32-bit value. */
|
|
117
|
+
export const H2_SETTING_SIZE: i32 = 6
|
|
118
|
+
|
|
119
|
+
/** A typed zero: a bare literal is an `f64` under `--number-mode f64`. */
|
|
120
|
+
const H2_FRAME_ZERO: i32 = 0
|
|
121
|
+
|
|
122
|
+
/** A typed one: the Pad Length field's size. */
|
|
123
|
+
const H2_FRAME_ONE: i32 = 1
|
|
124
|
+
|
|
125
|
+
/** A typed -1, for a field that is absent. */
|
|
126
|
+
const H2_FRAME_NONE: i32 = -1
|
|
127
|
+
|
|
128
|
+
/** The exclusive bit of a stream dependency (§5.3.1), above the 31-bit identifier. */
|
|
129
|
+
const H2_FRAME_EXCLUSIVE: i64 = 2147483648
|
|
130
|
+
|
|
131
|
+
/** The largest 32-bit value, which bounds a setting's value. */
|
|
132
|
+
const H2_FRAME_U32_MAX: i64 = 4294967295
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* One frame, as `http2ReadHeader` and `http2ParseFrame` read it. Every field
|
|
136
|
+
* is overwritten by the next read, so one frame serves a whole connection.
|
|
137
|
+
*/
|
|
138
|
+
export class Http2Frame {
|
|
139
|
+
/** The payload's length, from the header. */
|
|
140
|
+
length: i32 = 0
|
|
141
|
+
type: i32 = 0
|
|
142
|
+
flags: i32 = 0
|
|
143
|
+
/** The stream identifier, the reserved bit dropped. */
|
|
144
|
+
streamId: i32 = 0
|
|
145
|
+
/** Where the payload starts in the buffer read. */
|
|
146
|
+
payload: i32 = 0
|
|
147
|
+
/**
|
|
148
|
+
* The frame's content, `[contentStart, contentStart + contentLength)` of the
|
|
149
|
+
* buffer: DATA's data, a header block fragment, PING's eight bytes, GOAWAY's
|
|
150
|
+
* debug data, SETTINGS' settings; padding and fixed fields left out.
|
|
151
|
+
*/
|
|
152
|
+
contentStart: i32 = 0
|
|
153
|
+
contentLength: i32 = 0
|
|
154
|
+
/** The Pad Length field, or -1 without PADDED. */
|
|
155
|
+
padLength: i32 = -1
|
|
156
|
+
/** Whether a HEADERS frame carried the PRIORITY fields, as a PRIORITY frame always does. */
|
|
157
|
+
hasPriority: boolean = false
|
|
158
|
+
exclusive: boolean = false
|
|
159
|
+
dependency: i32 = 0
|
|
160
|
+
/** The weight, 1 to 256. */
|
|
161
|
+
weight: i32 = 0
|
|
162
|
+
/** RST_STREAM's and GOAWAY's error code, a 32-bit value. */
|
|
163
|
+
errorCode: i64 = 0
|
|
164
|
+
/** GOAWAY's last stream identifier. */
|
|
165
|
+
lastStreamId: i32 = 0
|
|
166
|
+
/** WINDOW_UPDATE's increment. */
|
|
167
|
+
increment: i32 = 0
|
|
168
|
+
/** PUSH_PROMISE's promised stream identifier. */
|
|
169
|
+
promisedId: i32 = 0
|
|
170
|
+
/** A stream error the frame is, or 0: the stream is reset and the connection lives. */
|
|
171
|
+
streamError: i32 = 0
|
|
172
|
+
|
|
173
|
+
/** Whether the flag `bit` is set. */
|
|
174
|
+
has(bit: i32): boolean {
|
|
175
|
+
return (this.flags & bit) !== 0
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Panics unless `[off, off + len)` lies inside `buf`: a window outside it is the program's mistake. */
|
|
180
|
+
const http2FrameCheckWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
|
|
181
|
+
if (off < 0 || len < 0 || toI64(off) + toI64(len) > toI64(buf.length)) {
|
|
182
|
+
panic(`${what}: the window [${off}, ${off} + ${len}) is outside a buffer of ${buf.length} bytes`)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Panics unless `id` is a stream identifier, 0 to 2^31 − 1. */
|
|
187
|
+
const http2FrameCheckStream = (what: string, id: i32): void => {
|
|
188
|
+
if (id < 0) {
|
|
189
|
+
panic(`${what}: a stream identifier of ${id}`)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The big-endian 32-bit value at `buf[at]`. */
|
|
194
|
+
const http2FrameU32 = (buf: u8[], at: i32): i64 =>
|
|
195
|
+
(toI64(buf[at]) << toI64(24)) |
|
|
196
|
+
(toI64(buf[at + 1]) << toI64(16)) |
|
|
197
|
+
(toI64(buf[at + 2]) << toI64(8)) |
|
|
198
|
+
toI64(buf[at + 3])
|
|
199
|
+
|
|
200
|
+
/** The big-endian 31-bit value at `buf[at]`, its reserved top bit dropped. */
|
|
201
|
+
const http2FrameU31 = (buf: u8[], at: i32): i32 => toI32(http2FrameU32(buf, at) & toI64(2147483647))
|
|
202
|
+
|
|
203
|
+
/** Writes `value`'s low 32 bits big-endian at `out[at]`. */
|
|
204
|
+
const http2FramePut32 = (out: u8[], at: i32, value: i64): void => {
|
|
205
|
+
out[at] = toU8((value >> toI64(24)) & toI64(255))
|
|
206
|
+
out[at + 1] = toU8((value >> toI64(16)) & toI64(255))
|
|
207
|
+
out[at + 2] = toU8((value >> toI64(8)) & toI64(255))
|
|
208
|
+
out[at + 3] = toU8(value & toI64(255))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Reads the nine-byte header at `buf[off]` into `frame`, when `len` bytes from
|
|
213
|
+
* `off` hold one, and answers whether they did. The payload need not be there
|
|
214
|
+
* yet: a reader checks `frame.length` against its limit before it waits for
|
|
215
|
+
* the rest, which is what keeps a peer from making it buffer more.
|
|
216
|
+
*/
|
|
217
|
+
export const http2ReadHeader = (frame: Http2Frame, buf: u8[], off: i32, len: i32): boolean => {
|
|
218
|
+
http2FrameCheckWindow("http2ReadHeader", buf, off, len)
|
|
219
|
+
if (len < H2_FRAME_HEADER_SIZE) {
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
frame.length = (toI32(buf[off]) << 16) | (toI32(buf[off + 1]) << 8) | toI32(buf[off + 2])
|
|
223
|
+
frame.type = toI32(buf[off + 3])
|
|
224
|
+
frame.flags = toI32(buf[off + 4])
|
|
225
|
+
frame.streamId = http2FrameU31(buf, off + 5)
|
|
226
|
+
frame.payload = off + H2_FRAME_HEADER_SIZE
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Takes the Pad Length field off the front of a padded frame's payload, when
|
|
232
|
+
* PADDED is set, leaving `fixed` bytes of fixed fields after it; sets the
|
|
233
|
+
* content window to what lies between them and the padding. Answers 0 or the
|
|
234
|
+
* connection error.
|
|
235
|
+
*/
|
|
236
|
+
const http2FrameUnpad = (frame: Http2Frame, buf: u8[], fixed: i32): i32 => {
|
|
237
|
+
const padded: boolean = frame.has(H2_FLAG_PADDED)
|
|
238
|
+
const head: i32 = (padded ? H2_FRAME_ONE : H2_FRAME_ZERO) + fixed
|
|
239
|
+
if (frame.length < head) {
|
|
240
|
+
return H2_FRAME_SIZE_ERROR
|
|
241
|
+
}
|
|
242
|
+
frame.padLength = padded ? toI32(buf[frame.payload]) : H2_FRAME_NONE
|
|
243
|
+
const pad: i32 = padded ? frame.padLength : H2_FRAME_ZERO
|
|
244
|
+
if (pad > frame.length - head) {
|
|
245
|
+
return H2_PROTOCOL_ERROR
|
|
246
|
+
}
|
|
247
|
+
frame.contentStart = frame.payload + head
|
|
248
|
+
frame.contentLength = frame.length - head - pad
|
|
249
|
+
return H2_NO_ERROR
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Reads the five bytes of priority fields at `buf[at]` into `frame` (§6.3). A stream may not depend on itself (§5.3.1). */
|
|
253
|
+
const http2FramePriority = (frame: Http2Frame, buf: u8[], at: i32): void => {
|
|
254
|
+
frame.hasPriority = true
|
|
255
|
+
frame.exclusive = (toI32(buf[at]) & 128) !== 0
|
|
256
|
+
frame.dependency = http2FrameU31(buf, at)
|
|
257
|
+
frame.weight = toI32(buf[at + 4]) + 1
|
|
258
|
+
if (frame.dependency === frame.streamId) {
|
|
259
|
+
frame.streamError = H2_PROTOCOL_ERROR
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Parses the frame whose header `http2ReadHeader` read from `buf[off]`; its
|
|
265
|
+
* whole payload must be in `buf`. `maxFrameSize` is the reader's
|
|
266
|
+
* SETTINGS_MAX_FRAME_SIZE. Answers 0, with the type's fields set and any
|
|
267
|
+
* stream error in `streamError`, or the connection error the module comment
|
|
268
|
+
* lists.
|
|
269
|
+
*/
|
|
270
|
+
export const http2ParseFrame = (frame: Http2Frame, buf: u8[], off: i32, maxFrameSize: i32): i32 => {
|
|
271
|
+
http2FrameCheckWindow("http2ParseFrame", buf, off, H2_FRAME_HEADER_SIZE + frame.length)
|
|
272
|
+
frame.payload = off + H2_FRAME_HEADER_SIZE
|
|
273
|
+
frame.contentStart = frame.payload
|
|
274
|
+
frame.contentLength = frame.length
|
|
275
|
+
frame.padLength = H2_FRAME_NONE
|
|
276
|
+
frame.hasPriority = false
|
|
277
|
+
frame.exclusive = false
|
|
278
|
+
frame.dependency = 0
|
|
279
|
+
frame.weight = 0
|
|
280
|
+
frame.streamError = 0
|
|
281
|
+
if (frame.length > maxFrameSize) {
|
|
282
|
+
return H2_FRAME_SIZE_ERROR
|
|
283
|
+
}
|
|
284
|
+
const p: i32 = frame.payload
|
|
285
|
+
const onStream: boolean = frame.streamId !== 0
|
|
286
|
+
switch (frame.type) {
|
|
287
|
+
case H2_FRAME_DATA:
|
|
288
|
+
return onStream ? http2FrameUnpad(frame, buf, H2_FRAME_ZERO) : H2_PROTOCOL_ERROR
|
|
289
|
+
case H2_FRAME_HEADERS: {
|
|
290
|
+
if (!onStream) {
|
|
291
|
+
return H2_PROTOCOL_ERROR
|
|
292
|
+
}
|
|
293
|
+
const priority: boolean = frame.has(H2_FLAG_PRIORITY)
|
|
294
|
+
const error: i32 = http2FrameUnpad(frame, buf, priority ? 5 : 0)
|
|
295
|
+
if (error === H2_NO_ERROR && priority) {
|
|
296
|
+
http2FramePriority(frame, buf, frame.contentStart - 5)
|
|
297
|
+
}
|
|
298
|
+
return error
|
|
299
|
+
}
|
|
300
|
+
case H2_FRAME_PRIORITY:
|
|
301
|
+
if (!onStream) {
|
|
302
|
+
return H2_PROTOCOL_ERROR
|
|
303
|
+
}
|
|
304
|
+
if (frame.length !== 5) {
|
|
305
|
+
frame.streamError = H2_FRAME_SIZE_ERROR
|
|
306
|
+
return H2_NO_ERROR
|
|
307
|
+
}
|
|
308
|
+
http2FramePriority(frame, buf, p)
|
|
309
|
+
return H2_NO_ERROR
|
|
310
|
+
case H2_FRAME_RST_STREAM:
|
|
311
|
+
if (!onStream) {
|
|
312
|
+
return H2_PROTOCOL_ERROR
|
|
313
|
+
}
|
|
314
|
+
if (frame.length !== 4) {
|
|
315
|
+
return H2_FRAME_SIZE_ERROR
|
|
316
|
+
}
|
|
317
|
+
frame.errorCode = http2FrameU32(buf, p)
|
|
318
|
+
return H2_NO_ERROR
|
|
319
|
+
case H2_FRAME_SETTINGS:
|
|
320
|
+
if (onStream) {
|
|
321
|
+
return H2_PROTOCOL_ERROR
|
|
322
|
+
}
|
|
323
|
+
if (frame.has(H2_FLAG_ACK) ? frame.length > 0 : frame.length % H2_SETTING_SIZE !== 0) {
|
|
324
|
+
return H2_FRAME_SIZE_ERROR
|
|
325
|
+
}
|
|
326
|
+
return H2_NO_ERROR
|
|
327
|
+
case H2_FRAME_PUSH_PROMISE: {
|
|
328
|
+
if (!onStream) {
|
|
329
|
+
return H2_PROTOCOL_ERROR
|
|
330
|
+
}
|
|
331
|
+
const error: i32 = http2FrameUnpad(frame, buf, 4)
|
|
332
|
+
if (error === H2_NO_ERROR) {
|
|
333
|
+
frame.promisedId = http2FrameU31(buf, frame.contentStart - 4)
|
|
334
|
+
}
|
|
335
|
+
return error
|
|
336
|
+
}
|
|
337
|
+
case H2_FRAME_PING:
|
|
338
|
+
if (onStream) {
|
|
339
|
+
return H2_PROTOCOL_ERROR
|
|
340
|
+
}
|
|
341
|
+
return frame.length === 8 ? H2_NO_ERROR : H2_FRAME_SIZE_ERROR
|
|
342
|
+
case H2_FRAME_GOAWAY:
|
|
343
|
+
if (onStream) {
|
|
344
|
+
return H2_PROTOCOL_ERROR
|
|
345
|
+
}
|
|
346
|
+
if (frame.length < 8) {
|
|
347
|
+
return H2_FRAME_SIZE_ERROR
|
|
348
|
+
}
|
|
349
|
+
frame.lastStreamId = http2FrameU31(buf, p)
|
|
350
|
+
frame.errorCode = http2FrameU32(buf, p + 4)
|
|
351
|
+
frame.contentStart = p + 8
|
|
352
|
+
frame.contentLength = frame.length - 8
|
|
353
|
+
return H2_NO_ERROR
|
|
354
|
+
case H2_FRAME_WINDOW_UPDATE:
|
|
355
|
+
if (frame.length !== 4) {
|
|
356
|
+
return H2_FRAME_SIZE_ERROR
|
|
357
|
+
}
|
|
358
|
+
frame.increment = http2FrameU31(buf, p)
|
|
359
|
+
if (frame.increment === 0) {
|
|
360
|
+
if (!onStream) {
|
|
361
|
+
return H2_PROTOCOL_ERROR
|
|
362
|
+
}
|
|
363
|
+
frame.streamError = H2_PROTOCOL_ERROR
|
|
364
|
+
}
|
|
365
|
+
return H2_NO_ERROR
|
|
366
|
+
case H2_FRAME_CONTINUATION:
|
|
367
|
+
return onStream ? H2_NO_ERROR : H2_PROTOCOL_ERROR
|
|
368
|
+
default:
|
|
369
|
+
return H2_NO_ERROR
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** How many settings a SETTINGS frame `http2ParseFrame` accepted carries. */
|
|
374
|
+
export const http2SettingCount = (frame: Http2Frame): i32 => frame.length / H2_SETTING_SIZE
|
|
375
|
+
|
|
376
|
+
/** The identifier of setting `k` of the SETTINGS frame in `buf`. */
|
|
377
|
+
export const http2SettingId = (frame: Http2Frame, buf: u8[], k: i32): i32 => {
|
|
378
|
+
const at: i32 = frame.payload + k * H2_SETTING_SIZE
|
|
379
|
+
return (toI32(buf[at]) << 8) | toI32(buf[at + 1])
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** The value of setting `k` of the SETTINGS frame in `buf`, a 32-bit value. */
|
|
383
|
+
export const http2SettingValue = (frame: Http2Frame, buf: u8[], k: i32): i64 =>
|
|
384
|
+
http2FrameU32(buf, frame.payload + k * H2_SETTING_SIZE + 2)
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The connection error a setting's value is, or 0 (§6.5.2, RFC 8441 §3). A
|
|
388
|
+
* setting this module does not know is not an error: §6.5.2 says it is
|
|
389
|
+
* ignored.
|
|
390
|
+
*/
|
|
391
|
+
export const http2SettingError = (id: i32, value: i64): i32 => {
|
|
392
|
+
switch (id) {
|
|
393
|
+
case H2_SETTINGS_ENABLE_PUSH:
|
|
394
|
+
case H2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
|
|
395
|
+
return value > toI64(1) ? H2_PROTOCOL_ERROR : H2_NO_ERROR
|
|
396
|
+
case H2_SETTINGS_INITIAL_WINDOW_SIZE:
|
|
397
|
+
return value > toI64(H2_MAX_WINDOW) ? H2_FLOW_CONTROL_ERROR : H2_NO_ERROR
|
|
398
|
+
case H2_SETTINGS_MAX_FRAME_SIZE:
|
|
399
|
+
return value < toI64(H2_DEFAULT_MAX_FRAME) || value > toI64(H2_MAX_FRAME_LIMIT)
|
|
400
|
+
? H2_PROTOCOL_ERROR
|
|
401
|
+
: H2_NO_ERROR
|
|
402
|
+
default:
|
|
403
|
+
return H2_NO_ERROR
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Writes a frame header at `out[at]` for a payload of `length` bytes, and
|
|
409
|
+
* answers where the payload goes, or -1 when the header and the payload would
|
|
410
|
+
* not both fit.
|
|
411
|
+
*/
|
|
412
|
+
export const http2WriteHeader = (
|
|
413
|
+
out: u8[],
|
|
414
|
+
at: i32,
|
|
415
|
+
length: i32,
|
|
416
|
+
type: i32,
|
|
417
|
+
flags: i32,
|
|
418
|
+
streamId: i32
|
|
419
|
+
): i32 => {
|
|
420
|
+
http2FrameCheckStream("http2WriteHeader", streamId)
|
|
421
|
+
if (at < 0 || length < 0 || length > H2_MAX_FRAME_LIMIT) {
|
|
422
|
+
panic(`http2WriteHeader: a frame of ${length} bytes at ${at}`)
|
|
423
|
+
}
|
|
424
|
+
if (toI64(at) + toI64(H2_FRAME_HEADER_SIZE) + toI64(length) > toI64(out.length)) {
|
|
425
|
+
return -1
|
|
426
|
+
}
|
|
427
|
+
out[at] = toU8((length >> 16) & 255)
|
|
428
|
+
out[at + 1] = toU8((length >> 8) & 255)
|
|
429
|
+
out[at + 2] = toU8(length & 255)
|
|
430
|
+
out[at + 3] = toU8(type)
|
|
431
|
+
out[at + 4] = toU8(flags)
|
|
432
|
+
http2FramePut32(out, at + 5, toI64(streamId))
|
|
433
|
+
return at + H2_FRAME_HEADER_SIZE
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Copies `src[off, off + len)` to `out[at]`; the callers have checked both windows. */
|
|
437
|
+
const http2FrameCopy = (out: u8[], at: i32, src: u8[], off: i32, len: i32): void => {
|
|
438
|
+
for (let k: i32 = 0; k < len; k++) {
|
|
439
|
+
out[at + k] = src[off + k]
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The shared shape of DATA, HEADERS, PUSH_PROMISE and CONTINUATION: the
|
|
445
|
+
* header, the Pad Length when `pad` is 0 to 255 (-1 for none), `fixed` bytes
|
|
446
|
+
* the caller fills at the answer's offset minus `fixed`, the content and
|
|
447
|
+
* the padding. Answers the offset after the frame, or -1.
|
|
448
|
+
*/
|
|
449
|
+
const http2FrameWritePadded = (
|
|
450
|
+
out: u8[],
|
|
451
|
+
at: i32,
|
|
452
|
+
type: i32,
|
|
453
|
+
flags: i32,
|
|
454
|
+
streamId: i32,
|
|
455
|
+
fixed: i32,
|
|
456
|
+
src: u8[],
|
|
457
|
+
off: i32,
|
|
458
|
+
len: i32,
|
|
459
|
+
pad: i32
|
|
460
|
+
): i32 => {
|
|
461
|
+
http2FrameCheckWindow("http2Write", src, off, len)
|
|
462
|
+
if (pad > 255) {
|
|
463
|
+
panic(`http2Write: ${pad} bytes of padding, past 255`)
|
|
464
|
+
}
|
|
465
|
+
const padded: boolean = pad >= 0
|
|
466
|
+
const head: i32 = (padded ? H2_FRAME_ONE : H2_FRAME_ZERO) + fixed
|
|
467
|
+
const tail: i32 = padded ? pad : H2_FRAME_ZERO
|
|
468
|
+
const at2: i32 = http2WriteHeader(
|
|
469
|
+
out,
|
|
470
|
+
at,
|
|
471
|
+
head + len + tail,
|
|
472
|
+
type,
|
|
473
|
+
padded ? flags | H2_FLAG_PADDED : flags & ~H2_FLAG_PADDED,
|
|
474
|
+
streamId
|
|
475
|
+
)
|
|
476
|
+
if (at2 < 0) {
|
|
477
|
+
return -1
|
|
478
|
+
}
|
|
479
|
+
if (padded) {
|
|
480
|
+
out[at2] = toU8(pad)
|
|
481
|
+
}
|
|
482
|
+
const body: i32 = at2 + head
|
|
483
|
+
http2FrameCopy(out, body, src, off, len)
|
|
484
|
+
out.fill(0, body + len, body + len + tail)
|
|
485
|
+
return body + len + tail
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Writes a DATA frame of `src[off, off + len)` on `streamId`, with `flags` (END_STREAM) and `pad` bytes of padding, -1 for none (§6.1). */
|
|
489
|
+
export const http2WriteData = (
|
|
490
|
+
out: u8[],
|
|
491
|
+
at: i32,
|
|
492
|
+
streamId: i32,
|
|
493
|
+
src: u8[],
|
|
494
|
+
off: i32,
|
|
495
|
+
len: i32,
|
|
496
|
+
flags: i32,
|
|
497
|
+
pad: i32
|
|
498
|
+
): i32 => {
|
|
499
|
+
if (streamId === 0) {
|
|
500
|
+
panic("http2WriteData: DATA on stream 0")
|
|
501
|
+
}
|
|
502
|
+
return http2FrameWritePadded(
|
|
503
|
+
out,
|
|
504
|
+
at,
|
|
505
|
+
H2_FRAME_DATA,
|
|
506
|
+
flags & H2_FLAG_END_STREAM,
|
|
507
|
+
streamId,
|
|
508
|
+
H2_FRAME_ZERO,
|
|
509
|
+
src,
|
|
510
|
+
off,
|
|
511
|
+
len,
|
|
512
|
+
pad
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Writes a HEADERS frame carrying the header block fragment `src[off, off +
|
|
518
|
+
* len)` on `streamId`, with `flags` (END_STREAM, END_HEADERS) and `pad`
|
|
519
|
+
* bytes of padding, -1 for none (§6.2). It never sets PRIORITY, which RFC
|
|
520
|
+
* 9113 deprecates.
|
|
521
|
+
*/
|
|
522
|
+
export const http2WriteHeaders = (
|
|
523
|
+
out: u8[],
|
|
524
|
+
at: i32,
|
|
525
|
+
streamId: i32,
|
|
526
|
+
src: u8[],
|
|
527
|
+
off: i32,
|
|
528
|
+
len: i32,
|
|
529
|
+
flags: i32,
|
|
530
|
+
pad: i32
|
|
531
|
+
): i32 => {
|
|
532
|
+
if (streamId === 0) {
|
|
533
|
+
panic("http2WriteHeaders: HEADERS on stream 0")
|
|
534
|
+
}
|
|
535
|
+
const allowed: i32 = H2_FLAG_END_STREAM | H2_FLAG_END_HEADERS
|
|
536
|
+
return http2FrameWritePadded(
|
|
537
|
+
out,
|
|
538
|
+
at,
|
|
539
|
+
H2_FRAME_HEADERS,
|
|
540
|
+
flags & allowed,
|
|
541
|
+
streamId,
|
|
542
|
+
H2_FRAME_ZERO,
|
|
543
|
+
src,
|
|
544
|
+
off,
|
|
545
|
+
len,
|
|
546
|
+
pad
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Writes a PRIORITY frame: `streamId` depends on `dependency`, exclusively or not, with `weight` 1 to 256 (§6.3). */
|
|
551
|
+
export const http2WritePriority = (
|
|
552
|
+
out: u8[],
|
|
553
|
+
at: i32,
|
|
554
|
+
streamId: i32,
|
|
555
|
+
dependency: i32,
|
|
556
|
+
exclusive: boolean,
|
|
557
|
+
weight: i32
|
|
558
|
+
): i32 => {
|
|
559
|
+
http2FrameCheckStream("http2WritePriority", dependency)
|
|
560
|
+
if (streamId === 0 || streamId === dependency || weight < 1 || weight > 256) {
|
|
561
|
+
panic(`http2WritePriority: stream ${streamId} on ${dependency} with weight ${weight}`)
|
|
562
|
+
}
|
|
563
|
+
const body: i32 = http2WriteHeader(out, at, 5, H2_FRAME_PRIORITY, H2_FRAME_ZERO, streamId)
|
|
564
|
+
if (body < 0) {
|
|
565
|
+
return -1
|
|
566
|
+
}
|
|
567
|
+
http2FramePut32(out, body, toI64(dependency) | (exclusive ? H2_FRAME_EXCLUSIVE : toI64(0)))
|
|
568
|
+
out[body + 4] = toU8(weight - 1)
|
|
569
|
+
return body + 5
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Writes a RST_STREAM frame closing `streamId` with `code` (§6.4). */
|
|
573
|
+
export const http2WriteRstStream = (out: u8[], at: i32, streamId: i32, code: i64): i32 => {
|
|
574
|
+
if (streamId === 0) {
|
|
575
|
+
panic("http2WriteRstStream: RST_STREAM on stream 0")
|
|
576
|
+
}
|
|
577
|
+
const body: i32 = http2WriteHeader(out, at, 4, H2_FRAME_RST_STREAM, H2_FRAME_ZERO, streamId)
|
|
578
|
+
if (body < 0) {
|
|
579
|
+
return -1
|
|
580
|
+
}
|
|
581
|
+
http2FramePut32(out, body, code)
|
|
582
|
+
return body + 4
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Writes a SETTINGS frame of the settings `ids[k]` = `values[k]` (§6.5).
|
|
587
|
+
* A value `http2SettingError` refuses panics, as two lists of different
|
|
588
|
+
* lengths do.
|
|
589
|
+
*/
|
|
590
|
+
export const http2WriteSettings = (out: u8[], at: i32, ids: i32[], values: i64[]): i32 => {
|
|
591
|
+
const n: i32 = toI32(ids.length)
|
|
592
|
+
if (n !== toI32(values.length)) {
|
|
593
|
+
panic(`http2WriteSettings: ${n} identifiers for ${values.length} values`)
|
|
594
|
+
}
|
|
595
|
+
for (let k: i32 = 0; k < n && k < toI32(ids.length) && k < toI32(values.length); k++) {
|
|
596
|
+
if (
|
|
597
|
+
ids[k] < 0 ||
|
|
598
|
+
ids[k] > 65535 ||
|
|
599
|
+
values[k] < toI64(0) ||
|
|
600
|
+
values[k] > H2_FRAME_U32_MAX ||
|
|
601
|
+
http2SettingError(ids[k], values[k]) !== H2_NO_ERROR
|
|
602
|
+
) {
|
|
603
|
+
panic(`http2WriteSettings: setting ${ids[k]} = ${values[k]}`)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const body: i32 = http2WriteHeader(
|
|
607
|
+
out,
|
|
608
|
+
at,
|
|
609
|
+
n * H2_SETTING_SIZE,
|
|
610
|
+
H2_FRAME_SETTINGS,
|
|
611
|
+
H2_FRAME_ZERO,
|
|
612
|
+
H2_FRAME_ZERO
|
|
613
|
+
)
|
|
614
|
+
if (body < 0) {
|
|
615
|
+
return -1
|
|
616
|
+
}
|
|
617
|
+
for (let k: i32 = 0; k < n && k < toI32(ids.length) && k < toI32(values.length); k++) {
|
|
618
|
+
const at2: i32 = body + k * H2_SETTING_SIZE
|
|
619
|
+
if (at2 >= 0 && at2 < toI32(out.length) && at2 + 1 < toI32(out.length)) {
|
|
620
|
+
out[at2] = toU8((ids[k] >> 8) & 255)
|
|
621
|
+
out[at2 + 1] = toU8(ids[k] & 255)
|
|
622
|
+
}
|
|
623
|
+
http2FramePut32(out, at2 + 2, values[k])
|
|
624
|
+
}
|
|
625
|
+
return body + n * H2_SETTING_SIZE
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Writes the empty SETTINGS frame with ACK that acknowledges the peer's (§6.5.3). */
|
|
629
|
+
export const http2WriteSettingsAck = (out: u8[], at: i32): i32 =>
|
|
630
|
+
http2WriteHeader(out, at, H2_FRAME_ZERO, H2_FRAME_SETTINGS, H2_FLAG_ACK, H2_FRAME_ZERO)
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Writes a PUSH_PROMISE frame on `streamId` reserving `promisedId`, carrying
|
|
634
|
+
* the header block fragment `src[off, off + len)`, with `flags`
|
|
635
|
+
* (END_HEADERS) and `pad` bytes of padding, -1 for none (§6.6).
|
|
636
|
+
*/
|
|
637
|
+
export const http2WritePushPromise = (
|
|
638
|
+
out: u8[],
|
|
639
|
+
at: i32,
|
|
640
|
+
streamId: i32,
|
|
641
|
+
promisedId: i32,
|
|
642
|
+
src: u8[],
|
|
643
|
+
off: i32,
|
|
644
|
+
len: i32,
|
|
645
|
+
flags: i32,
|
|
646
|
+
pad: i32
|
|
647
|
+
): i32 => {
|
|
648
|
+
http2FrameCheckStream("http2WritePushPromise", promisedId)
|
|
649
|
+
if (streamId === 0 || promisedId === 0) {
|
|
650
|
+
panic(`http2WritePushPromise: stream ${streamId} promising ${promisedId}`)
|
|
651
|
+
}
|
|
652
|
+
const end: i32 = http2FrameWritePadded(
|
|
653
|
+
out,
|
|
654
|
+
at,
|
|
655
|
+
H2_FRAME_PUSH_PROMISE,
|
|
656
|
+
flags & H2_FLAG_END_HEADERS,
|
|
657
|
+
streamId,
|
|
658
|
+
4,
|
|
659
|
+
src,
|
|
660
|
+
off,
|
|
661
|
+
len,
|
|
662
|
+
pad
|
|
663
|
+
)
|
|
664
|
+
if (end >= 0) {
|
|
665
|
+
http2FramePut32(
|
|
666
|
+
out,
|
|
667
|
+
at + H2_FRAME_HEADER_SIZE + (pad >= 0 ? H2_FRAME_ONE : H2_FRAME_ZERO),
|
|
668
|
+
toI64(promisedId)
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
return end
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Writes a PING frame of the eight bytes `opaque[off, off + 8)`, an ACK when `ack` (§6.7). */
|
|
675
|
+
export const http2WritePing = (out: u8[], at: i32, opaque: u8[], off: i32, ack: boolean): i32 => {
|
|
676
|
+
http2FrameCheckWindow("http2WritePing", opaque, off, 8)
|
|
677
|
+
const body: i32 = http2WriteHeader(out, at, 8, H2_FRAME_PING, ack ? H2_FLAG_ACK : 0, H2_FRAME_ZERO)
|
|
678
|
+
if (body < 0) {
|
|
679
|
+
return -1
|
|
680
|
+
}
|
|
681
|
+
http2FrameCopy(out, body, opaque, off, 8)
|
|
682
|
+
return body + 8
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Writes a GOAWAY frame naming `lastStreamId` and `code`, with the debug data `debug[off, off + len)` (§6.8). */
|
|
686
|
+
export const http2WriteGoaway = (
|
|
687
|
+
out: u8[],
|
|
688
|
+
at: i32,
|
|
689
|
+
lastStreamId: i32,
|
|
690
|
+
code: i64,
|
|
691
|
+
debug: u8[],
|
|
692
|
+
off: i32,
|
|
693
|
+
len: i32
|
|
694
|
+
): i32 => {
|
|
695
|
+
http2FrameCheckStream("http2WriteGoaway", lastStreamId)
|
|
696
|
+
http2FrameCheckWindow("http2WriteGoaway", debug, off, len)
|
|
697
|
+
const body: i32 = http2WriteHeader(out, at, 8 + len, H2_FRAME_GOAWAY, H2_FRAME_ZERO, H2_FRAME_ZERO)
|
|
698
|
+
if (body < 0) {
|
|
699
|
+
return -1
|
|
700
|
+
}
|
|
701
|
+
http2FramePut32(out, body, toI64(lastStreamId))
|
|
702
|
+
http2FramePut32(out, body + 4, code)
|
|
703
|
+
http2FrameCopy(out, body + 8, debug, off, len)
|
|
704
|
+
return body + 8 + len
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Writes a WINDOW_UPDATE frame adding `increment`, 1 to 2^31 − 1, to `streamId`'s window, or the connection's for 0 (§6.9). */
|
|
708
|
+
export const http2WriteWindowUpdate = (out: u8[], at: i32, streamId: i32, increment: i32): i32 => {
|
|
709
|
+
if (increment < 1) {
|
|
710
|
+
panic(`http2WriteWindowUpdate: an increment of ${increment}`)
|
|
711
|
+
}
|
|
712
|
+
const body: i32 = http2WriteHeader(out, at, 4, H2_FRAME_WINDOW_UPDATE, H2_FRAME_ZERO, streamId)
|
|
713
|
+
if (body < 0) {
|
|
714
|
+
return -1
|
|
715
|
+
}
|
|
716
|
+
http2FramePut32(out, body, toI64(increment))
|
|
717
|
+
return body + 4
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Writes a CONTINUATION frame of the header block fragment `src[off, off + len)`, with `flags` (END_HEADERS) (§6.10). */
|
|
721
|
+
export const http2WriteContinuation = (
|
|
722
|
+
out: u8[],
|
|
723
|
+
at: i32,
|
|
724
|
+
streamId: i32,
|
|
725
|
+
src: u8[],
|
|
726
|
+
off: i32,
|
|
727
|
+
len: i32,
|
|
728
|
+
flags: i32
|
|
729
|
+
): i32 => {
|
|
730
|
+
if (streamId === 0) {
|
|
731
|
+
panic("http2WriteContinuation: CONTINUATION on stream 0")
|
|
732
|
+
}
|
|
733
|
+
return http2FrameWritePadded(
|
|
734
|
+
out,
|
|
735
|
+
at,
|
|
736
|
+
H2_FRAME_CONTINUATION,
|
|
737
|
+
flags & H2_FLAG_END_HEADERS,
|
|
738
|
+
streamId,
|
|
739
|
+
H2_FRAME_ZERO,
|
|
740
|
+
src,
|
|
741
|
+
off,
|
|
742
|
+
len,
|
|
743
|
+
H2_FRAME_NONE
|
|
744
|
+
)
|
|
745
|
+
}
|