@gjsify/http2-native 0.4.0 → 0.4.3
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/package.json +55 -55
- package/prebuilds/linux-x86_64/libgjsifyhttp2.so +0 -0
- package/src/vala/frame-encoder.vala +141 -0
- package/src/vala/nghttp2-helpers.c +133 -0
- package/src/vala/nghttp2-helpers.h +82 -0
- package/src/vala/session-bridge.vala +65 -0
- package/src/vala/stream-id-allocator.vala +62 -0
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"name": "@gjsify/http2-native",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Optional Vala/GObject bridge providing nghttp2 primitives unreachable through libsoup's high-level GIR API: HPACK header-block encoding (for PUSH_PROMISE / DATA frames), server-side push stream-ID allocation, and a thin nghttp2_session wrapper for future cleartext HTTP/2 (h2c) support. Used by @gjsify/http2 to back ServerHttp2Stream.pushStream() / respondWithFD() / respondWithFile() and enable createServer() over plain TCP once the full Soup ↔ nghttp2 boundary is in place.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/esm/index.js",
|
|
7
|
+
"module": "lib/esm/index.js",
|
|
8
|
+
"types": "lib/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/types/index.d.ts",
|
|
12
|
+
"default": "./lib/esm/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"prebuilds",
|
|
18
|
+
"meson.build",
|
|
19
|
+
"src/vala"
|
|
20
|
+
],
|
|
21
|
+
"gjsify": {
|
|
22
|
+
"prebuilds": "prebuilds"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clear": "rm -rf lib build tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
|
|
26
|
+
"check": "tsc --noEmit",
|
|
27
|
+
"init:meson": "meson setup build .",
|
|
28
|
+
"init:meson:wipe": "gjsify run init:meson --wipe",
|
|
29
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
30
|
+
"build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}'",
|
|
31
|
+
"build:meson": "gjsify run init:meson && meson compile -C build",
|
|
32
|
+
"build:types": "tsc",
|
|
33
|
+
"build:prebuilds": "gjsify run build:meson && mkdir -p prebuilds/linux-x86_64 && cp build/libgjsifyhttp2.so build/GjsifyHttp2-1.0.gir build/GjsifyHttp2-1.0.typelib prebuilds/linux-x86_64/",
|
|
34
|
+
"build:gir-types": "ts-for-gir generate --externalDeps --allowMissingDeps --girDirectories=./prebuilds/linux-x86_64 --girDirectories=/usr/share/gir-1.0 --modules=GjsifyHttp2-1.0 --outdir=src/ts --npmScope=@girs --package=false --ignoreVersionConflicts=true"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"gjs",
|
|
38
|
+
"http2",
|
|
39
|
+
"nghttp2",
|
|
40
|
+
"hpack",
|
|
41
|
+
"push-promise",
|
|
42
|
+
"h2c",
|
|
43
|
+
"vala",
|
|
44
|
+
"native"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@girs/gjs": "4.0.0-rc.15",
|
|
48
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
49
|
+
"@girs/gobject-2.0": "2.88.0-4.0.0-rc.15"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@gjsify/cli": "workspace:^",
|
|
53
|
+
"@ts-for-gir/cli": "^4.0.0-rc.15",
|
|
54
|
+
"@types/node": "^25.6.2",
|
|
55
|
+
"typescript": "^6.0.3"
|
|
13
56
|
}
|
|
14
|
-
|
|
15
|
-
"files": [
|
|
16
|
-
"lib",
|
|
17
|
-
"prebuilds",
|
|
18
|
-
"meson.build",
|
|
19
|
-
"src/vala"
|
|
20
|
-
],
|
|
21
|
-
"gjsify": {
|
|
22
|
-
"prebuilds": "prebuilds"
|
|
23
|
-
},
|
|
24
|
-
"scripts": {
|
|
25
|
-
"clear": "rm -rf lib build tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
|
|
26
|
-
"check": "tsc --noEmit",
|
|
27
|
-
"init:meson": "meson setup build .",
|
|
28
|
-
"init:meson:wipe": "yarn init:meson --wipe",
|
|
29
|
-
"build": "yarn build:gjsify && yarn build:types",
|
|
30
|
-
"build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}'",
|
|
31
|
-
"build:meson": "yarn init:meson && meson compile -C build",
|
|
32
|
-
"build:types": "tsc",
|
|
33
|
-
"build:prebuilds": "yarn build:meson && mkdir -p prebuilds/linux-x86_64 && cp build/libgjsifyhttp2.so build/GjsifyHttp2-1.0.gir build/GjsifyHttp2-1.0.typelib prebuilds/linux-x86_64/",
|
|
34
|
-
"build:gir-types": "ts-for-gir generate --externalDeps --allowMissingDeps --girDirectories=./prebuilds/linux-x86_64 --girDirectories=/usr/share/gir-1.0 --modules=GjsifyHttp2-1.0 --outdir=src/ts --npmScope=@girs --package=false --ignoreVersionConflicts=true"
|
|
35
|
-
},
|
|
36
|
-
"keywords": [
|
|
37
|
-
"gjs",
|
|
38
|
-
"http2",
|
|
39
|
-
"nghttp2",
|
|
40
|
-
"hpack",
|
|
41
|
-
"push-promise",
|
|
42
|
-
"h2c",
|
|
43
|
-
"vala",
|
|
44
|
-
"native"
|
|
45
|
-
],
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@girs/gjs": "4.0.0-rc.15",
|
|
48
|
-
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
49
|
-
"@girs/gobject-2.0": "2.88.0-4.0.0-rc.15"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@gjsify/cli": "^0.4.0",
|
|
53
|
-
"@ts-for-gir/cli": "^4.0.0-rc.15",
|
|
54
|
-
"@types/node": "^25.6.2",
|
|
55
|
-
"typescript": "^6.0.3"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
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_PUSH_PROMISE = 0x05;
|
|
66
|
+
public const uint8 TYPE_GOAWAY = 0x07;
|
|
67
|
+
public const uint8 TYPE_WINDOW_UPDATE = 0x08;
|
|
68
|
+
|
|
69
|
+
public const uint8 FLAG_END_STREAM = 0x01;
|
|
70
|
+
public const uint8 FLAG_END_HEADERS = 0x04;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* encode_headers:
|
|
74
|
+
* @names: array of header names (must already be HTTP/2-lowercase)
|
|
75
|
+
* @values: array of header values, same length as @names
|
|
76
|
+
*
|
|
77
|
+
* HPACK-encode the given name/value pairs. Returns the encoded
|
|
78
|
+
* header block as a #GLib.Bytes, or %NULL on error.
|
|
79
|
+
*/
|
|
80
|
+
public GLib.Bytes? encode_headers (string[] names, string[] values) {
|
|
81
|
+
if (names.length != values.length) return null;
|
|
82
|
+
return _hpack_encode (names, values, names.length);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* build_data_frame:
|
|
87
|
+
* @stream_id: stream this DATA frame belongs to
|
|
88
|
+
* @end_stream: %TRUE to set END_STREAM flag (last DATA on the stream)
|
|
89
|
+
* @payload: payload bytes (caller must keep ≤ remote MAX_FRAME_SIZE)
|
|
90
|
+
*/
|
|
91
|
+
public GLib.Bytes build_data_frame (uint32 stream_id,
|
|
92
|
+
bool end_stream,
|
|
93
|
+
GLib.Bytes payload) {
|
|
94
|
+
uint8 flags = end_stream ? FLAG_END_STREAM : 0;
|
|
95
|
+
return _pack_frame (TYPE_DATA, flags, stream_id, payload);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* build_headers_frame:
|
|
100
|
+
* @stream_id: target stream id
|
|
101
|
+
* @end_stream: set END_STREAM (response with empty body)
|
|
102
|
+
* @end_headers: set END_HEADERS (single-frame header block)
|
|
103
|
+
* @header_block: HPACK-encoded headers from encode_headers()
|
|
104
|
+
*/
|
|
105
|
+
public GLib.Bytes build_headers_frame (uint32 stream_id,
|
|
106
|
+
bool end_stream,
|
|
107
|
+
bool end_headers,
|
|
108
|
+
GLib.Bytes header_block) {
|
|
109
|
+
uint8 flags = 0;
|
|
110
|
+
if (end_stream) flags |= FLAG_END_STREAM;
|
|
111
|
+
if (end_headers) flags |= FLAG_END_HEADERS;
|
|
112
|
+
return _pack_frame (TYPE_HEADERS, flags, stream_id, header_block);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* build_push_promise:
|
|
117
|
+
* @associated_stream_id: the request stream this push is associated with
|
|
118
|
+
* @promised_stream_id: the new even-numbered server stream-id (use
|
|
119
|
+
* StreamIdAllocator.next_promised())
|
|
120
|
+
* @header_block: HPACK-encoded request pseudo-headers for the push
|
|
121
|
+
* (`:method`, `:scheme`, `:authority`, `:path` etc.)
|
|
122
|
+
*
|
|
123
|
+
* Builds a complete PUSH_PROMISE frame with END_HEADERS set.
|
|
124
|
+
*/
|
|
125
|
+
public GLib.Bytes build_push_promise (uint32 associated_stream_id,
|
|
126
|
+
uint32 promised_stream_id,
|
|
127
|
+
GLib.Bytes header_block) {
|
|
128
|
+
return _pack_push_promise (associated_stream_id, promised_stream_id, header_block);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* nghttp2_version:
|
|
133
|
+
*
|
|
134
|
+
* Returns the libnghttp2 version string the bridge is linked against.
|
|
135
|
+
* Useful for diagnostics + version pinning in error messages.
|
|
136
|
+
*/
|
|
137
|
+
public unowned string nghttp2_version () {
|
|
138
|
+
return _nghttp2_version ();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tiny C shim around libnghttp2's HPACK encoder + frame helpers.
|
|
3
|
+
* See nghttp2-helpers.h for the rationale + per-function docs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#include "nghttp2-helpers.h"
|
|
7
|
+
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
|
|
11
|
+
GBytes *
|
|
12
|
+
gjsify_http2_hpack_encode (char **names,
|
|
13
|
+
char **values,
|
|
14
|
+
gsize n_pairs)
|
|
15
|
+
{
|
|
16
|
+
if (n_pairs == 0) {
|
|
17
|
+
return g_bytes_new (NULL, 0);
|
|
18
|
+
}
|
|
19
|
+
if (names == NULL || values == NULL) {
|
|
20
|
+
return NULL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
nghttp2_nv *nva = g_new0 (nghttp2_nv, n_pairs);
|
|
24
|
+
for (gsize i = 0; i < n_pairs; i++) {
|
|
25
|
+
const char *n = names[i] ? names[i] : "";
|
|
26
|
+
const char *v = values[i] ? values[i] : "";
|
|
27
|
+
nva[i].name = (uint8_t *) n;
|
|
28
|
+
nva[i].value = (uint8_t *) v;
|
|
29
|
+
nva[i].namelen = strlen (n);
|
|
30
|
+
nva[i].valuelen = strlen (v);
|
|
31
|
+
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
nghttp2_hd_deflater *def = NULL;
|
|
35
|
+
int rv = nghttp2_hd_deflate_new (&def, 4096);
|
|
36
|
+
if (rv != 0 || def == NULL) {
|
|
37
|
+
g_free (nva);
|
|
38
|
+
return NULL;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
size_t bound = nghttp2_hd_deflate_bound (def, nva, n_pairs);
|
|
42
|
+
guint8 *buf = g_malloc (bound);
|
|
43
|
+
ssize_t produced = nghttp2_hd_deflate_hd (def, buf, bound, nva, n_pairs);
|
|
44
|
+
|
|
45
|
+
nghttp2_hd_deflate_del (def);
|
|
46
|
+
g_free (nva);
|
|
47
|
+
|
|
48
|
+
if (produced < 0) {
|
|
49
|
+
g_free (buf);
|
|
50
|
+
return NULL;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* GBytes takes ownership of buf via g_free destroy notify. */
|
|
54
|
+
return g_bytes_new_with_free_func (buf, (gsize) produced, g_free, buf);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
GBytes *
|
|
58
|
+
gjsify_http2_pack_frame (guint8 type,
|
|
59
|
+
guint8 flags,
|
|
60
|
+
guint32 stream_id,
|
|
61
|
+
GBytes *payload)
|
|
62
|
+
{
|
|
63
|
+
gsize plen = 0;
|
|
64
|
+
const guint8 *pdata = payload ? g_bytes_get_data (payload, &plen) : NULL;
|
|
65
|
+
|
|
66
|
+
/* Frame header is 9 bytes:
|
|
67
|
+
* length : 24-bit big-endian
|
|
68
|
+
* type : 8-bit
|
|
69
|
+
* flags : 8-bit
|
|
70
|
+
* stream_id : 31-bit big-endian (high bit reserved, must be 0)
|
|
71
|
+
*/
|
|
72
|
+
gsize total = 9 + plen;
|
|
73
|
+
guint8 *buf = g_malloc (total);
|
|
74
|
+
|
|
75
|
+
buf[0] = (guint8) ((plen >> 16) & 0xff);
|
|
76
|
+
buf[1] = (guint8) ((plen >> 8) & 0xff);
|
|
77
|
+
buf[2] = (guint8) ((plen ) & 0xff);
|
|
78
|
+
buf[3] = type;
|
|
79
|
+
buf[4] = flags;
|
|
80
|
+
|
|
81
|
+
guint32 sid = stream_id & 0x7fffffffu;
|
|
82
|
+
buf[5] = (guint8) ((sid >> 24) & 0xff);
|
|
83
|
+
buf[6] = (guint8) ((sid >> 16) & 0xff);
|
|
84
|
+
buf[7] = (guint8) ((sid >> 8) & 0xff);
|
|
85
|
+
buf[8] = (guint8) ((sid ) & 0xff);
|
|
86
|
+
|
|
87
|
+
if (plen > 0 && pdata != NULL) {
|
|
88
|
+
memcpy (buf + 9, pdata, plen);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return g_bytes_new_with_free_func (buf, total, g_free, buf);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
GBytes *
|
|
95
|
+
gjsify_http2_pack_push_promise (guint32 associated_stream_id,
|
|
96
|
+
guint32 promised_stream_id,
|
|
97
|
+
GBytes *header_block)
|
|
98
|
+
{
|
|
99
|
+
gsize hlen = 0;
|
|
100
|
+
const guint8 *hdata = header_block ? g_bytes_get_data (header_block, &hlen) : NULL;
|
|
101
|
+
|
|
102
|
+
/* PUSH_PROMISE payload = 4-byte promised-stream-id (R + 31-bit id)
|
|
103
|
+
* || HPACK header block. */
|
|
104
|
+
gsize plen = 4 + hlen;
|
|
105
|
+
guint8 *payload = g_malloc (plen);
|
|
106
|
+
|
|
107
|
+
guint32 pid = promised_stream_id & 0x7fffffffu;
|
|
108
|
+
payload[0] = (guint8) ((pid >> 24) & 0xff);
|
|
109
|
+
payload[1] = (guint8) ((pid >> 16) & 0xff);
|
|
110
|
+
payload[2] = (guint8) ((pid >> 8) & 0xff);
|
|
111
|
+
payload[3] = (guint8) ((pid ) & 0xff);
|
|
112
|
+
|
|
113
|
+
if (hlen > 0 && hdata != NULL) {
|
|
114
|
+
memcpy (payload + 4, hdata, hlen);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
GBytes *payload_bytes = g_bytes_new_with_free_func (payload, plen, g_free, payload);
|
|
118
|
+
GBytes *frame = gjsify_http2_pack_frame (
|
|
119
|
+
NGHTTP2_PUSH_PROMISE,
|
|
120
|
+
NGHTTP2_FLAG_END_HEADERS,
|
|
121
|
+
associated_stream_id,
|
|
122
|
+
payload_bytes
|
|
123
|
+
);
|
|
124
|
+
g_bytes_unref (payload_bytes);
|
|
125
|
+
return frame;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const char *
|
|
129
|
+
gjsify_http2_nghttp2_version (void)
|
|
130
|
+
{
|
|
131
|
+
nghttp2_info *info = nghttp2_version (0);
|
|
132
|
+
return info ? info->version_str : "unknown";
|
|
133
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tiny C shim around libnghttp2's HPACK encoder + frame helpers.
|
|
3
|
+
*
|
|
4
|
+
* Vala can bind nghttp2 directly, but the calling conventions for opaque
|
|
5
|
+
* out-pointer constructors (nghttp2_hd_deflate_new) and pointer-array
|
|
6
|
+
* structs (nghttp2_nv) are awkward to express across a VAPI we have to
|
|
7
|
+
* maintain by hand. Wrapping them in plain C functions returning GBytes
|
|
8
|
+
* is far simpler — and keeps every pointer hand-off inside C, which is
|
|
9
|
+
* exactly the pattern @gjsify/http-soup-bridge / @gjsify/webrtc-native
|
|
10
|
+
* use to keep SpiderMonkey GC out of the libnghttp2 boxed lifecycle.
|
|
11
|
+
*
|
|
12
|
+
* Reference: nghttp2/nghttp2.h (libnghttp2 ≥ 1.40).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#ifndef GJSIFY_HTTP2_NGHTTP2_HELPERS_H
|
|
16
|
+
#define GJSIFY_HTTP2_NGHTTP2_HELPERS_H
|
|
17
|
+
|
|
18
|
+
#include <glib.h>
|
|
19
|
+
#include <nghttp2/nghttp2.h>
|
|
20
|
+
|
|
21
|
+
G_BEGIN_DECLS
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* gjsify_http2_hpack_encode:
|
|
25
|
+
* @names: flat name array (length = n_pairs)
|
|
26
|
+
* @values: flat value array (length = n_pairs)
|
|
27
|
+
* @n_pairs: number of header pairs
|
|
28
|
+
*
|
|
29
|
+
* HPACK-encode @n_pairs name/value strings using a fresh nghttp2 deflater
|
|
30
|
+
* (default 4096-byte dynamic table). Returns a freshly-allocated #GBytes
|
|
31
|
+
* containing the encoded header block, or %NULL on error.
|
|
32
|
+
*
|
|
33
|
+
* Lower-cases nothing — caller is responsible for HTTP/2 lowercase rules.
|
|
34
|
+
*/
|
|
35
|
+
GBytes *gjsify_http2_hpack_encode (char **names,
|
|
36
|
+
char **values,
|
|
37
|
+
gsize n_pairs);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* gjsify_http2_pack_frame:
|
|
41
|
+
* @type: nghttp2 frame type (1=HEADERS, 5=PUSH_PROMISE, 0=DATA, ...)
|
|
42
|
+
* @flags: frame flags (END_STREAM=0x01, END_HEADERS=0x04, ...)
|
|
43
|
+
* @stream_id: stream identifier (31-bit)
|
|
44
|
+
* @payload: frame payload bytes (already HPACK-encoded for header frames,
|
|
45
|
+
* raw bytes for DATA / PUSH_PROMISE-with-promised-id-prefix)
|
|
46
|
+
*
|
|
47
|
+
* Builds a complete HTTP/2 frame: 9-byte fixed header (length, type, flags,
|
|
48
|
+
* stream-id) followed by @payload. Returns a freshly-allocated #GBytes.
|
|
49
|
+
*
|
|
50
|
+
* Caller is responsible for splitting payloads larger than 16384 bytes
|
|
51
|
+
* across CONTINUATION frames if needed (this helper does NOT segment).
|
|
52
|
+
*/
|
|
53
|
+
GBytes *gjsify_http2_pack_frame (guint8 type,
|
|
54
|
+
guint8 flags,
|
|
55
|
+
guint32 stream_id,
|
|
56
|
+
GBytes *payload);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* gjsify_http2_pack_push_promise:
|
|
60
|
+
* @associated_stream_id: the parent stream the push is associated with
|
|
61
|
+
* @promised_stream_id: the new even-numbered stream-id the server reserves
|
|
62
|
+
* @header_block: HPACK-encoded request headers for the push
|
|
63
|
+
*
|
|
64
|
+
* Convenience wrapper combining the 4-byte promised-stream-id prefix that
|
|
65
|
+
* PUSH_PROMISE requires with @header_block, then frames it. Sets
|
|
66
|
+
* END_HEADERS flag (single-frame header block — caller must keep
|
|
67
|
+
* @header_block ≤ remote SETTINGS_MAX_FRAME_SIZE − 4).
|
|
68
|
+
*/
|
|
69
|
+
GBytes *gjsify_http2_pack_push_promise (guint32 associated_stream_id,
|
|
70
|
+
guint32 promised_stream_id,
|
|
71
|
+
GBytes *header_block);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* gjsify_http2_nghttp2_version:
|
|
75
|
+
*
|
|
76
|
+
* Returns the libnghttp2 runtime version string ("1.62.0", ...).
|
|
77
|
+
*/
|
|
78
|
+
const char *gjsify_http2_nghttp2_version (void);
|
|
79
|
+
|
|
80
|
+
G_END_DECLS
|
|
81
|
+
|
|
82
|
+
#endif /* GJSIFY_HTTP2_NGHTTP2_HELPERS_H */
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SessionBridge — placeholder for the future cleartext-HTTP/2 (h2c)
|
|
3
|
+
* session driver.
|
|
4
|
+
*
|
|
5
|
+
* Intent
|
|
6
|
+
* ──────
|
|
7
|
+
* Once we wire `nghttp2_session_server_new()` against a `Gio.Socket` we
|
|
8
|
+
* obtain from a Soup.Server `request-aborted` / raw socket callback (or
|
|
9
|
+
* directly from `Gio.SocketService` when bypassing Soup entirely), this
|
|
10
|
+
* class will own:
|
|
11
|
+
* • the nghttp2_session pointer
|
|
12
|
+
* • a 64 KiB read buffer driven by a `g_socket_create_source(IN)`
|
|
13
|
+
* watch on the GLib main context
|
|
14
|
+
* • a write queue drained on `OUT` readiness
|
|
15
|
+
* • mirror signals (`request_received`, `data_received`, `stream_closed`,
|
|
16
|
+
* `goaway_received`) re-emitted on the main context via `GLib.Idle.add`
|
|
17
|
+
* — same hop pattern as @gjsify/webrtc-native bridges
|
|
18
|
+
*
|
|
19
|
+
* Until that lands, this class only validates the HTTP/2 connection
|
|
20
|
+
* preface ("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24 bytes) so JS-side code
|
|
21
|
+
* detecting a prior-knowledge h2c upgrade can route the socket through
|
|
22
|
+
* a future native session loop.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
namespace GjsifyHttp2 {
|
|
26
|
+
|
|
27
|
+
public class SessionBridge : GLib.Object {
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* is_client_preface:
|
|
31
|
+
* @bytes: the first ≥ 24 bytes received on a TCP connection
|
|
32
|
+
*
|
|
33
|
+
* Returns %TRUE if @bytes starts with the RFC 7540 §3.5 client
|
|
34
|
+
* connection preface ("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n").
|
|
35
|
+
* Used to detect prior-knowledge h2c on a freshly-accepted socket
|
|
36
|
+
* before deciding whether to dispatch it to Soup (HTTP/1.1) or
|
|
37
|
+
* the future native nghttp2 session loop.
|
|
38
|
+
*/
|
|
39
|
+
public static bool is_client_preface (GLib.Bytes? bytes) {
|
|
40
|
+
if (bytes == null) return false;
|
|
41
|
+
const uint8 PREFACE[] = {
|
|
42
|
+
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
|
|
43
|
+
0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
|
|
44
|
+
0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
|
|
45
|
+
};
|
|
46
|
+
size_t blen;
|
|
47
|
+
unowned uint8[] data = bytes.get_data ();
|
|
48
|
+
blen = data.length;
|
|
49
|
+
if (blen < 24) return false;
|
|
50
|
+
for (int i = 0; i < 24; i++) {
|
|
51
|
+
if (data[i] != PREFACE[i]) return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* preface_length:
|
|
58
|
+
*
|
|
59
|
+
* Returns the size of the HTTP/2 client connection preface (24).
|
|
60
|
+
*/
|
|
61
|
+
public static uint preface_length () {
|
|
62
|
+
return 24;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* StreamIdAllocator — server-side HTTP/2 stream-id sequencer.
|
|
3
|
+
*
|
|
4
|
+
* Per RFC 7540 §5.1.1:
|
|
5
|
+
* • Client-initiated streams use odd ids (1, 3, 5, ...)
|
|
6
|
+
* • Server-initiated (push) streams use even ids (2, 4, 6, ...)
|
|
7
|
+
*
|
|
8
|
+
* The next-id pointer is monotonically increasing per session and must
|
|
9
|
+
* never wrap. Once we exhaust the 31-bit id space we MUST send a GOAWAY
|
|
10
|
+
* with NGHTTP2_NO_ERROR and let a fresh connection take over.
|
|
11
|
+
*
|
|
12
|
+
* One allocator instance per ServerHttp2Session.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
namespace GjsifyHttp2 {
|
|
16
|
+
|
|
17
|
+
public class StreamIdAllocator : GLib.Object {
|
|
18
|
+
|
|
19
|
+
/* Max valid stream id (31-bit). Anything >= this is exhausted. */
|
|
20
|
+
public const uint32 MAX_STREAM_ID = 0x7fffffffu;
|
|
21
|
+
|
|
22
|
+
private uint32 _next_promised = 2;
|
|
23
|
+
private uint32 _last_client = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* next_promised:
|
|
27
|
+
*
|
|
28
|
+
* Returns the next even stream-id to use for a PUSH_PROMISE.
|
|
29
|
+
* Returns 0 if the id space is exhausted — caller MUST then
|
|
30
|
+
* send GOAWAY and refuse further pushes.
|
|
31
|
+
*/
|
|
32
|
+
public uint32 next_promised () {
|
|
33
|
+
if (_next_promised > MAX_STREAM_ID) return 0;
|
|
34
|
+
uint32 id = _next_promised;
|
|
35
|
+
_next_promised += 2;
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* record_client_stream:
|
|
41
|
+
* @id: client-initiated odd stream-id observed on this session
|
|
42
|
+
*
|
|
43
|
+
* Track the highest client stream-id seen — needed for the
|
|
44
|
+
* `last-stream-id` field of a GOAWAY frame.
|
|
45
|
+
*/
|
|
46
|
+
public void record_client_stream (uint32 id) {
|
|
47
|
+
if ((id & 1u) == 1u && id > _last_client) {
|
|
48
|
+
_last_client = id;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public uint32 last_client_stream_id { get { return _last_client; } }
|
|
53
|
+
|
|
54
|
+
/** Number of pushes that can still be issued (count, not id). */
|
|
55
|
+
public uint32 remaining_pushes {
|
|
56
|
+
get {
|
|
57
|
+
if (_next_promised > MAX_STREAM_ID) return 0;
|
|
58
|
+
return (MAX_STREAM_ID - _next_promised) / 2u + 1u;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|