File Coverage

blib/lib/Hypersonic/Protocol/HTTP2.pm
Criterion Covered Total %
statement 15 82 18.2
branch 4 10 40.0
condition 1 3 33.3
subroutine 3 22 13.6
pod 0 20 0.0
total 23 137 16.7


line stmt bran cond sub pod time code
1             package Hypersonic::Protocol::HTTP2;
2 36     36   98421 use strict;
  36         106  
  36         1928  
3 36     36   200 use warnings;
  36         100  
  36         100696  
4              
5             # Hypersonic::Protocol::HTTP2 - JIT code generation for HTTP/2 protocol
6             #
7             # This module provides compile-time code generation for HTTP/2 support
8             # using nghttp2 for binary framing and HPACK compression.
9             # All methods generate C code - zero runtime overhead.
10              
11             our $VERSION = '0.15';
12              
13             # Cache nghttp2 detection result
14             my $_nghttp2_info;
15              
16             # Detect nghttp2 library on the system
17             sub check_nghttp2 {
18 37 100   37 0 523 return $_nghttp2_info if defined $_nghttp2_info;
19            
20             # Try pkg-config first (if available)
21 36         148241 my $cflags = `pkg-config --cflags libnghttp2 2>/dev/null`;
22 36         145143 my $ldflags = `pkg-config --libs libnghttp2 2>/dev/null`;
23            
24 36 50 33     1472 if ($? == 0 && $ldflags) {
25 0         0 chomp($cflags, $ldflags);
26 0         0 $_nghttp2_info = { cflags => $cflags, ldflags => $ldflags };
27 0         0 return $_nghttp2_info;
28             }
29            
30             # Try standard paths on macOS/Linux
31 36         837 my @prefixes = qw(/opt/homebrew /usr/local /usr);
32 36         363 for my $prefix (@prefixes) {
33 108 50       1816 if (-f "$prefix/include/nghttp2/nghttp2.h") {
34 0         0 $_nghttp2_info = {
35             cflags => "-I$prefix/include",
36             ldflags => "-L$prefix/lib -lnghttp2",
37             };
38 0         0 return $_nghttp2_info;
39             }
40             }
41            
42 36         194 $_nghttp2_info = 0; # Not found
43 36         1312 return undef;
44             }
45              
46             # Get compiler flags
47             sub get_extra_cflags {
48 0 0   0 0   my $info = check_nghttp2() or return '';
49 0           return $info->{cflags};
50             }
51              
52             sub get_extra_ldflags {
53 0 0   0 0   my $info = check_nghttp2() or return '';
54 0           return $info->{ldflags};
55             }
56              
57             # Protocol identifier
58 0     0 0   sub protocol_id { 'h2' }
59 0     0 0   sub version_string { 'HTTP/2' }
60              
61             # Generate nghttp2 includes
62             sub gen_includes {
63 0     0 0   my ($class, $builder) = @_;
64            
65 0           $builder->line('#include ')
66             ->line('#define HYPERSONIC_HTTP2 1')
67             ->blank;
68            
69 0           return $builder;
70             }
71              
72             # Generate HTTP/2 connection structure
73             sub gen_connection_struct {
74 0     0 0   my ($class, $builder) = @_;
75            
76 0           $builder->comment('HTTP/2 connection state')
77             ->line('typedef struct {')
78             ->line(' int fd;')
79             ->line(' nghttp2_session* session;')
80             ->line(' int protocol; /* PROTO_HTTP1=1, PROTO_HTTP2=2 */')
81             ->line(' time_t last_activity;')
82             ->line(' /* Stream state for multiplexed requests */')
83             ->line(' char* pending_method;')
84             ->line(' int pending_method_len;')
85             ->line(' char* pending_path;')
86             ->line(' int pending_path_len;')
87             ->line(' char* pending_body;')
88             ->line(' int pending_body_len;')
89             ->line(' int32_t pending_stream_id;')
90             ->line('} H2Connection;')
91             ->blank
92             ->line('#define PROTO_HTTP1 1')
93             ->line('#define PROTO_HTTP2 2')
94             ->line('#define MAX_H2_CONNECTIONS 1024')
95             ->line('static H2Connection g_h2_connections[MAX_H2_CONNECTIONS];')
96             ->blank;
97            
98 0           return $builder;
99             }
100              
101             # Generate nghttp2 callbacks
102             sub gen_callbacks {
103 0     0 0   my ($class, $builder, %opts) = @_;
104            
105             # Send callback - writes data to socket
106 0           $builder->comment('HTTP/2: nghttp2 send callback')
107             ->line('static ssize_t h2_send_cb(nghttp2_session* session,')
108             ->line(' const uint8_t* data, size_t length,')
109             ->line(' int flags, void* user_data) {')
110             ->line(' (void)session; (void)flags;')
111             ->line(' H2Connection* conn = (H2Connection*)user_data;')
112             ->line('#ifdef HYPERSONIC_TLS')
113             ->line(' TLSConnection* tls = get_tls_connection(conn->fd);')
114             ->if('tls')
115             ->line('return tls_send(tls, data, length);')
116             ->endif
117             ->line('#endif')
118             ->line(' ssize_t rv = send(conn->fd, data, length, 0);')
119             ->if('rv < 0')
120             ->if('errno == EAGAIN || errno == EWOULDBLOCK')
121             ->line('return NGHTTP2_ERR_WOULDBLOCK;')
122             ->endif
123             ->line('return NGHTTP2_ERR_CALLBACK_FAILURE;')
124             ->endif
125             ->line(' return rv;')
126             ->line('}')
127             ->blank;
128            
129             # Header callback - receives request headers
130 0           $builder->comment('HTTP/2: Header received callback')
131             ->line('static int h2_on_header_cb(nghttp2_session* session,')
132             ->line(' const nghttp2_frame* frame,')
133             ->line(' const uint8_t* name, size_t namelen,')
134             ->line(' const uint8_t* value, size_t valuelen,')
135             ->line(' uint8_t flags, void* user_data) {')
136             ->line(' (void)session; (void)flags;')
137             ->line(' H2Connection* conn = (H2Connection*)user_data;')
138             ->blank
139             ->if('frame->hd.type != NGHTTP2_HEADERS')
140             ->line('return 0;')
141             ->endif
142             ->blank
143             ->comment('Capture :method and :path pseudo-headers')
144             ->if('namelen == 7 && memcmp(name, ":method", 7) == 0')
145             ->line('conn->pending_method = (char*)malloc(valuelen + 1);')
146             ->line('memcpy(conn->pending_method, value, valuelen);')
147             ->line('conn->pending_method[valuelen] = \'\\0\';')
148             ->line('conn->pending_method_len = valuelen;')
149             ->elsif('namelen == 5 && memcmp(name, ":path", 5) == 0')
150             ->line('conn->pending_path = (char*)malloc(valuelen + 1);')
151             ->line('memcpy(conn->pending_path, value, valuelen);')
152             ->line('conn->pending_path[valuelen] = \'\\0\';')
153             ->line('conn->pending_path_len = valuelen;')
154             ->endif
155             ->line(' conn->pending_stream_id = frame->hd.stream_id;')
156             ->line(' return 0;')
157             ->line('}')
158             ->blank;
159            
160             # Data chunk callback - receives request body
161 0           $builder->comment('HTTP/2: Data chunk received callback')
162             ->line('static int h2_on_data_chunk_cb(nghttp2_session* session,')
163             ->line(' uint8_t flags, int32_t stream_id,')
164             ->line(' const uint8_t* data, size_t len,')
165             ->line(' void* user_data) {')
166             ->line(' (void)session; (void)flags; (void)stream_id;')
167             ->line(' H2Connection* conn = (H2Connection*)user_data;')
168             ->blank
169             ->comment('Append to pending body')
170             ->if('!conn->pending_body')
171             ->line('conn->pending_body = (char*)malloc(len + 1);')
172             ->line('memcpy(conn->pending_body, data, len);')
173             ->line('conn->pending_body_len = len;')
174             ->else
175             ->line('conn->pending_body = realloc(conn->pending_body, conn->pending_body_len + len + 1);')
176             ->line('memcpy(conn->pending_body + conn->pending_body_len, data, len);')
177             ->line('conn->pending_body_len += len;')
178             ->endif
179             ->line(' conn->pending_body[conn->pending_body_len] = \'\\0\';')
180             ->line(' return 0;')
181             ->line('}')
182             ->blank;
183            
184             # Frame received callback - trigger request handling
185 0           $builder->comment('HTTP/2: Frame received - dispatch request when headers complete')
186             ->line('static int h2_on_frame_recv_cb(nghttp2_session* session,')
187             ->line(' const nghttp2_frame* frame,')
188             ->line(' void* user_data) {')
189             ->line(' H2Connection* conn = (H2Connection*)user_data;')
190             ->blank
191             ->comment('Process request when HEADERS frame with END_HEADERS is received')
192             ->if('frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST && (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)')
193             ->comment('If END_STREAM is also set, no body expected - dispatch now')
194             ->if('frame->hd.flags & NGHTTP2_FLAG_END_STREAM')
195             ->line('h2_dispatch_request(session, conn, frame->hd.stream_id);')
196             ->endif
197             ->endif
198             ->blank
199             ->comment('Process request when DATA frame with END_STREAM is received')
200             ->if('frame->hd.type == NGHTTP2_DATA && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)')
201             ->line('h2_dispatch_request(session, conn, frame->hd.stream_id);')
202             ->endif
203             ->blank
204             ->line(' return 0;')
205             ->line('}')
206             ->blank;
207            
208             # Stream close callback - cleanup
209 0           $builder->comment('HTTP/2: Stream closed callback - cleanup')
210             ->line('static int h2_on_stream_close_cb(nghttp2_session* session,')
211             ->line(' int32_t stream_id, uint32_t error_code,')
212             ->line(' void* user_data) {')
213             ->line(' (void)session; (void)stream_id; (void)error_code;')
214             ->line(' H2Connection* conn = (H2Connection*)user_data;')
215             ->blank
216             ->comment('Free pending request data')
217             ->if('conn->pending_method')
218             ->line('free(conn->pending_method); conn->pending_method = NULL;')
219             ->endif
220             ->if('conn->pending_path')
221             ->line('free(conn->pending_path); conn->pending_path = NULL;')
222             ->endif
223             ->if('conn->pending_body')
224             ->line('free(conn->pending_body); conn->pending_body = NULL;')
225             ->endif
226             ->line(' conn->pending_method_len = 0;')
227             ->line(' conn->pending_path_len = 0;')
228             ->line(' conn->pending_body_len = 0;')
229             ->line(' return 0;')
230             ->line('}')
231             ->blank;
232            
233 0           return $builder;
234             }
235              
236             # Generate HTTP/2 session initialization
237             sub gen_session_init {
238 0     0 0   my ($class, $builder) = @_;
239            
240 0           $builder->comment('HTTP/2: Initialize nghttp2 callbacks (once)')
241             ->line('static nghttp2_session_callbacks* g_h2_callbacks = NULL;')
242             ->blank
243             ->line('static void init_h2_callbacks(void) {')
244             ->if('g_h2_callbacks')
245             ->line('return;')
246             ->endif
247             ->line(' nghttp2_session_callbacks_new(&g_h2_callbacks);')
248             ->line(' nghttp2_session_callbacks_set_send_callback(g_h2_callbacks, h2_send_cb);')
249             ->line(' nghttp2_session_callbacks_set_on_header_callback(g_h2_callbacks, h2_on_header_cb);')
250             ->line(' nghttp2_session_callbacks_set_on_data_chunk_recv_callback(g_h2_callbacks, h2_on_data_chunk_cb);')
251             ->line(' nghttp2_session_callbacks_set_on_frame_recv_callback(g_h2_callbacks, h2_on_frame_recv_cb);')
252             ->line(' nghttp2_session_callbacks_set_on_stream_close_callback(g_h2_callbacks, h2_on_stream_close_cb);')
253             ->line('}')
254             ->blank
255             ->line('static int init_h2_session(H2Connection* conn) {')
256             ->line(' init_h2_callbacks();')
257             ->blank
258             ->line(' int rv = nghttp2_session_server_new(&conn->session, g_h2_callbacks, conn);')
259             ->if('rv != 0')
260             ->line('return -1;')
261             ->endif
262             ->blank
263             ->comment('Send server settings')
264             ->line(' nghttp2_settings_entry settings[] = {')
265             ->line(' { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 },')
266             ->line(' { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535 },')
267             ->line(' };')
268             ->line(' rv = nghttp2_submit_settings(conn->session, NGHTTP2_FLAG_NONE,')
269             ->line(' settings, sizeof(settings)/sizeof(settings[0]));')
270             ->if('rv != 0')
271             ->line('nghttp2_session_del(conn->session);')
272             ->line('conn->session = NULL;')
273             ->line('return -1;')
274             ->endif
275             ->blank
276             ->comment('Send settings immediately')
277             ->line(' nghttp2_session_send(conn->session);')
278             ->line(' conn->protocol = PROTO_HTTP2;')
279             ->line(' return 0;')
280             ->line('}')
281             ->blank;
282            
283 0           return $builder;
284             }
285              
286             # Generate HTTP/2 request dispatcher
287             sub gen_dispatcher {
288 0     0 0   my ($class, $builder, %opts) = @_;
289            
290 0           $builder->comment('HTTP/2: Dispatch request and send response')
291             ->line('static void h2_dispatch_request(nghttp2_session* session,')
292             ->line(' H2Connection* conn, int32_t stream_id) {')
293             ->if('!conn->pending_method || !conn->pending_path')
294             ->line('return;')
295             ->endif
296             ->blank
297             ->comment('Strip query string for route matching')
298             ->line(' const char* query_pos = memchr(conn->pending_path, \'?\', conn->pending_path_len);')
299             ->line(' int path_len = query_pos ? (query_pos - conn->pending_path) : conn->pending_path_len;')
300             ->blank
301             ->comment('Dispatch to route handler')
302             ->line(' const char* resp;')
303             ->line(' int resp_len;')
304             ->line(' int handler_idx;')
305             ->line(' int result = dispatch_request(conn->pending_method, conn->pending_method_len,')
306             ->line(' conn->pending_path, path_len,')
307             ->line(' &resp, &resp_len, &handler_idx);')
308             ->blank
309             ->comment('Build HTTP/2 response')
310             ->if('result == 0')
311             ->comment('Static route - parse prebuilt HTTP/1.1 response')
312             ->line('h2_send_static_response(session, stream_id, resp, resp_len);')
313             ->elsif('result == 1')
314             ->comment('Dynamic route - call Perl handler')
315             ->line('h2_call_dynamic_handler(session, conn, stream_id, handler_idx);')
316             ->else
317             ->comment('404')
318             ->line('h2_send_404(session, stream_id);')
319             ->endif
320             ->line('}')
321             ->blank;
322            
323 0           return $builder;
324             }
325              
326             # Generate HTTP/2 response sender
327             sub gen_response_sender {
328 0     0 0   my ($class, $builder) = @_;
329            
330 0           $builder->comment('HTTP/2: Send static response')
331             ->line('static void h2_send_static_response(nghttp2_session* session,')
332             ->line(' int32_t stream_id,')
333             ->line(' const char* http1_resp, int resp_len) {')
334             ->line(' /* Parse HTTP/1.1 response to extract status and body */')
335             ->line(' /* Format: HTTP/1.1 200 OK\\r\\nContent-Type: ...\\r\\n\\r\\nbody */')
336             ->line(' ')
337             ->line(' /* Find status code */')
338             ->line(' int status = 200;')
339             ->line(' if (resp_len > 12 && memcmp(http1_resp, "HTTP/1.1 ", 9) == 0) {')
340             ->line(' status = (http1_resp[9] - \'0\') * 100 + ')
341             ->line(' (http1_resp[10] - \'0\') * 10 + ')
342             ->line(' (http1_resp[11] - \'0\');')
343             ->line(' }')
344             ->line(' ')
345             ->line(' /* Find body (after \\r\\n\\r\\n) */')
346             ->line(' const char* body = strstr(http1_resp, "\\r\\n\\r\\n");')
347             ->line(' int body_len = 0;')
348             ->line(' if (body) {')
349             ->line(' body += 4;')
350             ->line(' body_len = resp_len - (body - http1_resp);')
351             ->line(' } else {')
352             ->line(' body = "";')
353             ->line(' }')
354             ->line(' ')
355             ->line(' /* Find Content-Type */')
356             ->line(' const char* ct = "text/plain";')
357             ->line(' const char* ct_hdr = strstr(http1_resp, "Content-Type: ");')
358             ->line(' static char ct_buf[64];')
359             ->line(' if (ct_hdr) {')
360             ->line(' ct_hdr += 14;')
361             ->line(' const char* ct_end = strstr(ct_hdr, "\\r\\n");')
362             ->line(' if (ct_end && ct_end - ct_hdr < 63) {')
363             ->line(' memcpy(ct_buf, ct_hdr, ct_end - ct_hdr);')
364             ->line(' ct_buf[ct_end - ct_hdr] = \'\\0\';')
365             ->line(' ct = ct_buf;')
366             ->line(' }')
367             ->line(' }')
368             ->line(' ')
369             ->line(' /* Build content-length string */')
370             ->line(' char cl_buf[16];')
371             ->line(' snprintf(cl_buf, sizeof(cl_buf), "%d", body_len);')
372             ->line(' ')
373             ->line(' /* Build status string */')
374             ->line(' char status_buf[4];')
375             ->line(' snprintf(status_buf, sizeof(status_buf), "%d", status);')
376             ->line(' ')
377             ->line(' /* Submit HTTP/2 response headers */')
378             ->line(' nghttp2_nv hdrs[] = {')
379             ->line(' { (uint8_t*)":status", (uint8_t*)status_buf, 7, strlen(status_buf), NGHTTP2_NV_FLAG_NONE },')
380             ->line(' { (uint8_t*)"content-type", (uint8_t*)ct, 12, strlen(ct), NGHTTP2_NV_FLAG_NONE },')
381             ->line(' { (uint8_t*)"content-length", (uint8_t*)cl_buf, 14, strlen(cl_buf), NGHTTP2_NV_FLAG_NONE },')
382             ->line(' };')
383             ->line(' ')
384             ->line(' /* Create data provider for body */')
385             ->line(' nghttp2_data_provider data_prd;')
386             ->line(' data_prd.source.ptr = (void*)body;')
387             ->line(' data_prd.read_callback = h2_data_source_read_cb;')
388             ->line(' ')
389             ->line(' nghttp2_submit_response(session, stream_id, hdrs, 3, &data_prd);')
390             ->line(' nghttp2_session_send(session);')
391             ->line('}')
392             ->blank
393             ->comment('HTTP/2: Data source read callback for response body')
394             ->line('static ssize_t h2_data_source_read_cb(nghttp2_session* session,')
395             ->line(' int32_t stream_id,')
396             ->line(' uint8_t* buf, size_t length,')
397             ->line(' uint32_t* data_flags,')
398             ->line(' nghttp2_data_source* source,')
399             ->line(' void* user_data) {')
400             ->line(' (void)session; (void)stream_id; (void)user_data;')
401             ->line(' const char* body = (const char*)source->ptr;')
402             ->line(' size_t body_len = strlen(body);')
403             ->line(' ')
404             ->line(' if (body_len == 0) {')
405             ->line(' *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
406             ->line(' return 0;')
407             ->line(' }')
408             ->line(' ')
409             ->line(' size_t copy_len = (length < body_len) ? length : body_len;')
410             ->line(' memcpy(buf, body, copy_len);')
411             ->line(' ')
412             ->line(' /* Update source pointer for next read */')
413             ->line(' source->ptr = (void*)(body + copy_len);')
414             ->line(' ')
415             ->line(' if (copy_len == body_len) {')
416             ->line(' *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
417             ->line(' }')
418             ->line(' ')
419             ->line(' return copy_len;')
420             ->line('}')
421             ->blank;
422            
423 0           return $builder;
424             }
425              
426             # Generate 404 response for HTTP/2
427             sub gen_404_response {
428 0     0 0   my ($class, $builder) = @_;
429            
430 0           $builder->comment('HTTP/2: Send 404 response')
431             ->line('static void h2_send_404(nghttp2_session* session, int32_t stream_id) {')
432             ->line(' static const char* body = "Not Found";')
433             ->line(' ')
434             ->line(' nghttp2_nv hdrs[] = {')
435             ->line(' { (uint8_t*)":status", (uint8_t*)"404", 7, 3, NGHTTP2_NV_FLAG_NONE },')
436             ->line(' { (uint8_t*)"content-type", (uint8_t*)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE },')
437             ->line(' { (uint8_t*)"content-length", (uint8_t*)"9", 14, 1, NGHTTP2_NV_FLAG_NONE },')
438             ->line(' };')
439             ->line(' ')
440             ->line(' nghttp2_data_provider data_prd;')
441             ->line(' data_prd.source.ptr = (void*)body;')
442             ->line(' data_prd.read_callback = h2_data_source_read_cb;')
443             ->line(' ')
444             ->line(' nghttp2_submit_response(session, stream_id, hdrs, 3, &data_prd);')
445             ->line(' nghttp2_session_send(session);')
446             ->line('}')
447             ->blank;
448            
449 0           return $builder;
450             }
451              
452             # Generate HTTP/2 input processing
453             sub gen_input_processor {
454 0     0 0   my ($class, $builder) = @_;
455            
456 0           $builder->comment('HTTP/2: Process incoming data from socket')
457             ->line('static int h2_process_input(H2Connection* conn, const uint8_t* data, size_t len) {')
458             ->line(' ssize_t rv = nghttp2_session_mem_recv(conn->session, data, len);')
459             ->line(' if (rv < 0) {')
460             ->line(' return -1; /* Protocol error */')
461             ->line(' }')
462             ->line(' ')
463             ->line(' /* Send any pending frames */')
464             ->line(' rv = nghttp2_session_send(conn->session);')
465             ->line(' if (rv != 0) {')
466             ->line(' return -1;')
467             ->line(' }')
468             ->line(' ')
469             ->line(' return 0;')
470             ->line('}')
471             ->blank;
472            
473 0           return $builder;
474             }
475              
476             # Generate HTTP/2 connection detection (for h2c upgrade or ALPN)
477             sub gen_connection_preface_check {
478 0     0 0   my ($class, $builder) = @_;
479            
480 0           $builder->comment('HTTP/2: Check for connection preface (h2c)')
481             ->line('static const char H2_PREFACE[] = "PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n";')
482             ->line('#define H2_PREFACE_LEN 24')
483             ->blank
484             ->line('static int is_h2_preface(const char* data, size_t len) {')
485             ->line(' return len >= H2_PREFACE_LEN && memcmp(data, H2_PREFACE, H2_PREFACE_LEN) == 0;')
486             ->line('}')
487             ->blank;
488            
489 0           return $builder;
490             }
491              
492             # ============================================================
493             # HTTP/2 Streaming Support (Phase 3)
494             # ============================================================
495              
496             # Generate streaming headers without END_STREAM
497             sub gen_stream_headers {
498 0     0 0   my ($class, $builder) = @_;
499            
500 0           $builder->comment('HTTP/2 Streaming: Send HEADERS without END_STREAM (allows more DATA)')
501             ->line('static int h2_stream_headers(nghttp2_session* session, int32_t stream_id,')
502             ->line(' int status, const char* content_type) {')
503             ->line(' char status_str[4];')
504             ->line(' snprintf(status_str, sizeof(status_str), "%d", status);')
505             ->line(' ')
506             ->line(' nghttp2_nv hdrs[] = {')
507             ->line(' { (uint8_t*)":status", (uint8_t*)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE },')
508             ->line(' { (uint8_t*)"content-type", (uint8_t*)content_type, 12, strlen(content_type), NGHTTP2_NV_FLAG_NONE },')
509             ->line(' };')
510             ->line(' ')
511             ->line(' /* Submit headers WITHOUT END_STREAM - more DATA frames to come */')
512             ->line(' int rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_END_HEADERS,')
513             ->line(' stream_id, NULL, hdrs, 2, NULL);')
514             ->line(' if (rv < 0) return rv;')
515             ->line(' ')
516             ->line(' return nghttp2_session_send(session);')
517             ->line('}')
518             ->blank;
519            
520 0           return $builder;
521             }
522              
523             # Generate streaming data chunk sender
524             sub gen_stream_data {
525 0     0 0   my ($class, $builder) = @_;
526            
527 0           $builder->comment('HTTP/2 Streaming: Chunk provider for streaming DATA frames')
528             ->line('typedef struct {')
529             ->line(' const uint8_t* data;')
530             ->line(' size_t length;')
531             ->line(' size_t pos;')
532             ->line('} H2ChunkProvider;')
533             ->blank
534             ->line('static ssize_t h2_chunk_read_cb(nghttp2_session* session,')
535             ->line(' int32_t stream_id,')
536             ->line(' uint8_t* buf, size_t length,')
537             ->line(' uint32_t* data_flags,')
538             ->line(' nghttp2_data_source* source,')
539             ->line(' void* user_data) {')
540             ->line(' (void)session; (void)stream_id; (void)user_data;')
541             ->line(' H2ChunkProvider* provider = (H2ChunkProvider*)source->ptr;')
542             ->line(' size_t remaining = provider->length - provider->pos;')
543             ->line(' size_t to_copy = remaining < length ? remaining : length;')
544             ->line(' ')
545             ->line(' memcpy(buf, provider->data + provider->pos, to_copy);')
546             ->line(' provider->pos += to_copy;')
547             ->line(' ')
548             ->line(' if (provider->pos >= provider->length) {')
549             ->line(' /* This DATA frame is complete (but not end of stream) */')
550             ->line(' *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
551             ->line(' }')
552             ->line(' ')
553             ->line(' return (ssize_t)to_copy;')
554             ->line('}')
555             ->blank
556             ->comment('HTTP/2 Streaming: Send a single DATA frame (not final)')
557             ->line('static int h2_stream_data(nghttp2_session* session, int32_t stream_id,')
558             ->line(' const uint8_t* data, size_t len) {')
559             ->line(' /* Allocate provider on stack - nghttp2 copies data synchronously */')
560             ->line(' H2ChunkProvider provider = { data, len, 0 };')
561             ->line(' ')
562             ->line(' nghttp2_data_provider data_prd;')
563             ->line(' data_prd.source.ptr = &provider;')
564             ->line(' data_prd.read_callback = h2_chunk_read_cb;')
565             ->line(' ')
566             ->line(' /* Submit DATA without END_STREAM */')
567             ->line(' int rv = nghttp2_submit_data(session, NGHTTP2_FLAG_NONE,')
568             ->line(' stream_id, &data_prd);')
569             ->line(' if (rv < 0) return rv;')
570             ->line(' ')
571             ->line(' return nghttp2_session_send(session);')
572             ->line('}')
573             ->blank;
574            
575 0           return $builder;
576             }
577              
578             # Generate stream end (empty DATA with END_STREAM)
579             sub gen_stream_end {
580 0     0 0   my ($class, $builder) = @_;
581            
582 0           $builder->comment('HTTP/2 Streaming: Send empty DATA with END_STREAM flag')
583             ->line('static int h2_stream_end(nghttp2_session* session, int32_t stream_id) {')
584             ->line(' /* Submit empty data with END_STREAM flag */')
585             ->line(' nghttp2_data_provider data_prd;')
586             ->line(' data_prd.source.ptr = NULL;')
587             ->line(' data_prd.read_callback = NULL;')
588             ->line(' ')
589             ->line(' int rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,')
590             ->line(' stream_id, NULL);')
591             ->line(' if (rv < 0) return rv;')
592             ->line(' ')
593             ->line(' return nghttp2_session_send(session);')
594             ->line('}')
595             ->blank;
596            
597 0           return $builder;
598             }
599              
600             # Generate flow control helpers
601             sub gen_flow_control {
602 0     0 0   my ($class, $builder) = @_;
603            
604 0           $builder->comment('HTTP/2 Streaming: Check flow control window')
605             ->line('static int h2_can_send(nghttp2_session* session, int32_t stream_id, size_t len) {')
606             ->line(' /* Check connection-level window */')
607             ->line(' int32_t conn_window = nghttp2_session_get_remote_window_size(session);')
608             ->line(' if (conn_window < (int32_t)len) return 0;')
609             ->line(' ')
610             ->line(' /* Check stream-level window */')
611             ->line(' int32_t stream_window = nghttp2_session_get_stream_remote_window_size(')
612             ->line(' session, stream_id);')
613             ->line(' if (stream_window < (int32_t)len) return 0;')
614             ->line(' ')
615             ->line(' return 1;')
616             ->line('}')
617             ->blank
618             ->comment('HTTP/2 Streaming: Get available window size')
619             ->line('static int32_t h2_window_size(nghttp2_session* session, int32_t stream_id) {')
620             ->line(' int32_t conn_window = nghttp2_session_get_remote_window_size(session);')
621             ->line(' int32_t stream_window = nghttp2_session_get_stream_remote_window_size(')
622             ->line(' session, stream_id);')
623             ->line(' return conn_window < stream_window ? conn_window : stream_window;')
624             ->line('}')
625             ->blank;
626            
627 0           return $builder;
628             }
629              
630             # Generate XS wrappers for HTTP/2 streaming from Perl
631             sub gen_stream_xs_wrappers {
632 0     0 0   my ($class, $builder) = @_;
633            
634 0           $builder->comment('XS wrappers for HTTP/2 streaming from Perl');
635            
636             # h2_stream_start(session_ptr, stream_id, status, content_type)
637 0           $builder->xs_function('hypersonic_h2_stream_start')
638             ->xs_preamble
639             ->check_items(4, 4, 'session_ptr, stream_id, status, content_type')
640             ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
641             ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
642             ->line('int status = (int)SvIV(ST(2));')
643             ->line('STRLEN ct_len;')
644             ->line('const char* content_type = SvPV(ST(3), ct_len);')
645             ->line('int rv = h2_stream_headers(session, stream_id, status, content_type);')
646             ->line('XSRETURN_IV(rv);')
647             ->xs_end
648             ->blank;
649            
650             # h2_stream_write(session_ptr, stream_id, data)
651 0           $builder->xs_function('hypersonic_h2_stream_write')
652             ->xs_preamble
653             ->check_items(3, 3, 'session_ptr, stream_id, data')
654             ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
655             ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
656             ->line('STRLEN data_len;')
657             ->line('const char* data = SvPV(ST(2), data_len);')
658             ->line('int rv = h2_stream_data(session, stream_id, (const uint8_t*)data, data_len);')
659             ->line('XSRETURN_IV(rv);')
660             ->xs_end
661             ->blank;
662            
663             # h2_stream_end(session_ptr, stream_id)
664 0           $builder->xs_function('hypersonic_h2_stream_end')
665             ->xs_preamble
666             ->check_items(2, 2, 'session_ptr, stream_id')
667             ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
668             ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
669             ->line('int rv = h2_stream_end(session, stream_id);')
670             ->line('XSRETURN_IV(rv);')
671             ->xs_end
672             ->blank;
673            
674 0           return $builder;
675             }
676              
677             # Generate all HTTP/2 streaming code
678             sub generate_streaming {
679 0     0 0   my ($class, $builder, $opts) = @_;
680            
681 0           $class->gen_stream_headers($builder);
682 0           $class->gen_stream_data($builder);
683 0           $class->gen_stream_end($builder);
684 0           $class->gen_flow_control($builder);
685 0           $class->gen_stream_xs_wrappers($builder);
686            
687 0           return $builder;
688             }
689              
690             1;
691              
692             __END__