@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 CHANGED
@@ -1,57 +1,57 @@
1
1
  {
2
- "name": "@gjsify/http2-native",
3
- "version": "0.4.0",
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"
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
+ }