File Coverage

nghttp2.xs
Criterion Covered Total %
statement 402 578 69.5
branch 198 468 42.3
condition n/a
subroutine n/a
pod n/a
total 600 1046 57.3


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5              
6             #include
7             #include
8              
9             /*
10             * Net::HTTP2::nghttp2 - Perl XS bindings for nghttp2
11             *
12             * This module provides server-side HTTP/2 support via nghttp2.
13             */
14              
15             /* Per-stream data provider state for streaming responses */
16             typedef struct {
17             SV *callback; /* Perl callback to produce data */
18             SV *user_data; /* User data for callback */
19             int32_t stream_id; /* Stream ID */
20             int eof; /* End of data flag */
21             int deferred; /* Currently deferred */
22             } nghttp2_perl_data_provider;
23              
24             /* Session wrapper structure */
25             typedef struct {
26             nghttp2_session *session;
27             SV *user_data;
28             SV *cb_on_begin_headers;
29             SV *cb_on_header;
30             SV *cb_on_frame_recv;
31             SV *cb_on_data_chunk_recv;
32             SV *cb_on_stream_close;
33             SV *cb_send;
34             SV *cb_data_source_read;
35             /* Output buffer for mem_send */
36             char *send_buf;
37             size_t send_buf_len;
38             size_t send_buf_cap;
39             /* Data providers for active streams (simple linear array) */
40             nghttp2_perl_data_provider **data_providers;
41             int data_providers_count;
42             int data_providers_cap;
43             } nghttp2_perl_session;
44              
45             /* Forward declarations */
46             static ssize_t perl_send_callback(nghttp2_session *session,
47             const uint8_t *data, size_t length,
48             int flags, void *user_data);
49             static int perl_on_begin_headers_callback(nghttp2_session *session,
50             const nghttp2_frame *frame,
51             void *user_data);
52             static int perl_on_header_callback(nghttp2_session *session,
53             const nghttp2_frame *frame,
54             const uint8_t *name, size_t namelen,
55             const uint8_t *value, size_t valuelen,
56             uint8_t flags, void *user_data);
57             static int perl_on_frame_recv_callback(nghttp2_session *session,
58             const nghttp2_frame *frame,
59             void *user_data);
60             static int perl_on_data_chunk_recv_callback(nghttp2_session *session,
61             uint8_t flags, int32_t stream_id,
62             const uint8_t *data, size_t len,
63             void *user_data);
64             static int perl_on_stream_close_callback(nghttp2_session *session,
65             int32_t stream_id,
66             uint32_t error_code,
67             void *user_data);
68              
69             /* Data provider helper functions */
70 6           static nghttp2_perl_data_provider *find_data_provider(nghttp2_perl_session *ps, int32_t stream_id) {
71             int i;
72 6 50         for (i = 0; i < ps->data_providers_count; i++) {
73 6 50         if (ps->data_providers[i] && ps->data_providers[i]->stream_id == stream_id) {
    50          
74 6           return ps->data_providers[i];
75             }
76             }
77 0           return NULL;
78             }
79              
80 8           static void add_data_provider(nghttp2_perl_session *ps, nghttp2_perl_data_provider *dp) {
81             int i;
82             /* Find empty slot */
83 8 50         for (i = 0; i < ps->data_providers_count; i++) {
84 0 0         if (!ps->data_providers[i]) {
85 0           ps->data_providers[i] = dp;
86 0           return;
87             }
88             }
89             /* Grow array if needed */
90 8 50         if (ps->data_providers_count >= ps->data_providers_cap) {
91 8 50         int new_cap = ps->data_providers_cap ? ps->data_providers_cap * 2 : 8;
92 8           ps->data_providers = (nghttp2_perl_data_provider **)realloc(
93 8           ps->data_providers, new_cap * sizeof(nghttp2_perl_data_provider *));
94 8           memset(ps->data_providers + ps->data_providers_cap, 0,
95 8           (new_cap - ps->data_providers_cap) * sizeof(nghttp2_perl_data_provider *));
96 8           ps->data_providers_cap = new_cap;
97             }
98 8           ps->data_providers[ps->data_providers_count++] = dp;
99             }
100              
101 12           static void remove_data_provider(nghttp2_perl_session *ps, int32_t stream_id) {
102             dTHX;
103             int i;
104 12 100         for (i = 0; i < ps->data_providers_count; i++) {
105 2 50         if (ps->data_providers[i] && ps->data_providers[i]->stream_id == stream_id) {
    50          
106 2           nghttp2_perl_data_provider *dp = ps->data_providers[i];
107 2 100         if (dp->callback) SvREFCNT_dec(dp->callback);
108 2 50         if (dp->user_data) SvREFCNT_dec(dp->user_data);
109 2           Safefree(dp);
110 2           ps->data_providers[i] = NULL;
111 2           return;
112             }
113             }
114             }
115              
116             /* Data provider read callback - called by nghttp2 when it wants response body data */
117 16           static ssize_t perl_data_source_read_callback(
118             nghttp2_session *session,
119             int32_t stream_id,
120             uint8_t *buf,
121             size_t length,
122             uint32_t *data_flags,
123             nghttp2_data_source *source,
124             void *user_data)
125             {
126             dTHX;
127 16           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
128 16           nghttp2_perl_data_provider *dp = (nghttp2_perl_data_provider *)source->ptr;
129 16           dSP;
130             int count;
131 16           ssize_t ret = 0;
132              
133 16 50         if (!dp) {
134 0           *data_flags |= NGHTTP2_DATA_FLAG_EOF;
135 0           return 0;
136             }
137              
138             /* Special case: if callback is NULL but user_data contains body, use it directly */
139 16 100         if (!dp->callback && dp->user_data && SvOK(dp->user_data)) {
    100          
    50          
140             STRLEN full_len;
141 6           const char *body_ptr = SvPVbyte(dp->user_data, full_len);
142 6           STRLEN send_len = (full_len > length) ? length : full_len;
143              
144 6 50         if (send_len > 0) {
145 6           memcpy(buf, body_ptr, send_len);
146             }
147              
148 6 50         if (send_len >= full_len) {
149             /* All data consumed */
150 6           SvREFCNT_dec(dp->user_data);
151 6           dp->user_data = NULL;
152 6 100         if (dp->eof) {
153 5           *data_flags |= NGHTTP2_DATA_FLAG_EOF;
154             }
155             } else {
156             /* Partial send - keep remainder for next call */
157 0           SV *remaining = newSVpvn(body_ptr + send_len, full_len - send_len);
158 0           SvREFCNT_dec(dp->user_data);
159 0           dp->user_data = remaining;
160             }
161              
162 6           return (ssize_t)send_len;
163             }
164              
165             /* No callback and no pending data */
166 10 100         if (!dp->callback || !SvOK(dp->callback)) {
    50          
167 1 50         if (dp->eof) {
168 0           *data_flags |= NGHTTP2_DATA_FLAG_EOF;
169 0           return 0;
170             }
171             /* No EOF requested - defer until more data arrives via submit_data */
172 1           dp->deferred = 1;
173 1           return NGHTTP2_ERR_DEFERRED;
174             }
175              
176 9           ENTER;
177 9           SAVETMPS;
178 9 50         PUSHMARK(SP);
179              
180             /* Call: $callback->($stream_id, $max_length) */
181             /* Returns: ($data, $eof) or undef for deferred */
182 9 50         XPUSHs(sv_2mortal(newSViv(stream_id)));
183 9 50         XPUSHs(sv_2mortal(newSVuv(length)));
184 9 50         if (dp->user_data && SvOK(dp->user_data)) {
    0          
185 0 0         XPUSHs(dp->user_data);
186             }
187              
188 9           PUTBACK;
189 9           count = call_sv(dp->callback, G_ARRAY | G_EVAL);
190 9           SPAGAIN;
191              
192 9 50         if (SvTRUE(ERRSV)) {
    50          
193             /* Callback threw an exception */
194 0 0         warn("nghttp2 data provider callback error: %s", SvPV_nolen(ERRSV));
195 0           ret = NGHTTP2_ERR_CALLBACK_FAILURE;
196 9 50         } else if (count == 0) {
197             /* No return value = defer */
198 0           dp->deferred = 1;
199 0           ret = NGHTTP2_ERR_DEFERRED;
200 9 50         } else if (count >= 1) {
201 9           SV *eof_sv = NULL;
202 9           SV *data_sv = NULL;
203              
204 9 100         if (count >= 2) {
205 3           eof_sv = POPs;
206             }
207 9           data_sv = POPs;
208              
209 9 100         if (!SvOK(data_sv)) {
210             /* undef = defer */
211 6           dp->deferred = 1;
212 6           ret = NGHTTP2_ERR_DEFERRED;
213             } else {
214             STRLEN data_len;
215 3           const char *data_ptr = SvPVbyte(data_sv, data_len);
216              
217             /* Copy data to buffer */
218 3 50         if (data_len > length) {
219 0           data_len = length; /* Truncate if too much */
220             }
221 3 50         if (data_len > 0) {
222 3           memcpy(buf, data_ptr, data_len);
223             }
224 3           ret = (ssize_t)data_len;
225              
226             /* Check EOF flag */
227 3 50         if (eof_sv && SvTRUE(eof_sv)) {
    100          
228 2           *data_flags |= NGHTTP2_DATA_FLAG_EOF;
229 2           dp->eof = 1;
230             }
231             /* If returned empty string with no eof, also defer */
232 3 50         if (data_len == 0 && !dp->eof) {
    0          
233 0           dp->deferred = 1;
234 0           ret = NGHTTP2_ERR_DEFERRED;
235             }
236             }
237             }
238              
239 9           PUTBACK;
240 9 50         FREETMPS;
241 9           LEAVE;
242              
243 9           return ret;
244             }
245              
246             /* Helper to call Perl callbacks */
247 577           static int call_perl_callback(pTHX_ SV *callback, AV *args) {
248 577           dSP;
249             int count;
250 577           int ret = 0;
251              
252 577 50         if (!callback || !SvOK(callback)) {
    50          
253 0           return 0;
254             }
255              
256 577           ENTER;
257 577           SAVETMPS;
258 577 50         PUSHMARK(SP);
259              
260 577 50         if (args) {
261             int i;
262 577           int len = av_len(args) + 1;
263 2133 100         for (i = 0; i < len; i++) {
264 1556           SV **elem = av_fetch(args, i, 0);
265 1556 50         if (elem) {
266 1556 50         XPUSHs(*elem);
267             }
268             }
269             }
270              
271 577           PUTBACK;
272 577           count = call_sv(callback, G_SCALAR | G_EVAL);
273 577           SPAGAIN;
274              
275 577 50         if (SvTRUE(ERRSV)) {
    50          
276             /* Callback threw an exception */
277 0 0         warn("nghttp2 callback error: %s", SvPV_nolen(ERRSV));
278 0           ret = NGHTTP2_ERR_CALLBACK_FAILURE;
279 577 50         } else if (count > 0) {
280 577           SV *result = POPs;
281 577 50         if (SvIOK(result)) {
282 577           ret = SvIV(result);
283             }
284             }
285              
286 577           PUTBACK;
287 577 50         FREETMPS;
288 577           LEAVE;
289              
290 577           return ret;
291             }
292              
293             /* Send callback - buffers data for mem_send */
294 261           static ssize_t perl_send_callback(nghttp2_session *session,
295             const uint8_t *data, size_t length,
296             int flags, void *user_data) {
297 261           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
298              
299             /* Grow buffer if needed */
300 261 50         if (ps->send_buf_len + length > ps->send_buf_cap) {
301 0           size_t new_cap = ps->send_buf_cap * 2;
302 0 0         if (new_cap < ps->send_buf_len + length) {
303 0           new_cap = ps->send_buf_len + length + 16384;
304             }
305 0           ps->send_buf = (char *)realloc(ps->send_buf, new_cap);
306 0 0         if (!ps->send_buf) {
307 0           return NGHTTP2_ERR_NOMEM;
308             }
309 0           ps->send_buf_cap = new_cap;
310             }
311              
312 261           memcpy(ps->send_buf + ps->send_buf_len, data, length);
313 261           ps->send_buf_len += length;
314              
315 261           return (ssize_t)length;
316             }
317              
318             /* Begin headers callback */
319 69           static int perl_on_begin_headers_callback(nghttp2_session *session,
320             const nghttp2_frame *frame,
321             void *user_data) {
322             dTHX;
323 69           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
324             AV *args;
325             int ret;
326              
327 69 50         if (!ps->cb_on_begin_headers || !SvOK(ps->cb_on_begin_headers)) {
    50          
328 0           return 0;
329             }
330              
331 69           args = newAV();
332 69           av_push(args, newSViv(frame->hd.stream_id));
333 69           av_push(args, newSViv(frame->hd.type));
334 69           av_push(args, newSViv(frame->hd.flags));
335              
336 69           ret = call_perl_callback(aTHX_ ps->cb_on_begin_headers, args);
337              
338 69           SvREFCNT_dec((SV *)args);
339 69           return ret;
340             }
341              
342             /* Header callback */
343 261           static int perl_on_header_callback(nghttp2_session *session,
344             const nghttp2_frame *frame,
345             const uint8_t *name, size_t namelen,
346             const uint8_t *value, size_t valuelen,
347             uint8_t flags, void *user_data) {
348             dTHX;
349 261           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
350             AV *args;
351             int ret;
352              
353 261 50         if (!ps->cb_on_header || !SvOK(ps->cb_on_header)) {
    50          
354 0           return 0;
355             }
356              
357 261           args = newAV();
358 261           av_push(args, newSViv(frame->hd.stream_id));
359 261           av_push(args, newSVpvn((const char *)name, namelen));
360 261           av_push(args, newSVpvn((const char *)value, valuelen));
361 261           av_push(args, newSViv(flags));
362              
363 261           ret = call_perl_callback(aTHX_ ps->cb_on_header, args);
364              
365 261           SvREFCNT_dec((SV *)args);
366 261           return ret;
367             }
368              
369             /* Frame receive callback */
370 214           static int perl_on_frame_recv_callback(nghttp2_session *session,
371             const nghttp2_frame *frame,
372             void *user_data) {
373             dTHX;
374 214           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
375             AV *args;
376             HV *frame_hv;
377             int ret;
378              
379 214 50         if (!ps->cb_on_frame_recv || !SvOK(ps->cb_on_frame_recv)) {
    50          
380 0           return 0;
381             }
382              
383             /* Build frame info hash */
384 214           frame_hv = newHV();
385 214           hv_store(frame_hv, "stream_id", 9, newSViv(frame->hd.stream_id), 0);
386 214           hv_store(frame_hv, "type", 4, newSViv(frame->hd.type), 0);
387 214           hv_store(frame_hv, "flags", 5, newSViv(frame->hd.flags), 0);
388 214           hv_store(frame_hv, "length", 6, newSViv(frame->hd.length), 0);
389              
390 214           args = newAV();
391 214           av_push(args, newRV_noinc((SV *)frame_hv));
392              
393 214           ret = call_perl_callback(aTHX_ ps->cb_on_frame_recv, args);
394              
395 214           SvREFCNT_dec((SV *)args);
396 214           return ret;
397             }
398              
399             /* Data chunk receive callback */
400 25           static int perl_on_data_chunk_recv_callback(nghttp2_session *session,
401             uint8_t flags, int32_t stream_id,
402             const uint8_t *data, size_t len,
403             void *user_data) {
404             dTHX;
405 25           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
406             AV *args;
407             int ret;
408              
409 25 50         if (!ps->cb_on_data_chunk_recv || !SvOK(ps->cb_on_data_chunk_recv)) {
    50          
410 0           return 0;
411             }
412              
413 25           args = newAV();
414 25           av_push(args, newSViv(stream_id));
415 25           av_push(args, newSVpvn((const char *)data, len));
416 25           av_push(args, newSViv(flags));
417              
418 25           ret = call_perl_callback(aTHX_ ps->cb_on_data_chunk_recv, args);
419              
420 25           SvREFCNT_dec((SV *)args);
421 25           return ret;
422             }
423              
424             /* Stream close callback */
425 12           static int perl_on_stream_close_callback(nghttp2_session *session,
426             int32_t stream_id,
427             uint32_t error_code,
428             void *user_data) {
429             dTHX;
430 12           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
431             AV *args;
432 12           int ret = 0;
433              
434             /* Clean up any data provider for this stream */
435 12           remove_data_provider(ps, stream_id);
436              
437 12 100         if (!ps->cb_on_stream_close || !SvOK(ps->cb_on_stream_close)) {
    50          
438 4           return 0;
439             }
440              
441 8           args = newAV();
442 8           av_push(args, newSViv(stream_id));
443 8           av_push(args, newSVuv(error_code));
444              
445 8           ret = call_perl_callback(aTHX_ ps->cb_on_stream_close, args);
446              
447 8           SvREFCNT_dec((SV *)args);
448 8           return ret;
449             }
450              
451             MODULE = Net::HTTP2::nghttp2 PACKAGE = Net::HTTP2::nghttp2
452              
453             PROTOTYPES: DISABLE
454              
455             # Check if nghttp2 is available
456             int
457             _check_nghttp2_available()
458             CODE:
459 14           nghttp2_info *info = nghttp2_version(0);
460 14 50         RETVAL = info ? 1 : 0;
461             OUTPUT:
462             RETVAL
463              
464             # Get nghttp2 version string
465             const char *
466             version_string()
467             CODE:
468 2           nghttp2_info *info = nghttp2_version(0);
469 2 50         RETVAL = info ? info->version_str : "unknown";
470             OUTPUT:
471             RETVAL
472              
473             # Get nghttp2 version number
474             int
475             version_num()
476             CODE:
477 2           nghttp2_info *info = nghttp2_version(0);
478 2 50         RETVAL = info ? info->version_num : 0;
    50          
479             OUTPUT:
480             RETVAL
481              
482             # Constants
483             int
484             NGHTTP2_ERR_WOULDBLOCK()
485             CODE:
486 0 0         RETVAL = NGHTTP2_ERR_WOULDBLOCK;
487             OUTPUT:
488             RETVAL
489              
490             int
491             NGHTTP2_ERR_CALLBACK_FAILURE()
492             CODE:
493 0 0         RETVAL = NGHTTP2_ERR_CALLBACK_FAILURE;
494             OUTPUT:
495             RETVAL
496              
497             int
498             NGHTTP2_ERR_DEFERRED()
499             CODE:
500 0 0         RETVAL = NGHTTP2_ERR_DEFERRED;
501             OUTPUT:
502             RETVAL
503              
504             int
505             NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE()
506             CODE:
507 2 50         RETVAL = NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
508             OUTPUT:
509             RETVAL
510              
511             int
512             NGHTTP2_FLAG_NONE()
513             CODE:
514 0 0         RETVAL = NGHTTP2_FLAG_NONE;
515             OUTPUT:
516             RETVAL
517              
518             int
519             NGHTTP2_FLAG_END_STREAM()
520             CODE:
521 1 50         RETVAL = NGHTTP2_FLAG_END_STREAM;
522             OUTPUT:
523             RETVAL
524              
525             int
526             NGHTTP2_FLAG_END_HEADERS()
527             CODE:
528 0 0         RETVAL = NGHTTP2_FLAG_END_HEADERS;
529             OUTPUT:
530             RETVAL
531              
532             int
533             NGHTTP2_FLAG_ACK()
534             CODE:
535 0 0         RETVAL = NGHTTP2_FLAG_ACK;
536             OUTPUT:
537             RETVAL
538              
539             int
540             NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS()
541             CODE:
542 0 0         RETVAL = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
543             OUTPUT:
544             RETVAL
545              
546             int
547             NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE()
548             CODE:
549 0 0         RETVAL = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
550             OUTPUT:
551             RETVAL
552              
553             int
554             NGHTTP2_SETTINGS_MAX_FRAME_SIZE()
555             CODE:
556 0 0         RETVAL = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
557             OUTPUT:
558             RETVAL
559              
560             int
561             NGHTTP2_SETTINGS_ENABLE_PUSH()
562             CODE:
563 0 0         RETVAL = NGHTTP2_SETTINGS_ENABLE_PUSH;
564             OUTPUT:
565             RETVAL
566              
567             int
568             NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL()
569             CODE:
570 1 50         RETVAL = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
571             OUTPUT:
572             RETVAL
573              
574             int
575             NGHTTP2_DATA_FLAG_NONE()
576             CODE:
577 0 0         RETVAL = NGHTTP2_DATA_FLAG_NONE;
578             OUTPUT:
579             RETVAL
580              
581             int
582             NGHTTP2_DATA_FLAG_EOF()
583             CODE:
584 0 0         RETVAL = NGHTTP2_DATA_FLAG_EOF;
585             OUTPUT:
586             RETVAL
587              
588             int
589             NGHTTP2_DATA_FLAG_NO_END_STREAM()
590             CODE:
591 0 0         RETVAL = NGHTTP2_DATA_FLAG_NO_END_STREAM;
592             OUTPUT:
593             RETVAL
594              
595             int
596             NGHTTP2_DATA_FLAG_NO_COPY()
597             CODE:
598 0 0         RETVAL = NGHTTP2_DATA_FLAG_NO_COPY;
599             OUTPUT:
600             RETVAL
601              
602             # Frame types
603             int
604             NGHTTP2_DATA()
605             CODE:
606 0 0         RETVAL = NGHTTP2_DATA;
607             OUTPUT:
608             RETVAL
609              
610             int
611             NGHTTP2_HEADERS()
612             CODE:
613 0 0         RETVAL = NGHTTP2_HEADERS;
614             OUTPUT:
615             RETVAL
616              
617             int
618             NGHTTP2_SETTINGS()
619             CODE:
620 0 0         RETVAL = NGHTTP2_SETTINGS;
621             OUTPUT:
622             RETVAL
623              
624             int
625             NGHTTP2_PUSH_PROMISE()
626             CODE:
627 0 0         RETVAL = NGHTTP2_PUSH_PROMISE;
628             OUTPUT:
629             RETVAL
630              
631             int
632             NGHTTP2_GOAWAY()
633             CODE:
634 0 0         RETVAL = NGHTTP2_GOAWAY;
635             OUTPUT:
636             RETVAL
637              
638              
639             MODULE = Net::HTTP2::nghttp2 PACKAGE = Net::HTTP2::nghttp2::Session
640              
641             # Create new server session
642             SV *
643             _new_server_xs(class, callbacks_hv, user_data, ...)
644             char *class
645             HV *callbacks_hv
646             SV *user_data
647             PREINIT:
648             nghttp2_perl_session *ps;
649             nghttp2_session_callbacks *callbacks;
650 98           nghttp2_option *option = NULL;
651             int rv;
652             SV **svp;
653 98           HV *options_hv = NULL;
654             CODE:
655             /* Check for optional options hash (4th argument) */
656 98 100         if (items > 3 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVHV) {
    50          
    50          
657 1           options_hv = (HV *)SvRV(ST(3));
658             }
659              
660             /* Allocate our wrapper structure */
661 98           Newxz(ps, 1, nghttp2_perl_session);
662              
663             /* Initialize send buffer */
664 98           ps->send_buf_cap = 16384;
665 98           ps->send_buf = (char *)malloc(ps->send_buf_cap);
666 98           ps->send_buf_len = 0;
667              
668             /* Store user data */
669 98 50         if (SvOK(user_data)) {
670 0           ps->user_data = newSVsv(user_data);
671             }
672              
673             /* Extract callbacks from hash */
674 98 50         if ((svp = hv_fetch(callbacks_hv, "on_begin_headers", 16, 0))) {
675 98           ps->cb_on_begin_headers = newSVsv(*svp);
676             }
677 98 50         if ((svp = hv_fetch(callbacks_hv, "on_header", 9, 0))) {
678 98           ps->cb_on_header = newSVsv(*svp);
679             }
680 98 50         if ((svp = hv_fetch(callbacks_hv, "on_frame_recv", 13, 0))) {
681 98           ps->cb_on_frame_recv = newSVsv(*svp);
682             }
683 98 100         if ((svp = hv_fetch(callbacks_hv, "on_data_chunk_recv", 18, 0))) {
684 21           ps->cb_on_data_chunk_recv = newSVsv(*svp);
685             }
686 98 100         if ((svp = hv_fetch(callbacks_hv, "on_stream_close", 15, 0))) {
687 14           ps->cb_on_stream_close = newSVsv(*svp);
688             }
689              
690             /* Create nghttp2 callbacks */
691 98           nghttp2_session_callbacks_new(&callbacks);
692 98           nghttp2_session_callbacks_set_send_callback(callbacks, perl_send_callback);
693 98           nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, perl_on_begin_headers_callback);
694 98           nghttp2_session_callbacks_set_on_header_callback(callbacks, perl_on_header_callback);
695 98           nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, perl_on_frame_recv_callback);
696 98           nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, perl_on_data_chunk_recv_callback);
697 98           nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, perl_on_stream_close_callback);
698              
699             /* Create session — use new2 with options if provided */
700 98 100         if (options_hv) {
701 1           rv = nghttp2_option_new(&option);
702 1 50         if (rv != 0) {
703 0           nghttp2_session_callbacks_del(callbacks);
704 0 0         if (ps->user_data) SvREFCNT_dec(ps->user_data);
705 0 0         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
706 0 0         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
707 0 0         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
708 0 0         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
709 0 0         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
710 0           free(ps->send_buf);
711 0           Safefree(ps);
712 0           croak("nghttp2_option_new failed: %s", nghttp2_strerror(rv));
713             }
714              
715 1 50         if ((svp = hv_fetch(options_hv, "max_send_header_block_length", 28, 0))) {
716 1           nghttp2_option_set_max_send_header_block_length(option, SvUV(*svp));
717             }
718              
719 1           rv = nghttp2_session_server_new2(&ps->session, callbacks, ps, option);
720 1           nghttp2_option_del(option);
721             } else {
722 97           rv = nghttp2_session_server_new(&ps->session, callbacks, ps);
723             }
724 98           nghttp2_session_callbacks_del(callbacks);
725              
726 98 50         if (rv != 0) {
727 0 0         if (ps->user_data) SvREFCNT_dec(ps->user_data);
728 0 0         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
729 0 0         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
730 0 0         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
731 0 0         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
732 0 0         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
733 0           free(ps->send_buf);
734 0           Safefree(ps);
735 0           croak("nghttp2_session_server_new failed: %s", nghttp2_strerror(rv));
736             }
737              
738             /* Bless and return */
739 98           RETVAL = sv_newmortal();
740 98           sv_setref_pv(RETVAL, class, (void *)ps);
741 98           SvREFCNT_inc(RETVAL);
742             OUTPUT:
743             RETVAL
744              
745             # Destructor
746             void
747             DESTROY(self)
748             SV *self
749             PREINIT:
750             nghttp2_perl_session *ps;
751             CODE:
752 120           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
753 120 50         if (ps) {
754             int i;
755 120 50         if (ps->session) {
756 120           nghttp2_session_del(ps->session);
757             }
758 120 50         if (ps->user_data) SvREFCNT_dec(ps->user_data);
759 120 100         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
760 120 100         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
761 120 50         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
762 120 100         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
763 120 100         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
764 120 50         if (ps->send_buf) free(ps->send_buf);
765             /* Clean up data providers */
766 128 100         for (i = 0; i < ps->data_providers_count; i++) {
767 8 100         if (ps->data_providers[i]) {
768 6           nghttp2_perl_data_provider *dp = ps->data_providers[i];
769 6 100         if (dp->callback) SvREFCNT_dec(dp->callback);
770 6 50         if (dp->user_data) SvREFCNT_dec(dp->user_data);
771 6           Safefree(dp);
772             }
773             }
774 120 100         if (ps->data_providers) free(ps->data_providers);
775 120           Safefree(ps);
776             }
777              
778             # Feed incoming data to session
779             int
780             mem_recv(self, data)
781             SV *self
782             SV *data
783             PREINIT:
784             nghttp2_perl_session *ps;
785             STRLEN len;
786             const char *buf;
787             ssize_t rv;
788             CODE:
789 183           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
790 183           buf = SvPVbyte(data, len);
791              
792 183           rv = nghttp2_session_mem_recv(ps->session, (const uint8_t *)buf, len);
793 183 100         if (rv < 0) {
794 1           croak("nghttp2_session_mem_recv failed: %s", nghttp2_strerror((int)rv));
795             }
796 182 100         RETVAL = (int)rv;
797             OUTPUT:
798             RETVAL
799              
800             # Get data to send
801             SV *
802             mem_send(self)
803             SV *self
804             PREINIT:
805             nghttp2_perl_session *ps;
806             int rv;
807             CODE:
808 284           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
809              
810             /* Clear send buffer */
811 284           ps->send_buf_len = 0;
812              
813             /* Trigger send callback to fill buffer */
814 284           rv = nghttp2_session_send(ps->session);
815 284 50         if (rv != 0) {
816 0           croak("nghttp2_session_send failed: %s", nghttp2_strerror(rv));
817             }
818              
819             /* Return buffered data */
820 284 100         if (ps->send_buf_len > 0) {
821 219           RETVAL = newSVpvn(ps->send_buf, ps->send_buf_len);
822             } else {
823 65           RETVAL = newSVpvn("", 0);
824             }
825             OUTPUT:
826             RETVAL
827              
828             # Check if session wants to read
829             int
830             want_read(self)
831             SV *self
832             PREINIT:
833             nghttp2_perl_session *ps;
834             CODE:
835 3           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
836 3           RETVAL = nghttp2_session_want_read(ps->session);
837             OUTPUT:
838             RETVAL
839              
840             # Check if session wants to write
841             int
842             want_write(self)
843             SV *self
844             PREINIT:
845             nghttp2_perl_session *ps;
846             CODE:
847 3           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
848 3           RETVAL = nghttp2_session_want_write(ps->session);
849             OUTPUT:
850             RETVAL
851              
852             # Submit SETTINGS frame
853             int
854             submit_settings(self, settings_hv)
855             SV *self
856             HV *settings_hv
857             PREINIT:
858             nghttp2_perl_session *ps;
859             nghttp2_settings_entry iv[16];
860 123 50         int niv = 0;
861             SV **svp;
862             int rv;
863             CODE:
864 123           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
865              
866 123 100         if ((svp = hv_fetch(settings_hv, "max_concurrent_streams", 22, 0))) {
867 111           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
868 111           iv[niv].value = SvUV(*svp);
869 111           niv++;
870             }
871 123 100         if ((svp = hv_fetch(settings_hv, "initial_window_size", 19, 0))) {
872 109           iv[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
873 109           iv[niv].value = SvUV(*svp);
874 109           niv++;
875             }
876 123 50         if ((svp = hv_fetch(settings_hv, "max_frame_size", 14, 0))) {
877 0           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
878 0           iv[niv].value = SvUV(*svp);
879 0           niv++;
880             }
881 123 50         if ((svp = hv_fetch(settings_hv, "enable_push", 11, 0))) {
882 0           iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
883 0           iv[niv].value = SvTRUE(*svp) ? 1 : 0;
884 0           niv++;
885             }
886 123 100         if ((svp = hv_fetch(settings_hv, "enable_connect_protocol", 23, 0))) {
887 10           iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
888 10           iv[niv].value = SvTRUE(*svp) ? 1 : 0;
889 10           niv++;
890             }
891 123 100         if ((svp = hv_fetch(settings_hv, "header_table_size", 17, 0))) {
892 1           iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
893 1           iv[niv].value = SvUV(*svp);
894 1           niv++;
895             }
896 123 100         if ((svp = hv_fetch(settings_hv, "max_header_list_size", 20, 0))) {
897 1           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
898 1           iv[niv].value = SvUV(*svp);
899 1           niv++;
900             }
901              
902 123           rv = nghttp2_submit_settings(ps->session, NGHTTP2_FLAG_NONE, iv, niv);
903 123 50         if (rv != 0) {
904 0           croak("nghttp2_submit_settings failed: %s", nghttp2_strerror(rv));
905             }
906 123 100         RETVAL = rv;
907             OUTPUT:
908             RETVAL
909              
910             # Submit response (simple version with static body)
911             int
912             _submit_response_with_body(self, stream_id, headers_av, body)
913             SV *self
914             int stream_id
915             AV *headers_av
916             SV *body
917             PREINIT:
918             nghttp2_perl_session *ps;
919             nghttp2_nv *nva;
920             size_t nvlen;
921             nghttp2_data_provider data_prd;
922             int rv;
923             I32 i;
924             STRLEN body_len;
925             char *body_ptr;
926             CODE:
927 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
928              
929             /* Build name-value array from Perl array of arrayrefs */
930 0           nvlen = av_len(headers_av) + 1;
931 0 0         Newxz(nva, nvlen, nghttp2_nv);
932              
933 0 0         for (i = 0; i < (I32)nvlen; i++) {
934 0           SV **pair = av_fetch(headers_av, i, 0);
935 0 0         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    0          
    0          
936 0           AV *pair_av = (AV *)SvRV(*pair);
937 0           SV **name_sv = av_fetch(pair_av, 0, 0);
938 0           SV **value_sv = av_fetch(pair_av, 1, 0);
939              
940 0 0         if (name_sv && value_sv) {
    0          
941             STRLEN name_len, value_len;
942 0           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
943 0           nva[i].namelen = name_len;
944 0           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
945 0           nva[i].valuelen = value_len;
946 0           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
947             }
948             }
949             }
950              
951             /* For now, submit without data provider (headers only) */
952             /* TODO: Implement proper data provider for body */
953 0           body_ptr = SvPVbyte(body, body_len);
954              
955 0           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, NULL);
956              
957 0           Safefree(nva);
958              
959 0 0         if (rv != 0) {
960 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
961             }
962 0 0         RETVAL = rv;
963             OUTPUT:
964             RETVAL
965              
966             # Submit response without body
967             int
968             _submit_response_no_body(self, stream_id, headers_av)
969             SV *self
970             int stream_id
971             AV *headers_av
972             PREINIT:
973             nghttp2_perl_session *ps;
974             nghttp2_nv *nva;
975             size_t nvlen;
976             int rv;
977             I32 i;
978             CODE:
979 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
980              
981             /* Build name-value array */
982 0           nvlen = av_len(headers_av) + 1;
983 0 0         Newxz(nva, nvlen, nghttp2_nv);
984              
985 0 0         for (i = 0; i < (I32)nvlen; i++) {
986 0           SV **pair = av_fetch(headers_av, i, 0);
987 0 0         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    0          
    0          
988 0           AV *pair_av = (AV *)SvRV(*pair);
989 0           SV **name_sv = av_fetch(pair_av, 0, 0);
990 0           SV **value_sv = av_fetch(pair_av, 1, 0);
991              
992 0 0         if (name_sv && value_sv) {
    0          
993             STRLEN name_len, value_len;
994 0           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
995 0           nva[i].namelen = name_len;
996 0           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
997 0           nva[i].valuelen = value_len;
998 0           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
999             }
1000             }
1001             }
1002              
1003 0           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, NULL);
1004              
1005 0           Safefree(nva);
1006              
1007 0 0         if (rv != 0) {
1008 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
1009             }
1010 0 0         RETVAL = rv;
1011             OUTPUT:
1012             RETVAL
1013              
1014             # Resume data on a stream (after NGHTTP2_ERR_DEFERRED)
1015             int
1016             resume_data(self, stream_id)
1017             SV *self
1018             int stream_id
1019             PREINIT:
1020             nghttp2_perl_session *ps;
1021             int rv;
1022             CODE:
1023 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1024 2           rv = nghttp2_session_resume_data(ps->session, stream_id);
1025 2 50         if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
    0          
1026 0           croak("nghttp2_session_resume_data failed: %s", nghttp2_strerror(rv));
1027             }
1028 2 100         RETVAL = rv;
1029             OUTPUT:
1030             RETVAL
1031              
1032             # Get stream user data
1033             SV *
1034             get_stream_user_data(self, stream_id)
1035             SV *self
1036             int stream_id
1037             PREINIT:
1038             nghttp2_perl_session *ps;
1039             void *data;
1040             CODE:
1041 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1042 0           data = nghttp2_session_get_stream_user_data(ps->session, stream_id);
1043 0 0         if (data) {
1044 0           RETVAL = newSVsv((SV *)data);
1045             } else {
1046 0           RETVAL = &PL_sv_undef;
1047             }
1048             OUTPUT:
1049             RETVAL
1050              
1051             # Set stream user data
1052             int
1053             set_stream_user_data(self, stream_id, data)
1054             SV *self
1055             int stream_id
1056             SV *data
1057             PREINIT:
1058             nghttp2_perl_session *ps;
1059             int rv;
1060             CODE:
1061 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1062             /* Note: caller must ensure data SV survives */
1063 0           rv = nghttp2_session_set_stream_user_data(ps->session, stream_id,
1064 0 0         SvOK(data) ? newSVsv(data) : NULL);
1065 0 0         RETVAL = rv;
1066             OUTPUT:
1067             RETVAL
1068              
1069             # Terminate session with GOAWAY
1070             int
1071             terminate_session(self, error_code)
1072             SV *self
1073             int error_code
1074             PREINIT:
1075             nghttp2_perl_session *ps;
1076             int rv;
1077             CODE:
1078 1           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1079 1           rv = nghttp2_session_terminate_session(ps->session, error_code);
1080 1 50         RETVAL = rv;
1081             OUTPUT:
1082             RETVAL
1083              
1084             # Submit response with streaming data callback
1085             # Callback receives ($stream_id, $max_length, $user_data) and returns ($data, $eof)
1086             # Return undef or empty list to defer (call resume_data later)
1087             int
1088             _submit_response_streaming(self, stream_id, headers_av, data_callback, cb_user_data)
1089             SV *self
1090             int stream_id
1091             AV *headers_av
1092             SV *data_callback
1093             SV *cb_user_data
1094             PREINIT:
1095             nghttp2_perl_session *ps;
1096             nghttp2_nv *nva;
1097             size_t nvlen;
1098             nghttp2_data_provider data_prd;
1099             nghttp2_perl_data_provider *dp;
1100             int rv;
1101             I32 i;
1102             CODE:
1103 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1104              
1105             /* Build name-value array from Perl array of arrayrefs */
1106 2           nvlen = av_len(headers_av) + 1;
1107 2 50         Newxz(nva, nvlen, nghttp2_nv);
1108              
1109 6 100         for (i = 0; i < (I32)nvlen; i++) {
1110 4           SV **pair = av_fetch(headers_av, i, 0);
1111 4 50         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    50          
    50          
1112 4           AV *pair_av = (AV *)SvRV(*pair);
1113 4           SV **name_sv = av_fetch(pair_av, 0, 0);
1114 4           SV **value_sv = av_fetch(pair_av, 1, 0);
1115              
1116 4 50         if (name_sv && value_sv) {
    50          
1117             STRLEN name_len, value_len;
1118 4           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
1119 4           nva[i].namelen = name_len;
1120 4           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
1121 4           nva[i].valuelen = value_len;
1122 4           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
1123             }
1124             }
1125             }
1126              
1127             /* Create data provider state */
1128 2           Newxz(dp, 1, nghttp2_perl_data_provider);
1129 2           dp->stream_id = stream_id;
1130 2           dp->callback = newSVsv(data_callback);
1131 2 50         if (SvOK(cb_user_data)) {
1132 0           dp->user_data = newSVsv(cb_user_data);
1133             }
1134 2           dp->eof = 0;
1135 2           dp->deferred = 0;
1136              
1137             /* Track the data provider */
1138 2           add_data_provider(ps, dp);
1139              
1140             /* Set up nghttp2 data provider */
1141 2           data_prd.source.ptr = dp;
1142 2           data_prd.read_callback = perl_data_source_read_callback;
1143              
1144 2           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, &data_prd);
1145              
1146 2           Safefree(nva);
1147              
1148 2 50         if (rv != 0) {
1149 0           remove_data_provider(ps, stream_id);
1150 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
1151             }
1152 2 50         RETVAL = rv;
1153             OUTPUT:
1154             RETVAL
1155              
1156             # Queue data to send on an existing stream.
1157             # The stream must already have a data provider (from submit_request or
1158             # submit_response with a streaming body callback). This sets the data
1159             # provider's user_data to the given data and eof flag, clears the deferred
1160             # state, and calls nghttp2_session_resume_data so the next mem_send will
1161             # invoke the read callback which returns this data.
1162             int
1163             submit_data(self, stream_id, data_sv, eof)
1164             SV *self
1165             int stream_id
1166             SV *data_sv
1167             int eof
1168             PREINIT:
1169             nghttp2_perl_session *ps;
1170             nghttp2_perl_data_provider *dp;
1171             int rv;
1172             CODE:
1173 4           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1174              
1175 4           dp = find_data_provider(ps, stream_id);
1176 4 50         if (!dp) {
1177 0           croak("submit_data: no data provider for stream %d "
1178             "(submit_request or submit_response with body callback first)",
1179             stream_id);
1180             }
1181              
1182             /* Replace the callback with NULL (one-shot static body mode) and
1183             store the data in user_data for the read callback to pick up */
1184 4 100         if (dp->callback) {
1185 3           SvREFCNT_dec(dp->callback);
1186 3           dp->callback = NULL;
1187             }
1188 4 50         if (dp->user_data) {
1189 0           SvREFCNT_dec(dp->user_data);
1190             }
1191 4 50         dp->user_data = SvOK(data_sv) ? newSVsv(data_sv) : NULL;
1192 4           dp->eof = eof ? 1 : 0;
1193 4           dp->deferred = 0;
1194              
1195             /* Resume the stream so nghttp2 calls the read callback */
1196 4           rv = nghttp2_session_resume_data(ps->session, stream_id);
1197 4 50         if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
    0          
1198 0           croak("submit_data: resume failed: %s", nghttp2_strerror(rv));
1199             }
1200 4 50         RETVAL = 0;
1201             OUTPUT:
1202             RETVAL
1203              
1204             # Check if stream is deferred (waiting for data)
1205             int
1206             is_stream_deferred(self, stream_id)
1207             SV *self
1208             int stream_id
1209             PREINIT:
1210             nghttp2_perl_session *ps;
1211             nghttp2_perl_data_provider *dp;
1212             CODE:
1213 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1214 0           dp = find_data_provider(ps, stream_id);
1215 0 0         RETVAL = dp ? dp->deferred : 0;
    0          
1216             OUTPUT:
1217             RETVAL
1218              
1219             # Clear deferred flag for a stream (internal use after resume_data)
1220             void
1221             _clear_deferred(self, stream_id)
1222             SV *self
1223             int stream_id
1224             PREINIT:
1225             nghttp2_perl_session *ps;
1226             nghttp2_perl_data_provider *dp;
1227             CODE:
1228 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1229 2           dp = find_data_provider(ps, stream_id);
1230 2 50         if (dp) {
1231 2           dp->deferred = 0;
1232             }
1233              
1234             # Create new client session
1235             SV *
1236             _new_client_xs(class, callbacks_hv, user_data)
1237             char *class
1238             HV *callbacks_hv
1239             SV *user_data
1240             PREINIT:
1241             nghttp2_perl_session *ps;
1242             nghttp2_session_callbacks *callbacks;
1243             int rv;
1244             SV **svp;
1245             CODE:
1246             /* Allocate our wrapper structure */
1247 22           Newxz(ps, 1, nghttp2_perl_session);
1248              
1249             /* Initialize send buffer */
1250 22           ps->send_buf_cap = 16384;
1251 22           ps->send_buf = (char *)malloc(ps->send_buf_cap);
1252 22           ps->send_buf_len = 0;
1253              
1254             /* Store user data */
1255 22 50         if (SvOK(user_data)) {
1256 0           ps->user_data = newSVsv(user_data);
1257             }
1258              
1259             /* Extract callbacks from hash */
1260 22 50         if (callbacks_hv) {
1261 22 100         if ((svp = hv_fetch(callbacks_hv, "on_begin_headers", 16, 0))) {
1262 13           ps->cb_on_begin_headers = newSVsv(*svp);
1263             }
1264 22 100         if ((svp = hv_fetch(callbacks_hv, "on_header", 9, 0))) {
1265 14           ps->cb_on_header = newSVsv(*svp);
1266             }
1267 22 50         if ((svp = hv_fetch(callbacks_hv, "on_frame_recv", 13, 0))) {
1268 22           ps->cb_on_frame_recv = newSVsv(*svp);
1269             }
1270 22 100         if ((svp = hv_fetch(callbacks_hv, "on_data_chunk_recv", 18, 0))) {
1271 9           ps->cb_on_data_chunk_recv = newSVsv(*svp);
1272             }
1273 22 100         if ((svp = hv_fetch(callbacks_hv, "on_stream_close", 15, 0))) {
1274 9           ps->cb_on_stream_close = newSVsv(*svp);
1275             }
1276             }
1277              
1278             /* Create nghttp2 callbacks */
1279 22           nghttp2_session_callbacks_new(&callbacks);
1280 22           nghttp2_session_callbacks_set_send_callback(callbacks, perl_send_callback);
1281 22           nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, perl_on_begin_headers_callback);
1282 22           nghttp2_session_callbacks_set_on_header_callback(callbacks, perl_on_header_callback);
1283 22           nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, perl_on_frame_recv_callback);
1284 22           nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, perl_on_data_chunk_recv_callback);
1285 22           nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, perl_on_stream_close_callback);
1286              
1287             /* Create CLIENT session (difference from server) */
1288 22           rv = nghttp2_session_client_new(&ps->session, callbacks, ps);
1289 22           nghttp2_session_callbacks_del(callbacks);
1290              
1291 22 50         if (rv != 0) {
1292 0 0         if (ps->user_data) SvREFCNT_dec(ps->user_data);
1293 0 0         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
1294 0 0         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
1295 0 0         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
1296 0 0         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
1297 0 0         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
1298 0           free(ps->send_buf);
1299 0           Safefree(ps);
1300 0           croak("nghttp2_session_client_new failed: %s", nghttp2_strerror(rv));
1301             }
1302              
1303             /* Bless and return */
1304 22           RETVAL = sv_newmortal();
1305 22           sv_setref_pv(RETVAL, class, (void *)ps);
1306 22           SvREFCNT_inc(RETVAL);
1307             OUTPUT:
1308             RETVAL
1309              
1310             # Submit request (client-side)
1311             # Returns stream ID on success
1312             int
1313             _submit_request_xs(self, headers_av, body_sv)
1314             SV *self
1315             AV *headers_av
1316             SV *body_sv
1317             PREINIT:
1318             nghttp2_perl_session *ps;
1319             nghttp2_nv *nva;
1320             size_t nvlen;
1321             nghttp2_data_provider data_prd;
1322 24           nghttp2_data_provider *data_prd_ptr = NULL;
1323 24           nghttp2_perl_data_provider *dp = NULL;
1324             int32_t stream_id;
1325             I32 i;
1326 24 50         STRLEN body_len = 0;
1327             CODE:
1328 24           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1329              
1330             /* Build name-value array from Perl array of arrayrefs */
1331 24           nvlen = av_len(headers_av) + 1;
1332 24 50         Newxz(nva, nvlen, nghttp2_nv);
1333              
1334 179 100         for (i = 0; i < (I32)nvlen; i++) {
1335 155           SV **pair = av_fetch(headers_av, i, 0);
1336 155 50         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    50          
    50          
1337 155           AV *pair_av = (AV *)SvRV(*pair);
1338 155           SV **name_sv = av_fetch(pair_av, 0, 0);
1339 155           SV **value_sv = av_fetch(pair_av, 1, 0);
1340              
1341 155 50         if (name_sv && value_sv) {
    50          
1342             STRLEN name_len, value_len;
1343 155           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
1344 155           nva[i].namelen = name_len;
1345 155           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
1346 155           nva[i].valuelen = value_len;
1347 155           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
1348             }
1349             }
1350             }
1351              
1352             /* Check if we have a body to send */
1353 24 100         if (SvOK(body_sv) && SvROK(body_sv) && SvTYPE(SvRV(body_sv)) == SVt_PVCV) {
    100          
    50          
1354             /* CODE ref body: streaming callback data provider */
1355 4           Newxz(dp, 1, nghttp2_perl_data_provider);
1356 4           dp->stream_id = 0; /* Will be set after submit */
1357 4           dp->eof = 0;
1358 4           dp->deferred = 0;
1359 4           dp->callback = newSVsv(body_sv);
1360              
1361 4           data_prd.source.ptr = dp;
1362 4           data_prd.read_callback = perl_data_source_read_callback;
1363 4           data_prd_ptr = &data_prd;
1364             }
1365 20 100         else if (SvOK(body_sv) && SvPOK(body_sv)) {
    50          
1366 2           const char *body_ptr = SvPVbyte(body_sv, body_len);
1367 2 50         if (body_len > 0) {
1368             /* Static string body: one-shot data provider */
1369 2           Newxz(dp, 1, nghttp2_perl_data_provider);
1370 2           dp->stream_id = 0; /* Will be set after submit */
1371 2           dp->eof = 1; /* Static bodies always want EOF after sending */
1372 2           dp->deferred = 0;
1373 2           dp->user_data = newSVsv(body_sv);
1374 2           dp->callback = NULL; /* Use user_data as body */
1375              
1376 2           data_prd.source.ptr = dp;
1377 2           data_prd.read_callback = perl_data_source_read_callback;
1378 2           data_prd_ptr = &data_prd;
1379             }
1380             }
1381              
1382 24           stream_id = nghttp2_submit_request(ps->session, NULL, nva, nvlen, data_prd_ptr, NULL);
1383              
1384 24           Safefree(nva);
1385              
1386 24 50         if (stream_id < 0) {
1387 0 0         if (dp) {
1388 0 0         if (dp->callback) SvREFCNT_dec(dp->callback);
1389 0 0         if (dp->user_data) SvREFCNT_dec(dp->user_data);
1390 0           Safefree(dp);
1391             }
1392 0           croak("nghttp2_submit_request failed: %s", nghttp2_strerror(stream_id));
1393             }
1394              
1395             /* Track data provider if we have one */
1396 24 100         if (dp) {
1397 6           dp->stream_id = stream_id;
1398 6           add_data_provider(ps, dp);
1399             }
1400              
1401 24 100         RETVAL = stream_id;
1402             OUTPUT:
1403             RETVAL
1404              
1405             # Submit RST_STREAM (reset a stream)
1406             int
1407             submit_rst_stream(self, stream_id, error_code)
1408             SV *self
1409             int stream_id
1410             unsigned int error_code
1411             PREINIT:
1412             nghttp2_perl_session *ps;
1413             int rv;
1414             CODE:
1415 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1416 0           rv = nghttp2_submit_rst_stream(ps->session, NGHTTP2_FLAG_NONE, stream_id, error_code);
1417 0 0         if (rv != 0) {
1418 0           croak("nghttp2_submit_rst_stream failed: %s", nghttp2_strerror(rv));
1419             }
1420 0 0         RETVAL = rv;
1421             OUTPUT:
1422             RETVAL
1423              
1424             # Submit PING frame
1425             int
1426             submit_ping(self, ack, opaque_data)
1427             SV *self
1428             int ack
1429             SV *opaque_data
1430             PREINIT:
1431             nghttp2_perl_session *ps;
1432             STRLEN len;
1433             const uint8_t *data;
1434             uint8_t flags;
1435             int rv;
1436             CODE:
1437 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1438 0           flags = ack ? NGHTTP2_FLAG_ACK : NGHTTP2_FLAG_NONE;
1439              
1440 0 0         if (SvOK(opaque_data)) {
1441 0           data = (const uint8_t *)SvPVbyte(opaque_data, len);
1442 0 0         if (len != 8) {
1443 0           croak("PING opaque_data must be exactly 8 bytes");
1444             }
1445             } else {
1446 0           data = NULL;
1447             }
1448              
1449 0           rv = nghttp2_submit_ping(ps->session, flags, data);
1450 0 0         if (rv != 0) {
1451 0           croak("nghttp2_submit_ping failed: %s", nghttp2_strerror(rv));
1452             }
1453 0 0         RETVAL = rv;
1454             OUTPUT:
1455             RETVAL
1456              
1457             # Submit WINDOW_UPDATE frame
1458             int
1459             submit_window_update(self, stream_id, window_size_increment)
1460             SV *self
1461             int stream_id
1462             int window_size_increment
1463             PREINIT:
1464             nghttp2_perl_session *ps;
1465             int rv;
1466             CODE:
1467 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1468 0           rv = nghttp2_submit_window_update(ps->session, NGHTTP2_FLAG_NONE, stream_id, window_size_increment);
1469 0 0         if (rv != 0) {
1470 0           croak("nghttp2_submit_window_update failed: %s", nghttp2_strerror(rv));
1471             }
1472 0 0         RETVAL = rv;
1473             OUTPUT:
1474             RETVAL