@gjsify/http2-native 0.4.0 → 0.4.4
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/lib/types/index.d.ts +2 -0
- package/lib/types/session-bridge.gjs.spec.d.ts +1 -0
- package/package.json +60 -55
- package/prebuilds/linux-aarch64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-aarch64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-aarch64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-ppc64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-ppc64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-ppc64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-riscv64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-riscv64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-riscv64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-s390x/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-s390x/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-s390x/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-x86_64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-x86_64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifyhttp2.so +0 -0
- package/src/vala/frame-encoder.vala +241 -0
- package/src/vala/nghttp2-helpers.c +842 -0
- package/src/vala/nghttp2-helpers.h +327 -0
- package/src/vala/session-bridge.vala +468 -0
- package/src/vala/stream-id-allocator.vala +62 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Http2FrameEncoder — HPACK + frame-builder bridge backed by libnghttp2.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists
|
|
5
|
+
* ───────────────
|
|
6
|
+
* libsoup ≥ 3 speaks HTTP/2 internally, but its high-level GIR API
|
|
7
|
+
* (`Soup.Server` / `Soup.ServerMessage`) does not expose the underlying
|
|
8
|
+
* nghttp2 session — so things like PUSH_PROMISE frames, fine-grained
|
|
9
|
+
* flow-control window updates, and explicit stream-IDs are unreachable
|
|
10
|
+
* from JS. nghttp2 itself has no Vala VAPI, and its callback-driven
|
|
11
|
+
* session API is awkward to bind directly.
|
|
12
|
+
*
|
|
13
|
+
* This bridge wraps just the pieces JS-side `@gjsify/http2` needs:
|
|
14
|
+
* • encode_headers() — HPACK-deflate a flat name/value pair array
|
|
15
|
+
* • build_data_frame() — wrap arbitrary bytes in a DATA frame
|
|
16
|
+
* • build_push_promise() — emit a complete PUSH_PROMISE frame
|
|
17
|
+
*
|
|
18
|
+
* All buffer ownership stays C-side via `GLib.Bytes` to keep the
|
|
19
|
+
* SpiderMonkey GC out of nghttp2's allocations — same pattern as
|
|
20
|
+
* `@gjsify/http-soup-bridge` and `@gjsify/webrtc-native`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
namespace GjsifyHttp2 {
|
|
24
|
+
|
|
25
|
+
[CCode (cname = "gjsify_http2_hpack_encode",
|
|
26
|
+
cheader_filename = "nghttp2-helpers.h")]
|
|
27
|
+
private extern GLib.Bytes? _hpack_encode (
|
|
28
|
+
[CCode (array_length = false, array_null_terminated = false)]
|
|
29
|
+
string[] names,
|
|
30
|
+
[CCode (array_length = false, array_null_terminated = false)]
|
|
31
|
+
string[] values,
|
|
32
|
+
size_t n_pairs);
|
|
33
|
+
|
|
34
|
+
[CCode (cname = "gjsify_http2_pack_frame",
|
|
35
|
+
cheader_filename = "nghttp2-helpers.h")]
|
|
36
|
+
private extern GLib.Bytes _pack_frame (uint8 type,
|
|
37
|
+
uint8 flags,
|
|
38
|
+
uint32 stream_id,
|
|
39
|
+
GLib.Bytes payload);
|
|
40
|
+
|
|
41
|
+
[CCode (cname = "gjsify_http2_pack_push_promise",
|
|
42
|
+
cheader_filename = "nghttp2-helpers.h")]
|
|
43
|
+
private extern GLib.Bytes _pack_push_promise (uint32 associated_stream_id,
|
|
44
|
+
uint32 promised_stream_id,
|
|
45
|
+
GLib.Bytes header_block);
|
|
46
|
+
|
|
47
|
+
[CCode (cname = "gjsify_http2_nghttp2_version",
|
|
48
|
+
cheader_filename = "nghttp2-helpers.h")]
|
|
49
|
+
private extern unowned string _nghttp2_version ();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* FrameEncoder — stateless HPACK / frame builder.
|
|
53
|
+
*
|
|
54
|
+
* Each call constructs a fresh nghttp2 deflater (4 KiB dynamic table)
|
|
55
|
+
* for the header block, so callers don't need to track encoder state
|
|
56
|
+
* across multiple pushes. This trades a few microseconds of HPACK
|
|
57
|
+
* setup for simplicity — server-push is not a hot path.
|
|
58
|
+
*/
|
|
59
|
+
public class FrameEncoder : GLib.Object {
|
|
60
|
+
|
|
61
|
+
/* Frame types (RFC 7540 §11.2) — exposed as constants so JS
|
|
62
|
+
* code doesn't have to hard-code magic numbers. */
|
|
63
|
+
public const uint8 TYPE_DATA = 0x00;
|
|
64
|
+
public const uint8 TYPE_HEADERS = 0x01;
|
|
65
|
+
public const uint8 TYPE_PRIORITY = 0x02;
|
|
66
|
+
public const uint8 TYPE_RST_STREAM = 0x03;
|
|
67
|
+
public const uint8 TYPE_SETTINGS = 0x04;
|
|
68
|
+
public const uint8 TYPE_PUSH_PROMISE = 0x05;
|
|
69
|
+
public const uint8 TYPE_PING = 0x06;
|
|
70
|
+
public const uint8 TYPE_GOAWAY = 0x07;
|
|
71
|
+
public const uint8 TYPE_WINDOW_UPDATE = 0x08;
|
|
72
|
+
|
|
73
|
+
public const uint8 FLAG_END_STREAM = 0x01;
|
|
74
|
+
public const uint8 FLAG_ACK = 0x01; /* SETTINGS/PING share value */
|
|
75
|
+
public const uint8 FLAG_END_HEADERS = 0x04;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* encode_headers:
|
|
79
|
+
* @names: array of header names (must already be HTTP/2-lowercase)
|
|
80
|
+
* @values: array of header values, same length as @names
|
|
81
|
+
*
|
|
82
|
+
* HPACK-encode the given name/value pairs. Returns the encoded
|
|
83
|
+
* header block as a #GLib.Bytes, or %NULL on error.
|
|
84
|
+
*/
|
|
85
|
+
public GLib.Bytes? encode_headers (string[] names, string[] values) {
|
|
86
|
+
if (names.length != values.length) return null;
|
|
87
|
+
return _hpack_encode (names, values, names.length);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* build_data_frame:
|
|
92
|
+
* @stream_id: stream this DATA frame belongs to
|
|
93
|
+
* @end_stream: %TRUE to set END_STREAM flag (last DATA on the stream)
|
|
94
|
+
* @payload: payload bytes (caller must keep ≤ remote MAX_FRAME_SIZE)
|
|
95
|
+
*/
|
|
96
|
+
public GLib.Bytes build_data_frame (uint32 stream_id,
|
|
97
|
+
bool end_stream,
|
|
98
|
+
GLib.Bytes payload) {
|
|
99
|
+
uint8 flags = end_stream ? FLAG_END_STREAM : 0;
|
|
100
|
+
return _pack_frame (TYPE_DATA, flags, stream_id, payload);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* build_headers_frame:
|
|
105
|
+
* @stream_id: target stream id
|
|
106
|
+
* @end_stream: set END_STREAM (response with empty body)
|
|
107
|
+
* @end_headers: set END_HEADERS (single-frame header block)
|
|
108
|
+
* @header_block: HPACK-encoded headers from encode_headers()
|
|
109
|
+
*/
|
|
110
|
+
public GLib.Bytes build_headers_frame (uint32 stream_id,
|
|
111
|
+
bool end_stream,
|
|
112
|
+
bool end_headers,
|
|
113
|
+
GLib.Bytes header_block) {
|
|
114
|
+
uint8 flags = 0;
|
|
115
|
+
if (end_stream) flags |= FLAG_END_STREAM;
|
|
116
|
+
if (end_headers) flags |= FLAG_END_HEADERS;
|
|
117
|
+
return _pack_frame (TYPE_HEADERS, flags, stream_id, header_block);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* build_push_promise:
|
|
122
|
+
* @associated_stream_id: the request stream this push is associated with
|
|
123
|
+
* @promised_stream_id: the new even-numbered server stream-id (use
|
|
124
|
+
* StreamIdAllocator.next_promised())
|
|
125
|
+
* @header_block: HPACK-encoded request pseudo-headers for the push
|
|
126
|
+
* (`:method`, `:scheme`, `:authority`, `:path` etc.)
|
|
127
|
+
*
|
|
128
|
+
* Builds a complete PUSH_PROMISE frame with END_HEADERS set.
|
|
129
|
+
*/
|
|
130
|
+
public GLib.Bytes build_push_promise (uint32 associated_stream_id,
|
|
131
|
+
uint32 promised_stream_id,
|
|
132
|
+
GLib.Bytes header_block) {
|
|
133
|
+
return _pack_push_promise (associated_stream_id, promised_stream_id, header_block);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* build_settings_frame:
|
|
138
|
+
* @ack: %TRUE to emit SETTINGS-ACK (empty payload).
|
|
139
|
+
* @ids: SETTINGS identifier array (e.g. SETTINGS_MAX_CONCURRENT_STREAMS=3)
|
|
140
|
+
* @values: matching values array
|
|
141
|
+
*
|
|
142
|
+
* Builds a SETTINGS frame. Length of @ids must equal @values.
|
|
143
|
+
* RFC 7540 §6.5: payload is N × (16-bit id || 32-bit value).
|
|
144
|
+
*/
|
|
145
|
+
public GLib.Bytes build_settings_frame (bool ack, uint16[] ids, uint32[] values) {
|
|
146
|
+
if (ack) {
|
|
147
|
+
return _pack_frame (TYPE_SETTINGS, FLAG_ACK, 0,
|
|
148
|
+
new GLib.Bytes (null));
|
|
149
|
+
}
|
|
150
|
+
int n = (ids.length < values.length) ? ids.length : values.length;
|
|
151
|
+
uint8[] payload = new uint8[n * 6];
|
|
152
|
+
for (int i = 0; i < n; i++) {
|
|
153
|
+
payload[i * 6 + 0] = (uint8)((ids[i] >> 8) & 0xff);
|
|
154
|
+
payload[i * 6 + 1] = (uint8)( ids[i] & 0xff);
|
|
155
|
+
payload[i * 6 + 2] = (uint8)((values[i] >> 24) & 0xff);
|
|
156
|
+
payload[i * 6 + 3] = (uint8)((values[i] >> 16) & 0xff);
|
|
157
|
+
payload[i * 6 + 4] = (uint8)((values[i] >> 8) & 0xff);
|
|
158
|
+
payload[i * 6 + 5] = (uint8)( values[i] & 0xff);
|
|
159
|
+
}
|
|
160
|
+
return _pack_frame (TYPE_SETTINGS, 0, 0, new GLib.Bytes (payload));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* build_window_update_frame:
|
|
165
|
+
* @stream_id: 0 for connection-level, > 0 for per-stream
|
|
166
|
+
* @increment: 31-bit window-size increment
|
|
167
|
+
*/
|
|
168
|
+
public GLib.Bytes build_window_update_frame (uint32 stream_id, uint32 increment) {
|
|
169
|
+
uint32 inc = increment & 0x7fffffffu;
|
|
170
|
+
uint8[] payload = new uint8[4];
|
|
171
|
+
payload[0] = (uint8)((inc >> 24) & 0xff);
|
|
172
|
+
payload[1] = (uint8)((inc >> 16) & 0xff);
|
|
173
|
+
payload[2] = (uint8)((inc >> 8) & 0xff);
|
|
174
|
+
payload[3] = (uint8)( inc & 0xff);
|
|
175
|
+
return _pack_frame (TYPE_WINDOW_UPDATE, 0, stream_id, new GLib.Bytes (payload));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* build_ping_frame:
|
|
180
|
+
* @ack: %TRUE to emit PING-ACK (caller MUST echo the payload).
|
|
181
|
+
* @payload: 8-byte opaque data (will be padded/truncated to 8 bytes).
|
|
182
|
+
*/
|
|
183
|
+
public GLib.Bytes build_ping_frame (bool ack, GLib.Bytes? payload) {
|
|
184
|
+
uint8[] data = new uint8[8];
|
|
185
|
+
if (payload != null) {
|
|
186
|
+
unowned uint8[] src = payload.get_data ();
|
|
187
|
+
int copy = (src.length < 8) ? src.length : 8;
|
|
188
|
+
for (int i = 0; i < copy; i++) data[i] = src[i];
|
|
189
|
+
}
|
|
190
|
+
return _pack_frame (TYPE_PING, ack ? FLAG_ACK : 0, 0, new GLib.Bytes (data));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* build_rst_stream_frame:
|
|
195
|
+
* @stream_id: stream to reset
|
|
196
|
+
* @error_code: RFC 7540 error code (NO_ERROR=0, PROTOCOL_ERROR=1, ...)
|
|
197
|
+
*/
|
|
198
|
+
public GLib.Bytes build_rst_stream_frame (uint32 stream_id, uint32 error_code) {
|
|
199
|
+
uint8[] payload = new uint8[4];
|
|
200
|
+
payload[0] = (uint8)((error_code >> 24) & 0xff);
|
|
201
|
+
payload[1] = (uint8)((error_code >> 16) & 0xff);
|
|
202
|
+
payload[2] = (uint8)((error_code >> 8) & 0xff);
|
|
203
|
+
payload[3] = (uint8)( error_code & 0xff);
|
|
204
|
+
return _pack_frame (TYPE_RST_STREAM, 0, stream_id, new GLib.Bytes (payload));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* build_goaway_frame:
|
|
209
|
+
* @last_stream_id: highest stream id processed (caller tracks)
|
|
210
|
+
* @error_code: RFC 7540 error code
|
|
211
|
+
* @debug_data: optional debug info (often empty)
|
|
212
|
+
*/
|
|
213
|
+
public GLib.Bytes build_goaway_frame (uint32 last_stream_id,
|
|
214
|
+
uint32 error_code,
|
|
215
|
+
GLib.Bytes? debug_data) {
|
|
216
|
+
unowned uint8[] dbg = (debug_data != null) ? debug_data.get_data () : new uint8[0];
|
|
217
|
+
uint8[] payload = new uint8[8 + dbg.length];
|
|
218
|
+
uint32 lsi = last_stream_id & 0x7fffffffu;
|
|
219
|
+
payload[0] = (uint8)((lsi >> 24) & 0xff);
|
|
220
|
+
payload[1] = (uint8)((lsi >> 16) & 0xff);
|
|
221
|
+
payload[2] = (uint8)((lsi >> 8) & 0xff);
|
|
222
|
+
payload[3] = (uint8)( lsi & 0xff);
|
|
223
|
+
payload[4] = (uint8)((error_code >> 24) & 0xff);
|
|
224
|
+
payload[5] = (uint8)((error_code >> 16) & 0xff);
|
|
225
|
+
payload[6] = (uint8)((error_code >> 8) & 0xff);
|
|
226
|
+
payload[7] = (uint8)( error_code & 0xff);
|
|
227
|
+
for (int i = 0; i < dbg.length; i++) payload[8 + i] = dbg[i];
|
|
228
|
+
return _pack_frame (TYPE_GOAWAY, 0, 0, new GLib.Bytes (payload));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* nghttp2_version:
|
|
233
|
+
*
|
|
234
|
+
* Returns the libnghttp2 version string the bridge is linked against.
|
|
235
|
+
* Useful for diagnostics + version pinning in error messages.
|
|
236
|
+
*/
|
|
237
|
+
public unowned string nghttp2_version () {
|
|
238
|
+
return _nghttp2_version ();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|