raptor 0.1.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.
@@ -0,0 +1,772 @@
1
+ #include "ruby.h"
2
+ #include <string.h>
3
+ #include <stdint.h>
4
+
5
+ #define HTTP2_FRAME_HEADER_SIZE 9
6
+ #define HTTP2_DEFAULT_HEADER_TABLE_SIZE 4096
7
+ #define HTTP2_ENTRY_OVERHEAD 32
8
+ #define HTTP2_CONNECTION_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
9
+ #define HTTP2_CONNECTION_PREFACE_LEN 24
10
+
11
+ #define FRAME_DATA 0x0
12
+ #define FRAME_HEADERS 0x1
13
+ #define FRAME_PRIORITY 0x2
14
+ #define FRAME_RST_STREAM 0x3
15
+ #define FRAME_SETTINGS 0x4
16
+ #define FRAME_PUSH_PROMISE 0x5
17
+ #define FRAME_PING 0x6
18
+ #define FRAME_GOAWAY 0x7
19
+ #define FRAME_WINDOW_UPDATE 0x8
20
+ #define FRAME_CONTINUATION 0x9
21
+
22
+ #define FLAG_END_STREAM 0x1
23
+ #define FLAG_END_HEADERS 0x4
24
+ #define FLAG_PADDED 0x8
25
+ #define FLAG_PRIORITY 0x20
26
+ #define FLAG_ACK 0x1
27
+
28
+ #define SETTINGS_HEADER_TABLE_SIZE 0x1
29
+ #define SETTINGS_ENABLE_PUSH 0x2
30
+ #define SETTINGS_MAX_CONCURRENT_STREAMS 0x3
31
+ #define SETTINGS_INITIAL_WINDOW_SIZE 0x4
32
+ #define SETTINGS_MAX_FRAME_SIZE 0x5
33
+ #define SETTINGS_MAX_HEADER_LIST_SIZE 0x6
34
+
35
+ #define STATIC_TABLE_SIZE 61
36
+
37
+ #define HUFFMAN_ACCEPTED 1
38
+ #define HUFFMAN_SYM (1 << 1)
39
+ #define HUFFMAN_FAIL_STATE 0x100
40
+
41
+ static VALUE cHttp2Parser;
42
+ static VALUE eHttp2ParserError;
43
+
44
+ typedef struct {
45
+ const char *name;
46
+ const char *value;
47
+ } hpack_static_entry;
48
+
49
+ static const hpack_static_entry STATIC_TABLE[] = {
50
+ {NULL, NULL},
51
+ {":authority", ""},
52
+ {":method", "GET"},
53
+ {":method", "POST"},
54
+ {":path", "/"},
55
+ {":path", "/index.html"},
56
+ {":scheme", "http"},
57
+ {":scheme", "https"},
58
+ {":status", "200"},
59
+ {":status", "204"},
60
+ {":status", "206"},
61
+ {":status", "304"},
62
+ {":status", "400"},
63
+ {":status", "404"},
64
+ {":status", "500"},
65
+ {"accept-charset", ""},
66
+ {"accept-encoding", "gzip, deflate"},
67
+ {"accept-language", ""},
68
+ {"accept-ranges", ""},
69
+ {"accept", ""},
70
+ {"access-control-allow-origin", ""},
71
+ {"age", ""},
72
+ {"allow", ""},
73
+ {"authorization", ""},
74
+ {"cache-control", ""},
75
+ {"content-disposition", ""},
76
+ {"content-encoding", ""},
77
+ {"content-language", ""},
78
+ {"content-length", ""},
79
+ {"content-location", ""},
80
+ {"content-range", ""},
81
+ {"content-type", ""},
82
+ {"cookie", ""},
83
+ {"date", ""},
84
+ {"etag", ""},
85
+ {"expect", ""},
86
+ {"expires", ""},
87
+ {"from", ""},
88
+ {"host", ""},
89
+ {"if-match", ""},
90
+ {"if-modified-since", ""},
91
+ {"if-none-match", ""},
92
+ {"if-range", ""},
93
+ {"if-unmodified-since", ""},
94
+ {"last-modified", ""},
95
+ {"link", ""},
96
+ {"location", ""},
97
+ {"max-forwards", ""},
98
+ {"proxy-authenticate", ""},
99
+ {"proxy-authorization", ""},
100
+ {"range", ""},
101
+ {"referer", ""},
102
+ {"refresh", ""},
103
+ {"retry-after", ""},
104
+ {"server", ""},
105
+ {"set-cookie", ""},
106
+ {"strict-transport-security", ""},
107
+ {"transfer-encoding", ""},
108
+ {"user-agent", ""},
109
+ {"vary", ""},
110
+ {"via", ""},
111
+ {"www-authenticate", ""}
112
+ };
113
+
114
+ typedef struct {
115
+ uint32_t code;
116
+ uint8_t bits;
117
+ } huffman_entry;
118
+
119
+ static const huffman_entry HUFFMAN_TABLE[257] = {
120
+ {0x1ff8, 13}, {0x7fffd8, 23}, {0xfffffe2, 28}, {0xfffffe3, 28},
121
+ {0xfffffe4, 28}, {0xfffffe5, 28}, {0xfffffe6, 28}, {0xfffffe7, 28},
122
+ {0xfffffe8, 28}, {0xffffea, 24}, {0x3ffffffc, 30}, {0xfffffe9, 28},
123
+ {0xfffffea, 28}, {0x3ffffffd, 30}, {0xfffffeb, 28}, {0xfffffec, 28},
124
+ {0xfffffed, 28}, {0xfffffee, 28}, {0xfffffef, 28}, {0xffffff0, 28},
125
+ {0xffffff1, 28}, {0xffffff2, 28}, {0x3ffffffe, 30}, {0xffffff3, 28},
126
+ {0xffffff4, 28}, {0xffffff5, 28}, {0xffffff6, 28}, {0xffffff7, 28},
127
+ {0xffffff8, 28}, {0xffffff9, 28}, {0xffffffa, 28}, {0xffffffb, 28},
128
+ {0x14, 6}, {0x3f8, 10}, {0x3f9, 10}, {0xffa, 12},
129
+ {0x1ff9, 13}, {0x15, 6}, {0xf8, 8}, {0x7fa, 11},
130
+ {0x3fa, 10}, {0x3fb, 10}, {0xf9, 8}, {0x7fb, 11},
131
+ {0xfa, 8}, {0x16, 6}, {0x17, 6}, {0x18, 6},
132
+ {0x0, 5}, {0x1, 5}, {0x2, 5}, {0x19, 6},
133
+ {0x1a, 6}, {0x1b, 6}, {0x1c, 6}, {0x1d, 6},
134
+ {0x1e, 6}, {0x1f, 6}, {0x5c, 7}, {0xfb, 8},
135
+ {0x7ffc, 15}, {0x20, 6}, {0xffb, 12}, {0x3fc, 10},
136
+ {0x1ffa, 13}, {0x21, 6}, {0x5d, 7}, {0x5e, 7},
137
+ {0x5f, 7}, {0x60, 7}, {0x61, 7}, {0x62, 7},
138
+ {0x63, 7}, {0x64, 7}, {0x65, 7}, {0x66, 7},
139
+ {0x67, 7}, {0x68, 7}, {0x69, 7}, {0x6a, 7},
140
+ {0x6b, 7}, {0x6c, 7}, {0x6d, 7}, {0x6e, 7},
141
+ {0x6f, 7}, {0x70, 7}, {0x71, 7}, {0x72, 7},
142
+ {0xfc, 8}, {0x73, 7}, {0xfd, 8}, {0x1ffb, 13},
143
+ {0x7fff0, 19}, {0x1ffc, 13}, {0x3ffc, 14}, {0x22, 6},
144
+ {0x7ffd, 15}, {0x3, 5}, {0x23, 6}, {0x4, 5},
145
+ {0x24, 6}, {0x5, 5}, {0x25, 6}, {0x26, 6},
146
+ {0x27, 6}, {0x6, 5}, {0x74, 7}, {0x75, 7},
147
+ {0x28, 6}, {0x29, 6}, {0x2a, 6}, {0x7, 5},
148
+ {0x2b, 6}, {0x76, 7}, {0x2c, 6}, {0x8, 5},
149
+ {0x9, 5}, {0x2d, 6}, {0x77, 7}, {0x78, 7},
150
+ {0x79, 7}, {0x7a, 7}, {0x7b, 7}, {0x7fffe, 19},
151
+ {0x7fc, 11}, {0x3ffd, 14}, {0x1ffd, 13}, {0xffffffc, 28},
152
+ {0xfffe6, 20}, {0x3fffd2, 22}, {0xfffe7, 20}, {0xfffe8, 20},
153
+ {0x3fffd3, 22}, {0x3fffd4, 22}, {0x3fffd5, 22}, {0x7fffd9, 23},
154
+ {0x3fffd6, 22}, {0x7fffda, 23}, {0x7fffdb, 23}, {0x7fffdc, 23},
155
+ {0x7fffdd, 23}, {0x7fffde, 23}, {0xffffeb, 24}, {0x7fffdf, 23},
156
+ {0xffffec, 24}, {0xffffed, 24}, {0x3fffd7, 22}, {0x7fffe0, 23},
157
+ {0xffffee, 24}, {0x7fffe1, 23}, {0x7fffe2, 23}, {0x7fffe3, 23},
158
+ {0x7fffe4, 23}, {0x1fffdc, 21}, {0x3fffd8, 22}, {0x7fffe5, 23},
159
+ {0x3fffd9, 22}, {0x7fffe6, 23}, {0x7fffe7, 23}, {0xffffef, 24},
160
+ {0x3fffda, 22}, {0x1fffdd, 21}, {0xfffe9, 20}, {0x3fffdb, 22},
161
+ {0x3fffdc, 22}, {0x7fffe8, 23}, {0x7fffe9, 23}, {0x1fffde, 21},
162
+ {0x7fffea, 23}, {0x3fffdd, 22}, {0x3fffde, 22}, {0xfffff0, 24},
163
+ {0x1fffdf, 21}, {0x3fffdf, 22}, {0x7fffeb, 23}, {0x7fffec, 23},
164
+ {0x1fffe0, 21}, {0x1fffe1, 21}, {0x3fffe0, 22}, {0x1fffe2, 21},
165
+ {0x7fffed, 23}, {0x3fffe1, 22}, {0x7fffee, 23}, {0x7fffef, 23},
166
+ {0xfffea, 20}, {0x3fffe2, 22}, {0x3fffe3, 22}, {0x3fffe4, 22},
167
+ {0x7ffff0, 23}, {0x3fffe5, 22}, {0x3fffe6, 22}, {0x7ffff1, 23},
168
+ {0x3ffffe0, 26}, {0x3ffffe1, 26}, {0xfffeb, 20}, {0x7fff1, 19},
169
+ {0x3fffe7, 22}, {0x7ffff2, 23}, {0x3fffe8, 22}, {0x1ffffec, 25},
170
+ {0x3ffffe2, 26}, {0x3ffffe3, 26}, {0x3ffffe4, 26}, {0x7ffffde, 27},
171
+ {0x7ffffdf, 27}, {0x3ffffe5, 26}, {0xfffff1, 24}, {0x1ffffed, 25},
172
+ {0x7fff2, 19}, {0x1fffe3, 21}, {0x3ffffe6, 26}, {0x7ffffe0, 27},
173
+ {0x7ffffe1, 27}, {0x3ffffe7, 26}, {0x7ffffe2, 27}, {0xfffff2, 24},
174
+ {0x1fffe4, 21}, {0x1fffe5, 21}, {0x3ffffe8, 26}, {0x3ffffe9, 26},
175
+ {0xffffffd, 28}, {0x7ffffe3, 27}, {0x7ffffe4, 27}, {0x7ffffe5, 27},
176
+ {0xfffec, 20}, {0xfffff3, 24}, {0xfffed, 20}, {0x1fffe6, 21},
177
+ {0x3fffe9, 22}, {0x1fffe7, 21}, {0x1fffe8, 21}, {0x7ffff3, 23},
178
+ {0x3fffea, 22}, {0x3fffeb, 22}, {0x1ffffee, 25}, {0x1ffffef, 25},
179
+ {0xfffff4, 24}, {0xfffff5, 24}, {0x3ffffea, 26}, {0x7ffff4, 23},
180
+ {0x3ffffeb, 26}, {0x7ffffe6, 27}, {0x3ffffec, 26}, {0x3ffffed, 26},
181
+ {0x7ffffe7, 27}, {0x7ffffe8, 27}, {0x7ffffe9, 27}, {0x7ffffea, 27},
182
+ {0x7ffffeb, 27}, {0xffffffe, 28}, {0x7ffffec, 27}, {0x7ffffed, 27},
183
+ {0x7ffffee, 27}, {0x7ffffef, 27}, {0x7fffff0, 27}, {0x3ffffee, 26},
184
+ {0x3fffffff, 30}
185
+ };
186
+
187
+ typedef struct {
188
+ uint16_t state;
189
+ uint8_t flags;
190
+ uint8_t symbol;
191
+ } huffman_decode_entry;
192
+
193
+ #include "huffman_table.h"
194
+
195
+ typedef struct {
196
+ uint32_t length;
197
+ uint8_t type;
198
+ uint8_t flags;
199
+ uint32_t stream_id;
200
+ } frame_header;
201
+
202
+ typedef struct {
203
+ long max_table_size;
204
+ } raptor_h2_parser;
205
+
206
+ static int hpack_decode_int(const uint8_t *buf, size_t len, size_t *pos, uint8_t prefix_bits, uint64_t *result) {
207
+ if (*pos >= len) return -1;
208
+
209
+ uint8_t prefix_mask = (1 << prefix_bits) - 1;
210
+ *result = buf[*pos] & prefix_mask;
211
+ (*pos)++;
212
+
213
+ if (*result < prefix_mask) return 0;
214
+
215
+ uint64_t m = 0;
216
+ do {
217
+ if (*pos >= len) return -1;
218
+
219
+ uint8_t byte = buf[*pos];
220
+ (*pos)++;
221
+ *result += (uint64_t)(byte & 0x7f) << m;
222
+ m += 7;
223
+
224
+ if (m > 63) return -1;
225
+ if (!(byte & 0x80)) return 0;
226
+ } while (1);
227
+ }
228
+
229
+ static size_t hpack_encode_int(uint8_t *buf, uint64_t value, uint8_t prefix_bits, uint8_t prefix_byte) {
230
+ uint8_t prefix_mask = (1 << prefix_bits) - 1;
231
+ size_t pos = 0;
232
+
233
+ if (value < prefix_mask) {
234
+ buf[pos++] = prefix_byte | (uint8_t)value;
235
+ return pos;
236
+ }
237
+
238
+ buf[pos++] = prefix_byte | prefix_mask;
239
+ value -= prefix_mask;
240
+
241
+ while (value >= 128) {
242
+ buf[pos++] = (uint8_t)(value & 0x7f) | 0x80;
243
+ value >>= 7;
244
+ }
245
+ buf[pos++] = (uint8_t)value;
246
+ return pos;
247
+ }
248
+
249
+ static int hpack_decode_huffman(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_cap, size_t *dst_len) {
250
+ uint16_t state = 0;
251
+ int accepted = 1;
252
+ *dst_len = 0;
253
+
254
+ for (size_t i = 0; i < src_len; i++) {
255
+ uint8_t byte = src[i];
256
+ const huffman_decode_entry *entry;
257
+
258
+ entry = &huffman_decode_table[state][byte >> 4];
259
+ if (entry->state == HUFFMAN_FAIL_STATE) return -1;
260
+ if (entry->flags & HUFFMAN_SYM) {
261
+ if (*dst_len >= dst_cap) return -1;
262
+ dst[(*dst_len)++] = entry->symbol;
263
+ }
264
+ state = entry->state;
265
+
266
+ entry = &huffman_decode_table[state][byte & 0x0f];
267
+ if (entry->state == HUFFMAN_FAIL_STATE) return -1;
268
+ if (entry->flags & HUFFMAN_SYM) {
269
+ if (*dst_len >= dst_cap) return -1;
270
+ dst[(*dst_len)++] = entry->symbol;
271
+ }
272
+ state = entry->state;
273
+ accepted = (entry->flags & HUFFMAN_ACCEPTED) != 0;
274
+ }
275
+
276
+ return accepted ? 0 : -1;
277
+ }
278
+
279
+ static int hpack_decode_string(const uint8_t *buf, size_t len, size_t *pos, VALUE *out_str) {
280
+ if (*pos >= len) return -1;
281
+
282
+ int huffman = (buf[*pos] & 0x80) != 0;
283
+ uint64_t str_len;
284
+ if (hpack_decode_int(buf, len, pos, 7, &str_len) < 0) return -1;
285
+ if (*pos + str_len > len) return -1;
286
+
287
+ if (huffman) {
288
+ size_t decoded_cap = str_len * 2 + 256;
289
+ uint8_t *decoded = ALLOCA_N(uint8_t, decoded_cap);
290
+ size_t decoded_len;
291
+
292
+ if (hpack_decode_huffman(buf + *pos, (size_t)str_len, decoded, decoded_cap, &decoded_len) < 0) {
293
+ rb_raise(rb_eRuntimeError, "HPACK Huffman decode error");
294
+ return -1;
295
+ }
296
+
297
+ *out_str = rb_str_new((const char *)decoded, decoded_len);
298
+ } else {
299
+ *out_str = rb_str_new((const char *)(buf + *pos), (long)str_len);
300
+ }
301
+
302
+ *pos += (size_t)str_len;
303
+ return 0;
304
+ }
305
+
306
+ static size_t hpack_huffman_encode_len(const uint8_t *src, size_t src_len) {
307
+ size_t bits = 0;
308
+ for (size_t i = 0; i < src_len; i++)
309
+ bits += HUFFMAN_TABLE[src[i]].bits;
310
+ return (bits + 7) / 8;
311
+ }
312
+
313
+ static size_t hpack_huffman_encode(const uint8_t *src, size_t src_len, uint8_t *dst) {
314
+ size_t pos = 0;
315
+ uint64_t current = 0;
316
+ int remaining = 0;
317
+
318
+ for (size_t i = 0; i < src_len; i++) {
319
+ uint32_t code = HUFFMAN_TABLE[src[i]].code;
320
+ uint8_t bits = HUFFMAN_TABLE[src[i]].bits;
321
+
322
+ current = (current << bits) | code;
323
+ remaining += bits;
324
+
325
+ while (remaining >= 8) {
326
+ remaining -= 8;
327
+ dst[pos++] = (uint8_t)(current >> remaining);
328
+ }
329
+ }
330
+
331
+ if (remaining > 0) {
332
+ current = (current << (8 - remaining)) | ((1 << (8 - remaining)) - 1);
333
+ dst[pos++] = (uint8_t)current;
334
+ }
335
+
336
+ return pos;
337
+ }
338
+
339
+ static VALUE dynamic_table_lookup(VALUE dyn_table, long index, VALUE *name, VALUE *value) {
340
+ if (index < 1) return Qfalse;
341
+
342
+ if (index <= STATIC_TABLE_SIZE) {
343
+ *name = rb_str_new_cstr(STATIC_TABLE[index].name);
344
+ *value = rb_str_new_cstr(STATIC_TABLE[index].value);
345
+ return Qtrue;
346
+ }
347
+
348
+ long dyn_index = index - STATIC_TABLE_SIZE - 1;
349
+ if (dyn_index >= RARRAY_LEN(dyn_table)) return Qfalse;
350
+
351
+ VALUE entry = rb_ary_entry(dyn_table, dyn_index);
352
+ *name = rb_ary_entry(entry, 0);
353
+ *value = rb_ary_entry(entry, 1);
354
+ return Qtrue;
355
+ }
356
+
357
+ static VALUE dynamic_table_add(VALUE dyn_table, VALUE name, VALUE value, long max_size) {
358
+ long entry_size = RSTRING_LEN(name) + RSTRING_LEN(value) + HTTP2_ENTRY_OVERHEAD;
359
+
360
+ VALUE new_table = rb_ary_new();
361
+ VALUE new_entry = rb_ary_new_from_args(2, rb_str_freeze(rb_str_dup(name)), rb_str_freeze(rb_str_dup(value)));
362
+ rb_ary_push(new_table, rb_ary_freeze(new_entry));
363
+
364
+ long current_size = entry_size;
365
+ for (long i = 0; i < RARRAY_LEN(dyn_table); i++) {
366
+ VALUE existing = rb_ary_entry(dyn_table, i);
367
+ VALUE ename = rb_ary_entry(existing, 0);
368
+ VALUE evalue = rb_ary_entry(existing, 1);
369
+ long esize = RSTRING_LEN(ename) + RSTRING_LEN(evalue) + HTTP2_ENTRY_OVERHEAD;
370
+
371
+ if (current_size + esize > max_size) break;
372
+
373
+ rb_ary_push(new_table, existing);
374
+ current_size += esize;
375
+ }
376
+
377
+ return new_table;
378
+ }
379
+
380
+ static VALUE dynamic_table_evict(VALUE dyn_table, long max_size) {
381
+ long current_size = 0;
382
+ long keep = 0;
383
+
384
+ for (long i = 0; i < RARRAY_LEN(dyn_table); i++) {
385
+ VALUE entry = rb_ary_entry(dyn_table, i);
386
+ VALUE name = rb_ary_entry(entry, 0);
387
+ VALUE value = rb_ary_entry(entry, 1);
388
+ long esize = RSTRING_LEN(name) + RSTRING_LEN(value) + HTTP2_ENTRY_OVERHEAD;
389
+
390
+ if (current_size + esize > max_size) break;
391
+
392
+ current_size += esize;
393
+ keep++;
394
+ }
395
+
396
+ if (keep == RARRAY_LEN(dyn_table)) return dyn_table;
397
+
398
+ VALUE new_table = rb_ary_new_capa(keep);
399
+ for (long i = 0; i < keep; i++)
400
+ rb_ary_push(new_table, rb_ary_entry(dyn_table, i));
401
+ return new_table;
402
+ }
403
+
404
+ static int hpack_decode_header_block(const uint8_t *buf, size_t len,
405
+ VALUE headers_out, VALUE *dyn_table,
406
+ long *max_table_size) {
407
+ size_t pos = 0;
408
+
409
+ while (pos < len) {
410
+ uint8_t byte = buf[pos];
411
+
412
+ if (byte & 0x80) {
413
+ /* indexed (RFC 7541 6.1) */
414
+ uint64_t index;
415
+ if (hpack_decode_int(buf, len, &pos, 7, &index) < 0) return -1;
416
+ if (index == 0) return -1;
417
+
418
+ VALUE name, value;
419
+ if (!RTEST(dynamic_table_lookup(*dyn_table, (long)index, &name, &value))) return -1;
420
+
421
+ rb_ary_push(headers_out, rb_ary_new_from_args(2, name, value));
422
+
423
+ } else if ((byte & 0xc0) == 0x40) {
424
+ /* literal with incremental indexing (RFC 7541 6.2.1) */
425
+ uint64_t index;
426
+ if (hpack_decode_int(buf, len, &pos, 6, &index) < 0) return -1;
427
+
428
+ VALUE name, value;
429
+ if (index > 0) {
430
+ VALUE dummy;
431
+ if (!RTEST(dynamic_table_lookup(*dyn_table, (long)index, &name, &dummy))) return -1;
432
+ } else {
433
+ if (hpack_decode_string(buf, len, &pos, &name) < 0) return -1;
434
+ }
435
+ if (hpack_decode_string(buf, len, &pos, &value) < 0) return -1;
436
+
437
+ rb_ary_push(headers_out, rb_ary_new_from_args(2, name, value));
438
+ *dyn_table = dynamic_table_add(*dyn_table, name, value, *max_table_size);
439
+
440
+ } else if ((byte & 0xf0) == 0x00 || (byte & 0xf0) == 0x10) {
441
+ /* literal without indexing / never indexed (RFC 7541 6.2.2, 6.2.3) */
442
+ uint64_t index;
443
+ if (hpack_decode_int(buf, len, &pos, 4, &index) < 0) return -1;
444
+
445
+ VALUE name, value;
446
+ if (index > 0) {
447
+ VALUE dummy;
448
+ if (!RTEST(dynamic_table_lookup(*dyn_table, (long)index, &name, &dummy))) return -1;
449
+ } else {
450
+ if (hpack_decode_string(buf, len, &pos, &name) < 0) return -1;
451
+ }
452
+ if (hpack_decode_string(buf, len, &pos, &value) < 0) return -1;
453
+
454
+ rb_ary_push(headers_out, rb_ary_new_from_args(2, name, value));
455
+
456
+ } else if ((byte & 0xe0) == 0x20) {
457
+ /* dynamic table size update (RFC 7541 6.3) */
458
+ uint64_t new_size;
459
+ if (hpack_decode_int(buf, len, &pos, 5, &new_size) < 0) return -1;
460
+ *max_table_size = (long)new_size;
461
+ *dyn_table = dynamic_table_evict(*dyn_table, *max_table_size);
462
+
463
+ } else {
464
+ return -1;
465
+ }
466
+ }
467
+
468
+ return 0;
469
+ }
470
+
471
+ static VALUE hpack_encode_header_block(VALUE headers) {
472
+ VALUE buf = rb_str_buf_new(256);
473
+
474
+ for (long i = 0; i < RARRAY_LEN(headers); i++) {
475
+ VALUE pair = rb_ary_entry(headers, i);
476
+ VALUE name = rb_ary_entry(pair, 0);
477
+ VALUE value = rb_ary_entry(pair, 1);
478
+
479
+ const uint8_t *name_ptr = (const uint8_t *)RSTRING_PTR(name);
480
+ size_t name_len = RSTRING_LEN(name);
481
+ const uint8_t *value_ptr = (const uint8_t *)RSTRING_PTR(value);
482
+ size_t value_len = RSTRING_LEN(value);
483
+
484
+ uint8_t int_buf[16];
485
+ size_t int_len;
486
+
487
+ int found_index = 0;
488
+ for (int idx = 1; idx <= STATIC_TABLE_SIZE; idx++) {
489
+ if (strlen(STATIC_TABLE[idx].name) == name_len &&
490
+ memcmp(STATIC_TABLE[idx].name, name_ptr, name_len) == 0) {
491
+ found_index = idx;
492
+ break;
493
+ }
494
+ }
495
+
496
+ if (found_index > 0) {
497
+ int_len = hpack_encode_int(int_buf, found_index, 4, 0x00);
498
+ rb_str_buf_cat(buf, (const char *)int_buf, int_len);
499
+ } else {
500
+ rb_str_buf_cat(buf, "\x00", 1);
501
+
502
+ size_t huff_len = hpack_huffman_encode_len(name_ptr, name_len);
503
+ if (huff_len < name_len) {
504
+ int_len = hpack_encode_int(int_buf, huff_len, 7, 0x80);
505
+ rb_str_buf_cat(buf, (const char *)int_buf, int_len);
506
+ uint8_t *huff_buf = ALLOCA_N(uint8_t, huff_len);
507
+ hpack_huffman_encode(name_ptr, name_len, huff_buf);
508
+ rb_str_buf_cat(buf, (const char *)huff_buf, huff_len);
509
+ } else {
510
+ int_len = hpack_encode_int(int_buf, name_len, 7, 0x00);
511
+ rb_str_buf_cat(buf, (const char *)int_buf, int_len);
512
+ rb_str_buf_cat(buf, (const char *)name_ptr, name_len);
513
+ }
514
+ }
515
+
516
+ size_t huff_len = hpack_huffman_encode_len(value_ptr, value_len);
517
+ if (huff_len < value_len) {
518
+ int_len = hpack_encode_int(int_buf, huff_len, 7, 0x80);
519
+ rb_str_buf_cat(buf, (const char *)int_buf, int_len);
520
+ uint8_t *huff_buf = ALLOCA_N(uint8_t, huff_len);
521
+ hpack_huffman_encode(value_ptr, value_len, huff_buf);
522
+ rb_str_buf_cat(buf, (const char *)huff_buf, huff_len);
523
+ } else {
524
+ int_len = hpack_encode_int(int_buf, value_len, 7, 0x00);
525
+ rb_str_buf_cat(buf, (const char *)int_buf, int_len);
526
+ rb_str_buf_cat(buf, (const char *)value_ptr, value_len);
527
+ }
528
+ }
529
+
530
+ return buf;
531
+ }
532
+
533
+ static void parse_frame_header(const uint8_t *buf, frame_header *fh) {
534
+ fh->length = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2];
535
+ fh->type = buf[3];
536
+ fh->flags = buf[4];
537
+ fh->stream_id = ((uint32_t)(buf[5] & 0x7f) << 24) | ((uint32_t)buf[6] << 16) |
538
+ ((uint32_t)buf[7] << 8) | buf[8];
539
+ }
540
+
541
+ static VALUE frame_type_sym(uint8_t type) {
542
+ switch (type) {
543
+ case FRAME_DATA: return ID2SYM(rb_intern("data"));
544
+ case FRAME_HEADERS: return ID2SYM(rb_intern("headers"));
545
+ case FRAME_PRIORITY: return ID2SYM(rb_intern("priority"));
546
+ case FRAME_RST_STREAM: return ID2SYM(rb_intern("rst_stream"));
547
+ case FRAME_SETTINGS: return ID2SYM(rb_intern("settings"));
548
+ case FRAME_PUSH_PROMISE: return ID2SYM(rb_intern("push_promise"));
549
+ case FRAME_PING: return ID2SYM(rb_intern("ping"));
550
+ case FRAME_GOAWAY: return ID2SYM(rb_intern("goaway"));
551
+ case FRAME_WINDOW_UPDATE: return ID2SYM(rb_intern("window_update"));
552
+ case FRAME_CONTINUATION: return ID2SYM(rb_intern("continuation"));
553
+ default: return ID2SYM(rb_intern("unknown"));
554
+ }
555
+ }
556
+
557
+ static void h2_parser_mark(void *ptr) { (void)ptr; }
558
+ static void h2_parser_free(void *ptr) { xfree(ptr); }
559
+ static size_t h2_parser_memsize(const void *ptr) { return sizeof(raptor_h2_parser); }
560
+
561
+ static const rb_data_type_t h2_parser_type = {
562
+ "raptor_http2_parser",
563
+ {h2_parser_mark, h2_parser_free, h2_parser_memsize},
564
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
565
+ };
566
+
567
+ static VALUE h2_parser_alloc(VALUE klass) {
568
+ raptor_h2_parser *parser;
569
+ VALUE obj = TypedData_Make_Struct(klass, raptor_h2_parser, &h2_parser_type, parser);
570
+ parser->max_table_size = HTTP2_DEFAULT_HEADER_TABLE_SIZE;
571
+ return obj;
572
+ }
573
+
574
+ static VALUE h2_parse_headers(VALUE self, VALUE header_block, VALUE dyn_table) {
575
+ raptor_h2_parser *parser;
576
+ TypedData_Get_Struct(self, raptor_h2_parser, &h2_parser_type, parser);
577
+
578
+ Check_Type(header_block, T_STRING);
579
+ Check_Type(dyn_table, T_ARRAY);
580
+
581
+ const uint8_t *buf = (const uint8_t *)RSTRING_PTR(header_block);
582
+ size_t len = RSTRING_LEN(header_block);
583
+
584
+ VALUE headers = rb_ary_new();
585
+ VALUE table = rb_ary_dup(dyn_table);
586
+ long max_size = parser->max_table_size;
587
+
588
+ if (hpack_decode_header_block(buf, len, headers, &table, &max_size) < 0)
589
+ rb_raise(eHttp2ParserError, "HPACK header block decode error");
590
+
591
+ parser->max_table_size = max_size;
592
+ return rb_ary_new_from_args(2, headers, table);
593
+ }
594
+
595
+ static VALUE h2_encode_headers(VALUE self, VALUE headers) {
596
+ (void)self;
597
+ Check_Type(headers, T_ARRAY);
598
+ return hpack_encode_header_block(headers);
599
+ }
600
+
601
+ static VALUE h2_parse_frame(VALUE self, VALUE buffer) {
602
+ (void)self;
603
+ Check_Type(buffer, T_STRING);
604
+
605
+ const uint8_t *buf = (const uint8_t *)RSTRING_PTR(buffer);
606
+ size_t len = RSTRING_LEN(buffer);
607
+
608
+ if (len < HTTP2_FRAME_HEADER_SIZE) return Qnil;
609
+
610
+ frame_header fh;
611
+ parse_frame_header(buf, &fh);
612
+
613
+ size_t total = HTTP2_FRAME_HEADER_SIZE + fh.length;
614
+ if (len < total) return Qnil;
615
+
616
+ VALUE frame = rb_hash_new();
617
+ rb_hash_aset(frame, ID2SYM(rb_intern("type")), frame_type_sym(fh.type));
618
+ rb_hash_aset(frame, ID2SYM(rb_intern("length")), UINT2NUM(fh.length));
619
+ rb_hash_aset(frame, ID2SYM(rb_intern("flags")), UINT2NUM(fh.flags));
620
+ rb_hash_aset(frame, ID2SYM(rb_intern("stream_id")), UINT2NUM(fh.stream_id));
621
+ rb_hash_aset(frame, ID2SYM(rb_intern("payload")), rb_str_new((const char *)(buf + HTTP2_FRAME_HEADER_SIZE), fh.length));
622
+
623
+ return rb_ary_new_from_args(2, frame, SIZET2NUM(total));
624
+ }
625
+
626
+ static VALUE h2_build_frame(VALUE self, VALUE type, VALUE flags, VALUE stream_id, VALUE payload) {
627
+ (void)self;
628
+
629
+ uint8_t frame_type;
630
+ ID type_id = SYM2ID(type);
631
+
632
+ if (type_id == rb_intern("data")) frame_type = FRAME_DATA;
633
+ else if (type_id == rb_intern("headers")) frame_type = FRAME_HEADERS;
634
+ else if (type_id == rb_intern("priority")) frame_type = FRAME_PRIORITY;
635
+ else if (type_id == rb_intern("rst_stream")) frame_type = FRAME_RST_STREAM;
636
+ else if (type_id == rb_intern("settings")) frame_type = FRAME_SETTINGS;
637
+ else if (type_id == rb_intern("push_promise")) frame_type = FRAME_PUSH_PROMISE;
638
+ else if (type_id == rb_intern("ping")) frame_type = FRAME_PING;
639
+ else if (type_id == rb_intern("goaway")) frame_type = FRAME_GOAWAY;
640
+ else if (type_id == rb_intern("window_update")) frame_type = FRAME_WINDOW_UPDATE;
641
+ else if (type_id == rb_intern("continuation")) frame_type = FRAME_CONTINUATION;
642
+ else rb_raise(rb_eArgError, "unknown frame type");
643
+
644
+ uint8_t frame_flags = (uint8_t)NUM2UINT(flags);
645
+ uint32_t sid = NUM2UINT(stream_id);
646
+
647
+ const char *payload_ptr = NIL_P(payload) ? "" : RSTRING_PTR(payload);
648
+ size_t payload_len = NIL_P(payload) ? 0 : RSTRING_LEN(payload);
649
+
650
+ uint8_t header[HTTP2_FRAME_HEADER_SIZE];
651
+ header[0] = (payload_len >> 16) & 0xff;
652
+ header[1] = (payload_len >> 8) & 0xff;
653
+ header[2] = payload_len & 0xff;
654
+ header[3] = frame_type;
655
+ header[4] = frame_flags;
656
+ header[5] = (sid >> 24) & 0x7f;
657
+ header[6] = (sid >> 16) & 0xff;
658
+ header[7] = (sid >> 8) & 0xff;
659
+ header[8] = sid & 0xff;
660
+
661
+ VALUE result = rb_str_new((const char *)header, HTTP2_FRAME_HEADER_SIZE);
662
+ rb_str_buf_cat(result, payload_ptr, payload_len);
663
+ return result;
664
+ }
665
+
666
+ static VALUE h2_connection_preface(VALUE self) {
667
+ (void)self;
668
+ return rb_str_new(HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN);
669
+ }
670
+
671
+ static VALUE h2_parse_settings(VALUE self, VALUE payload) {
672
+ (void)self;
673
+ Check_Type(payload, T_STRING);
674
+
675
+ const uint8_t *buf = (const uint8_t *)RSTRING_PTR(payload);
676
+ size_t len = RSTRING_LEN(payload);
677
+
678
+ if (len % 6 != 0)
679
+ rb_raise(eHttp2ParserError, "invalid SETTINGS payload length");
680
+
681
+ VALUE settings = rb_hash_new();
682
+
683
+ for (size_t i = 0; i < len; i += 6) {
684
+ uint16_t id = ((uint16_t)buf[i] << 8) | buf[i + 1];
685
+ uint32_t val = ((uint32_t)buf[i + 2] << 24) | ((uint32_t)buf[i + 3] << 16) |
686
+ ((uint32_t)buf[i + 4] << 8) | buf[i + 5];
687
+
688
+ switch (id) {
689
+ case SETTINGS_HEADER_TABLE_SIZE:
690
+ rb_hash_aset(settings, ID2SYM(rb_intern("header_table_size")), UINT2NUM(val)); break;
691
+ case SETTINGS_ENABLE_PUSH:
692
+ rb_hash_aset(settings, ID2SYM(rb_intern("enable_push")), UINT2NUM(val)); break;
693
+ case SETTINGS_MAX_CONCURRENT_STREAMS:
694
+ rb_hash_aset(settings, ID2SYM(rb_intern("max_concurrent_streams")), UINT2NUM(val)); break;
695
+ case SETTINGS_INITIAL_WINDOW_SIZE:
696
+ rb_hash_aset(settings, ID2SYM(rb_intern("initial_window_size")), UINT2NUM(val)); break;
697
+ case SETTINGS_MAX_FRAME_SIZE:
698
+ rb_hash_aset(settings, ID2SYM(rb_intern("max_frame_size")), UINT2NUM(val)); break;
699
+ case SETTINGS_MAX_HEADER_LIST_SIZE:
700
+ rb_hash_aset(settings, ID2SYM(rb_intern("max_header_list_size")), UINT2NUM(val)); break;
701
+ }
702
+ }
703
+
704
+ return settings;
705
+ }
706
+
707
+ static VALUE h2_build_settings(VALUE self, VALUE settings) {
708
+ (void)self;
709
+ Check_Type(settings, T_HASH);
710
+
711
+ VALUE buf = rb_str_buf_new(36);
712
+ VALUE keys = rb_funcall(settings, rb_intern("keys"), 0);
713
+
714
+ for (long i = 0; i < RARRAY_LEN(keys); i++) {
715
+ VALUE key = rb_ary_entry(keys, i);
716
+ VALUE val = rb_hash_aref(settings, key);
717
+ ID key_id = SYM2ID(key);
718
+
719
+ uint16_t id;
720
+ if (key_id == rb_intern("header_table_size")) id = SETTINGS_HEADER_TABLE_SIZE;
721
+ else if (key_id == rb_intern("enable_push")) id = SETTINGS_ENABLE_PUSH;
722
+ else if (key_id == rb_intern("max_concurrent_streams")) id = SETTINGS_MAX_CONCURRENT_STREAMS;
723
+ else if (key_id == rb_intern("initial_window_size")) id = SETTINGS_INITIAL_WINDOW_SIZE;
724
+ else if (key_id == rb_intern("max_frame_size")) id = SETTINGS_MAX_FRAME_SIZE;
725
+ else if (key_id == rb_intern("max_header_list_size")) id = SETTINGS_MAX_HEADER_LIST_SIZE;
726
+ else continue;
727
+
728
+ uint32_t v = NUM2UINT(val);
729
+ uint8_t entry[6];
730
+ entry[0] = (id >> 8) & 0xff;
731
+ entry[1] = id & 0xff;
732
+ entry[2] = (v >> 24) & 0xff;
733
+ entry[3] = (v >> 16) & 0xff;
734
+ entry[4] = (v >> 8) & 0xff;
735
+ entry[5] = v & 0xff;
736
+ rb_str_buf_cat(buf, (const char *)entry, 6);
737
+ }
738
+
739
+ return buf;
740
+ }
741
+
742
+ static VALUE h2_parse_window_update(VALUE self, VALUE payload) {
743
+ (void)self;
744
+ Check_Type(payload, T_STRING);
745
+
746
+ if (RSTRING_LEN(payload) != 4)
747
+ rb_raise(eHttp2ParserError, "invalid WINDOW_UPDATE payload length");
748
+
749
+ const uint8_t *buf = (const uint8_t *)RSTRING_PTR(payload);
750
+ uint32_t increment = ((uint32_t)(buf[0] & 0x7f) << 24) | ((uint32_t)buf[1] << 16) |
751
+ ((uint32_t)buf[2] << 8) | buf[3];
752
+
753
+ return UINT2NUM(increment);
754
+ }
755
+
756
+ RUBY_FUNC_EXPORTED void Init_raptor_http2(void) {
757
+ rb_ext_ractor_safe(true);
758
+
759
+ VALUE mRaptor = rb_define_module("Raptor");
760
+ cHttp2Parser = rb_define_class_under(mRaptor, "Http2Parser", rb_cObject);
761
+ eHttp2ParserError = rb_define_class_under(mRaptor, "Http2ParserError", rb_eStandardError);
762
+
763
+ rb_define_alloc_func(cHttp2Parser, h2_parser_alloc);
764
+ rb_define_method(cHttp2Parser, "parse_frame", h2_parse_frame, 1);
765
+ rb_define_method(cHttp2Parser, "parse_headers", h2_parse_headers, 2);
766
+ rb_define_method(cHttp2Parser, "encode_headers", h2_encode_headers, 1);
767
+ rb_define_method(cHttp2Parser, "parse_settings", h2_parse_settings, 1);
768
+ rb_define_method(cHttp2Parser, "build_settings", h2_build_settings, 1);
769
+ rb_define_method(cHttp2Parser, "build_frame", h2_build_frame, 4);
770
+ rb_define_method(cHttp2Parser, "parse_window_update", h2_parse_window_update, 1);
771
+ rb_define_singleton_method(cHttp2Parser, "connection_preface", h2_connection_preface, 0);
772
+ }