hyperion-rb 1.6.2 → 2.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4768 -0
- data/README.md +222 -13
- data/ext/hyperion_h2_codec/Cargo.lock +7 -0
- data/ext/hyperion_h2_codec/Cargo.toml +33 -0
- data/ext/hyperion_h2_codec/extconf.rb +73 -0
- data/ext/hyperion_h2_codec/src/frames.rs +140 -0
- data/ext/hyperion_h2_codec/src/hpack/huffman.rs +161 -0
- data/ext/hyperion_h2_codec/src/hpack.rs +457 -0
- data/ext/hyperion_h2_codec/src/lib.rs +296 -0
- data/ext/hyperion_http/extconf.rb +28 -0
- data/ext/hyperion_http/h2_codec_glue.c +408 -0
- data/ext/hyperion_http/page_cache.c +1125 -0
- data/ext/hyperion_http/parser.c +473 -38
- data/ext/hyperion_http/sendfile.c +982 -0
- data/ext/hyperion_http/websocket.c +493 -0
- data/ext/hyperion_io_uring/Cargo.lock +33 -0
- data/ext/hyperion_io_uring/Cargo.toml +34 -0
- data/ext/hyperion_io_uring/extconf.rb +74 -0
- data/ext/hyperion_io_uring/src/lib.rs +316 -0
- data/lib/hyperion/adapter/rack.rb +370 -42
- data/lib/hyperion/admin_listener.rb +207 -0
- data/lib/hyperion/admin_middleware.rb +36 -7
- data/lib/hyperion/cli.rb +310 -11
- data/lib/hyperion/config.rb +440 -14
- data/lib/hyperion/connection.rb +679 -22
- data/lib/hyperion/deprecations.rb +81 -0
- data/lib/hyperion/dispatch_mode.rb +165 -0
- data/lib/hyperion/fiber_local.rb +75 -13
- data/lib/hyperion/h2_admission.rb +77 -0
- data/lib/hyperion/h2_codec.rb +499 -0
- data/lib/hyperion/http/page_cache.rb +122 -0
- data/lib/hyperion/http/sendfile.rb +696 -0
- data/lib/hyperion/http2/native_hpack_adapter.rb +70 -0
- data/lib/hyperion/http2_handler.rb +618 -19
- data/lib/hyperion/io_uring.rb +317 -0
- data/lib/hyperion/lint_wrapper_pool.rb +126 -0
- data/lib/hyperion/master.rb +96 -9
- data/lib/hyperion/metrics/path_templater.rb +68 -0
- data/lib/hyperion/metrics.rb +256 -0
- data/lib/hyperion/prometheus_exporter.rb +150 -0
- data/lib/hyperion/request.rb +13 -0
- data/lib/hyperion/response_writer.rb +477 -16
- data/lib/hyperion/runtime.rb +195 -0
- data/lib/hyperion/server/route_table.rb +179 -0
- data/lib/hyperion/server.rb +519 -55
- data/lib/hyperion/static_preload.rb +133 -0
- data/lib/hyperion/thread_pool.rb +61 -7
- data/lib/hyperion/tls.rb +343 -1
- data/lib/hyperion/version.rb +1 -1
- data/lib/hyperion/websocket/close_codes.rb +71 -0
- data/lib/hyperion/websocket/connection.rb +876 -0
- data/lib/hyperion/websocket/frame.rb +356 -0
- data/lib/hyperion/websocket/handshake.rb +525 -0
- data/lib/hyperion/worker.rb +111 -9
- data/lib/hyperion.rb +137 -3
- metadata +50 -1
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/* ----------------------------------------------------------------------
|
|
2
|
+
* Hyperion::H2Codec::CGlue — direct C → Rust bridge for HPACK encode/decode.
|
|
3
|
+
*
|
|
4
|
+
* 2.4-A (RFC §3 2.4.0) — round-2 FFI marshalling. The 2.0.0 path used
|
|
5
|
+
* Fiddle::Pointer per call (`Fiddle::Pointer[bytes]` allocates a Ruby
|
|
6
|
+
* object for each pointer wrapper). The 2.2.x fix-B path collapsed
|
|
7
|
+
* argv encoding into a single `pack('Q*', buffer:)` and reused scratch
|
|
8
|
+
* buffers, which trimmed per-call alloc from ~12 strings to ~7.5.
|
|
9
|
+
*
|
|
10
|
+
* The remaining ~7.5 strings/call sat in the Fiddle layer:
|
|
11
|
+
*
|
|
12
|
+
* 1. `pack('Q*', buffer: scratch_argv)` — ~1 alloc
|
|
13
|
+
* 2. `Fiddle::Pointer[scratch_blob/argv/out]` — 3 wrappers/call
|
|
14
|
+
* 3. Per-header `name.b` / `value.b` (when source isn't ASCII-8BIT)
|
|
15
|
+
*
|
|
16
|
+
* 2.4-A bypasses Fiddle entirely on the per-call hot path. Ruby calls
|
|
17
|
+
* `Hyperion::H2Codec::CGlue.encoder_encode_v3(handle_long, headers, scratch_out)`
|
|
18
|
+
* which:
|
|
19
|
+
*
|
|
20
|
+
* * walks `headers` in-place, building a packed argv buffer
|
|
21
|
+
* (4×u64 per header) on the C stack (or a heap-allocated growable
|
|
22
|
+
* buffer if there are >256 headers);
|
|
23
|
+
* * concatenates name+value bytes into the C-side blob buffer (also
|
|
24
|
+
* stack-resident with heap fallback);
|
|
25
|
+
* * directly invokes the cached `hyperion_h2_codec_encoder_encode_v2`
|
|
26
|
+
* function pointer (resolved at install time via `dlsym`);
|
|
27
|
+
* * truncates `scratch_out` to the bytes-written count via
|
|
28
|
+
* `rb_str_set_len`, returning the count to Ruby. Ruby's wrapper
|
|
29
|
+
* does the single unavoidable allocation: `byteslice(0, n)` to
|
|
30
|
+
* hand back the encoded frame as an owned String.
|
|
31
|
+
*
|
|
32
|
+
* Per-call allocations (steady state, no header table growth):
|
|
33
|
+
* * 1 String for the byteslice return.
|
|
34
|
+
* * That's it. argv/blob are stack-resident.
|
|
35
|
+
*
|
|
36
|
+
* Fiddle still owns the build-time loader path: `H2Codec.load!` opens
|
|
37
|
+
* the cdylib via `Fiddle.dlopen` exactly as before, then calls
|
|
38
|
+
* `CGlue.install(path_string)` to hand the cdylib path off to this
|
|
39
|
+
* C unit. We re-`dlopen` it with `RTLD_NOLOAD | RTLD_NOW` (or just
|
|
40
|
+
* `RTLD_NOW` on macOS where NOLOAD isn't honoured the same way) so
|
|
41
|
+
* we get a `void *handle` we can `dlsym` on. The encoder/decoder
|
|
42
|
+
* `_new`/`_free` Rust funcs are still called via Fiddle on the
|
|
43
|
+
* one-time ctor/dtor — only encode/decode is on the hot path.
|
|
44
|
+
*
|
|
45
|
+
* If `dlopen`/`dlsym` fail (or the .so isn't present), `Init_hyperion_h2_codec_glue`
|
|
46
|
+
* leaves `CGlue.available?` returning `false` and Ruby falls back to
|
|
47
|
+
* the v2 (Fiddle) path automatically.
|
|
48
|
+
*
|
|
49
|
+
* Why a separate `_v3` Ruby method instead of replacing v2?
|
|
50
|
+
* * Lets the v2 Fiddle path remain as a drop-in fallback when CGlue
|
|
51
|
+
* fails to load (older glibc, hardened sandbox blocking dlopen).
|
|
52
|
+
* * Spec parity check ("v3 and v2 produce identical decoded
|
|
53
|
+
* headers") catches any C-side argv-packing regression before it
|
|
54
|
+
* hits production traffic.
|
|
55
|
+
* ---------------------------------------------------------------------- */
|
|
56
|
+
|
|
57
|
+
#include <ruby.h>
|
|
58
|
+
#include <ruby/encoding.h>
|
|
59
|
+
|
|
60
|
+
#include <stdint.h>
|
|
61
|
+
#include <stdlib.h>
|
|
62
|
+
#include <string.h>
|
|
63
|
+
#include <dlfcn.h>
|
|
64
|
+
|
|
65
|
+
/* ------------------------------------------------------------------ */
|
|
66
|
+
/* Cached Rust function pointers + module/class globals. */
|
|
67
|
+
/* ------------------------------------------------------------------ */
|
|
68
|
+
|
|
69
|
+
static VALUE rb_mHyperion;
|
|
70
|
+
static VALUE rb_mH2Codec;
|
|
71
|
+
static VALUE rb_mCGlue;
|
|
72
|
+
static VALUE rb_eOutputOverflow;
|
|
73
|
+
|
|
74
|
+
/* Match the Rust ABI from ext/hyperion_h2_codec/src/lib.rs. */
|
|
75
|
+
typedef long long (*rust_encode_v2_fn)(
|
|
76
|
+
void *handle,
|
|
77
|
+
const unsigned char *blob_ptr, size_t blob_len,
|
|
78
|
+
const uint64_t *argv_ptr, size_t argv_count,
|
|
79
|
+
unsigned char *out_ptr, size_t out_capacity);
|
|
80
|
+
|
|
81
|
+
typedef int (*rust_decode_fn)(
|
|
82
|
+
void *handle,
|
|
83
|
+
const unsigned char *in_ptr, unsigned int in_len,
|
|
84
|
+
unsigned char *out_ptr, unsigned int out_capacity);
|
|
85
|
+
|
|
86
|
+
typedef unsigned int (*rust_abi_version_fn)(void);
|
|
87
|
+
|
|
88
|
+
static void *rust_dl_handle = NULL;
|
|
89
|
+
static rust_encode_v2_fn rust_encode_v2 = NULL;
|
|
90
|
+
static rust_decode_fn rust_decode = NULL;
|
|
91
|
+
static rust_abi_version_fn rust_abi_version = NULL;
|
|
92
|
+
static int cglue_available = 0;
|
|
93
|
+
|
|
94
|
+
/* Stack-resident argv/blob caps. 99% of HEADERS frames have <= 32
|
|
95
|
+
* pairs and total name+value bytes < 4 KiB, so these defaults handle
|
|
96
|
+
* the steady state without touching the heap. Above this we malloc
|
|
97
|
+
* a one-shot growable buffer (still cheaper than a Ruby allocation
|
|
98
|
+
* because no GC pressure). */
|
|
99
|
+
#define HYP_GLUE_STACK_ARGV_CAP 64 /* 64 headers × 4×u64 = 2 KiB */
|
|
100
|
+
#define HYP_GLUE_STACK_BLOB_CAP 8192 /* 8 KiB total name+value bytes */
|
|
101
|
+
|
|
102
|
+
/* ------------------------------------------------------------------ */
|
|
103
|
+
/* CGlue.install(path) -> true on success, false otherwise */
|
|
104
|
+
/* */
|
|
105
|
+
/* Called once from `Hyperion::H2Codec.load!` after Fiddle has */
|
|
106
|
+
/* probed the candidate paths and confirmed the cdylib loads. We */
|
|
107
|
+
/* dlopen the same path independently so we get our own handle to */
|
|
108
|
+
/* dlsym against — re-dlopen with the same path is a no-op refcount */
|
|
109
|
+
/* bump per POSIX semantics, so this doesn't double-load the .so. */
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
static VALUE rb_cglue_install(VALUE self, VALUE rb_path) {
|
|
112
|
+
(void)self;
|
|
113
|
+
Check_Type(rb_path, T_STRING);
|
|
114
|
+
|
|
115
|
+
if (cglue_available) {
|
|
116
|
+
/* Idempotent — second call is a no-op. */
|
|
117
|
+
return Qtrue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const char *path = StringValueCStr(rb_path);
|
|
121
|
+
void *h = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
|
122
|
+
if (!h) {
|
|
123
|
+
return Qfalse;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
rust_abi_version_fn abi_fn =
|
|
127
|
+
(rust_abi_version_fn)dlsym(h, "hyperion_h2_codec_abi_version");
|
|
128
|
+
rust_encode_v2_fn enc_fn =
|
|
129
|
+
(rust_encode_v2_fn)dlsym(h, "hyperion_h2_codec_encoder_encode_v2");
|
|
130
|
+
rust_decode_fn dec_fn =
|
|
131
|
+
(rust_decode_fn)dlsym(h, "hyperion_h2_codec_decoder_decode");
|
|
132
|
+
|
|
133
|
+
if (!abi_fn || !enc_fn || !dec_fn) {
|
|
134
|
+
dlclose(h);
|
|
135
|
+
return Qfalse;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ABI 1 is the only version currently shipped. If a future Rust
|
|
139
|
+
* crate bumps the ABI, this guard prevents the v3 path from
|
|
140
|
+
* silently dispatching to a mismatched layout. The v2 (Fiddle)
|
|
141
|
+
* path has its own ABI check. */
|
|
142
|
+
if (abi_fn() != 1) {
|
|
143
|
+
dlclose(h);
|
|
144
|
+
return Qfalse;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
rust_dl_handle = h;
|
|
148
|
+
rust_abi_version = abi_fn;
|
|
149
|
+
rust_encode_v2 = enc_fn;
|
|
150
|
+
rust_decode = dec_fn;
|
|
151
|
+
cglue_available = 1;
|
|
152
|
+
return Qtrue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static VALUE rb_cglue_available_p(VALUE self) {
|
|
156
|
+
(void)self;
|
|
157
|
+
return cglue_available ? Qtrue : Qfalse;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ------------------------------------------------------------------ */
|
|
161
|
+
/* CGlue.encoder_encode_v3(handle_addr, headers_array, scratch_out) */
|
|
162
|
+
/* */
|
|
163
|
+
/* Per-call allocations: ZERO from C; ONE String alloc happens in */
|
|
164
|
+
/* Ruby for the byteslice return at the call site. */
|
|
165
|
+
/* */
|
|
166
|
+
/* Returns: Integer bytes_written. Raises on overflow / bad args. */
|
|
167
|
+
/* ------------------------------------------------------------------ */
|
|
168
|
+
static VALUE rb_cglue_encoder_encode_v3(VALUE self,
|
|
169
|
+
VALUE rb_handle_addr,
|
|
170
|
+
VALUE rb_headers,
|
|
171
|
+
VALUE rb_scratch_out) {
|
|
172
|
+
(void)self;
|
|
173
|
+
|
|
174
|
+
if (!cglue_available || !rust_encode_v2) {
|
|
175
|
+
rb_raise(rb_eRuntimeError,
|
|
176
|
+
"Hyperion::H2Codec::CGlue not installed (call .install(path) first)");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Check_Type(rb_headers, T_ARRAY);
|
|
180
|
+
Check_Type(rb_scratch_out, T_STRING);
|
|
181
|
+
|
|
182
|
+
void *handle = (void *)(intptr_t)NUM2LL(rb_handle_addr);
|
|
183
|
+
long count = RARRAY_LEN(rb_headers);
|
|
184
|
+
if (count == 0) {
|
|
185
|
+
return INT2FIX(0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Stack buffers — heap fallback for unusually large header sets. */
|
|
189
|
+
uint64_t stack_argv[HYP_GLUE_STACK_ARGV_CAP * 4];
|
|
190
|
+
uint8_t stack_blob[HYP_GLUE_STACK_BLOB_CAP];
|
|
191
|
+
|
|
192
|
+
uint64_t *argv = stack_argv;
|
|
193
|
+
uint8_t *blob = stack_blob;
|
|
194
|
+
int argv_on_heap = 0;
|
|
195
|
+
int blob_on_heap = 0;
|
|
196
|
+
size_t argv_cap = HYP_GLUE_STACK_ARGV_CAP;
|
|
197
|
+
size_t blob_cap = HYP_GLUE_STACK_BLOB_CAP;
|
|
198
|
+
|
|
199
|
+
if ((size_t)count > argv_cap) {
|
|
200
|
+
argv = (uint64_t *)malloc((size_t)count * 4 * sizeof(uint64_t));
|
|
201
|
+
if (!argv) {
|
|
202
|
+
rb_raise(rb_eNoMemError, "H2Codec::CGlue argv malloc failed");
|
|
203
|
+
}
|
|
204
|
+
argv_cap = (size_t)count;
|
|
205
|
+
argv_on_heap = 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* First pass: compute total blob size to decide stack vs heap. */
|
|
209
|
+
size_t total_blob = 0;
|
|
210
|
+
for (long i = 0; i < count; i++) {
|
|
211
|
+
VALUE pair = rb_ary_entry(rb_headers, i);
|
|
212
|
+
if (TYPE(pair) != T_ARRAY || RARRAY_LEN(pair) < 2) {
|
|
213
|
+
if (argv_on_heap) free(argv);
|
|
214
|
+
rb_raise(rb_eArgError,
|
|
215
|
+
"H2Codec::CGlue.encode_v3: each header must be a [name, value] array");
|
|
216
|
+
}
|
|
217
|
+
VALUE name = rb_ary_entry(pair, 0);
|
|
218
|
+
VALUE value = rb_ary_entry(pair, 1);
|
|
219
|
+
if (TYPE(name) != T_STRING || TYPE(value) != T_STRING) {
|
|
220
|
+
if (argv_on_heap) free(argv);
|
|
221
|
+
rb_raise(rb_eTypeError,
|
|
222
|
+
"H2Codec::CGlue.encode_v3: header name and value must be Strings");
|
|
223
|
+
}
|
|
224
|
+
total_blob += (size_t)RSTRING_LEN(name);
|
|
225
|
+
total_blob += (size_t)RSTRING_LEN(value);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (total_blob > blob_cap) {
|
|
229
|
+
blob = (uint8_t *)malloc(total_blob);
|
|
230
|
+
if (!blob) {
|
|
231
|
+
if (argv_on_heap) free(argv);
|
|
232
|
+
rb_raise(rb_eNoMemError, "H2Codec::CGlue blob malloc failed");
|
|
233
|
+
}
|
|
234
|
+
blob_cap = total_blob;
|
|
235
|
+
blob_on_heap = 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Second pass: pack argv quads + concatenate blob. We *do not*
|
|
239
|
+
* call `name.b` / `value.b` here even when the source encoding
|
|
240
|
+
* isn't ASCII_8BIT — HPACK only cares about the byte sequence.
|
|
241
|
+
* `RSTRING_PTR` + `RSTRING_LEN` give us the raw byte view
|
|
242
|
+
* regardless of the Ruby encoding tag, which avoids a per-header
|
|
243
|
+
* String allocation that the v2 Ruby path could not avoid for
|
|
244
|
+
* non-binary inputs. */
|
|
245
|
+
size_t blob_off = 0;
|
|
246
|
+
for (long i = 0; i < count; i++) {
|
|
247
|
+
VALUE pair = rb_ary_entry(rb_headers, i);
|
|
248
|
+
VALUE name = rb_ary_entry(pair, 0);
|
|
249
|
+
VALUE value = rb_ary_entry(pair, 1);
|
|
250
|
+
|
|
251
|
+
size_t nl = (size_t)RSTRING_LEN(name);
|
|
252
|
+
size_t vl = (size_t)RSTRING_LEN(value);
|
|
253
|
+
|
|
254
|
+
size_t base = (size_t)i * 4;
|
|
255
|
+
argv[base + 0] = (uint64_t)blob_off;
|
|
256
|
+
argv[base + 1] = (uint64_t)nl;
|
|
257
|
+
argv[base + 2] = (uint64_t)(blob_off + nl);
|
|
258
|
+
argv[base + 3] = (uint64_t)vl;
|
|
259
|
+
|
|
260
|
+
if (nl > 0) {
|
|
261
|
+
memcpy(blob + blob_off, RSTRING_PTR(name), nl);
|
|
262
|
+
}
|
|
263
|
+
blob_off += nl;
|
|
264
|
+
if (vl > 0) {
|
|
265
|
+
memcpy(blob + blob_off, RSTRING_PTR(value), vl);
|
|
266
|
+
}
|
|
267
|
+
blob_off += vl;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Make sure scratch_out has at least `out_capacity` bytes of
|
|
271
|
+
* usable buffer space. Ruby pre-sized it via `String.new(capacity:)`
|
|
272
|
+
* + `<<` to set the length, so RSTRING_LEN reflects the full
|
|
273
|
+
* usable region (we'll truncate to `written` after the FFI call).
|
|
274
|
+
*/
|
|
275
|
+
size_t out_capacity = (size_t)RSTRING_LEN(rb_scratch_out);
|
|
276
|
+
/* rb_str_modify ensures the scratch String is mutable, has its own
|
|
277
|
+
* (unshared) backing buffer, and that RSTRING_PTR is valid for
|
|
278
|
+
* out_capacity bytes of writes. Required before we hand its raw
|
|
279
|
+
* pointer to Rust. */
|
|
280
|
+
rb_str_modify(rb_scratch_out);
|
|
281
|
+
unsigned char *out_ptr = (unsigned char *)RSTRING_PTR(rb_scratch_out);
|
|
282
|
+
|
|
283
|
+
long long written = rust_encode_v2(handle,
|
|
284
|
+
blob, blob_off,
|
|
285
|
+
argv, (size_t)count,
|
|
286
|
+
out_ptr, out_capacity);
|
|
287
|
+
|
|
288
|
+
if (argv_on_heap) free(argv);
|
|
289
|
+
if (blob_on_heap) free(blob);
|
|
290
|
+
|
|
291
|
+
/* Keep the headers array alive across the FFI call — RSTRING_PTR
|
|
292
|
+
* pointers we read from `name`/`value` are only valid while their
|
|
293
|
+
* VALUEs are live and unmoved. */
|
|
294
|
+
RB_GC_GUARD(rb_headers);
|
|
295
|
+
RB_GC_GUARD(rb_scratch_out);
|
|
296
|
+
|
|
297
|
+
if (written == -1) {
|
|
298
|
+
rb_raise(rb_eOutputOverflow,
|
|
299
|
+
"Hyperion::H2Codec::CGlue.encode_v3 output buffer overflow "
|
|
300
|
+
"(capacity=%zu)", out_capacity);
|
|
301
|
+
}
|
|
302
|
+
if (written < 0) {
|
|
303
|
+
rb_raise(rb_eRuntimeError,
|
|
304
|
+
"Hyperion::H2Codec::CGlue.encode_v3 failed (rc=%lld)", written);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Truncate the scratch String to the bytes-written count. The
|
|
308
|
+
* caller's Ruby wrapper then `byteslice(0, written)`s it — that
|
|
309
|
+
* single byteslice is the only String alloc per encode call. */
|
|
310
|
+
rb_str_set_len(rb_scratch_out, (long)written);
|
|
311
|
+
return LL2NUM(written);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* ------------------------------------------------------------------ */
|
|
315
|
+
/* CGlue.decoder_decode_v3(handle_addr, bytes_str, scratch_out) */
|
|
316
|
+
/* */
|
|
317
|
+
/* The decoder side is less hot than encode (responses are encode- */
|
|
318
|
+
/* heavy), but the same Fiddle-layer overhead applies on h2 request */
|
|
319
|
+
/* dispatch. Same direct C → Rust path. */
|
|
320
|
+
/* ------------------------------------------------------------------ */
|
|
321
|
+
static VALUE rb_cglue_decoder_decode_v3(VALUE self,
|
|
322
|
+
VALUE rb_handle_addr,
|
|
323
|
+
VALUE rb_bytes,
|
|
324
|
+
VALUE rb_scratch_out) {
|
|
325
|
+
(void)self;
|
|
326
|
+
|
|
327
|
+
if (!cglue_available || !rust_decode) {
|
|
328
|
+
rb_raise(rb_eRuntimeError,
|
|
329
|
+
"Hyperion::H2Codec::CGlue not installed (call .install(path) first)");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
Check_Type(rb_bytes, T_STRING);
|
|
333
|
+
Check_Type(rb_scratch_out, T_STRING);
|
|
334
|
+
|
|
335
|
+
void *handle = (void *)(intptr_t)NUM2LL(rb_handle_addr);
|
|
336
|
+
|
|
337
|
+
long in_len = RSTRING_LEN(rb_bytes);
|
|
338
|
+
if (in_len == 0) {
|
|
339
|
+
rb_str_set_len(rb_scratch_out, 0);
|
|
340
|
+
return INT2FIX(0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
rb_str_modify(rb_scratch_out);
|
|
344
|
+
long out_capacity = RSTRING_LEN(rb_scratch_out);
|
|
345
|
+
|
|
346
|
+
int written = rust_decode(handle,
|
|
347
|
+
(const unsigned char *)RSTRING_PTR(rb_bytes),
|
|
348
|
+
(unsigned int)in_len,
|
|
349
|
+
(unsigned char *)RSTRING_PTR(rb_scratch_out),
|
|
350
|
+
(unsigned int)out_capacity);
|
|
351
|
+
|
|
352
|
+
RB_GC_GUARD(rb_bytes);
|
|
353
|
+
RB_GC_GUARD(rb_scratch_out);
|
|
354
|
+
|
|
355
|
+
if (written == -1) {
|
|
356
|
+
rb_raise(rb_eOutputOverflow,
|
|
357
|
+
"Hyperion::H2Codec::CGlue.decode_v3 output buffer overflow "
|
|
358
|
+
"(capacity=%ld)", out_capacity);
|
|
359
|
+
}
|
|
360
|
+
if (written < 0) {
|
|
361
|
+
rb_raise(rb_eRuntimeError,
|
|
362
|
+
"Hyperion::H2Codec::CGlue.decode_v3 failed (rc=%d)", written);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
rb_str_set_len(rb_scratch_out, (long)written);
|
|
366
|
+
return INT2NUM(written);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* ------------------------------------------------------------------ */
|
|
370
|
+
/* Init */
|
|
371
|
+
/* ------------------------------------------------------------------ */
|
|
372
|
+
|
|
373
|
+
void Init_hyperion_h2_codec_glue(void) {
|
|
374
|
+
rb_mHyperion = rb_const_get(rb_cObject, rb_intern("Hyperion"));
|
|
375
|
+
|
|
376
|
+
/* `Hyperion::H2Codec` may not be defined yet at C-init time — its
|
|
377
|
+
* Ruby file is loaded lazily by the gem entry point. Define it
|
|
378
|
+
* here as an empty module placeholder if needed; the Ruby file
|
|
379
|
+
* will reopen it. */
|
|
380
|
+
if (rb_const_defined(rb_mHyperion, rb_intern("H2Codec"))) {
|
|
381
|
+
rb_mH2Codec = rb_const_get(rb_mHyperion, rb_intern("H2Codec"));
|
|
382
|
+
} else {
|
|
383
|
+
rb_mH2Codec = rb_define_module_under(rb_mHyperion, "H2Codec");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
rb_mCGlue = rb_define_module_under(rb_mH2Codec, "CGlue");
|
|
387
|
+
|
|
388
|
+
/* OutputOverflow is defined in lib/hyperion/h2_codec.rb (Ruby
|
|
389
|
+
* side). If the Ruby file loaded first we reuse it; otherwise we
|
|
390
|
+
* define a placeholder that the Ruby file's class definition
|
|
391
|
+
* re-opens (it's a `class OutputOverflow < StandardError; end`
|
|
392
|
+
* so re-opening is safe). */
|
|
393
|
+
if (rb_const_defined(rb_mH2Codec, rb_intern("OutputOverflow"))) {
|
|
394
|
+
rb_eOutputOverflow = rb_const_get(rb_mH2Codec, rb_intern("OutputOverflow"));
|
|
395
|
+
} else {
|
|
396
|
+
rb_eOutputOverflow = rb_define_class_under(rb_mH2Codec,
|
|
397
|
+
"OutputOverflow",
|
|
398
|
+
rb_eStandardError);
|
|
399
|
+
}
|
|
400
|
+
rb_global_variable(&rb_eOutputOverflow);
|
|
401
|
+
|
|
402
|
+
rb_define_singleton_method(rb_mCGlue, "install", rb_cglue_install, 1);
|
|
403
|
+
rb_define_singleton_method(rb_mCGlue, "available?", rb_cglue_available_p, 0);
|
|
404
|
+
rb_define_singleton_method(rb_mCGlue, "encoder_encode_v3",
|
|
405
|
+
rb_cglue_encoder_encode_v3, 3);
|
|
406
|
+
rb_define_singleton_method(rb_mCGlue, "decoder_decode_v3",
|
|
407
|
+
rb_cglue_decoder_decode_v3, 3);
|
|
408
|
+
}
|