File Coverage

nghttp2.xs
Criterion Covered Total %
statement 407 583 69.8
branch 202 472 42.8
condition n/a
subroutine n/a
pod n/a
total 609 1055 57.7


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 18           static void remove_data_provider(nghttp2_perl_session *ps, int32_t stream_id) {
102             dTHX;
103             int i;
104 18 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 267           static ssize_t perl_send_callback(nghttp2_session *session,
295             const uint8_t *data, size_t length,
296             int flags, void *user_data) {
297 267           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
298              
299             /* Grow buffer if needed */
300 267 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 267           memcpy(ps->send_buf + ps->send_buf_len, data, length);
313 267           ps->send_buf_len += length;
314              
315 267           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 18           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 18           nghttp2_perl_session *ps = (nghttp2_perl_session *)user_data;
431             AV *args;
432 18           int ret = 0;
433              
434             /* Clean up any data provider for this stream */
435 18           remove_data_provider(ps, stream_id);
436              
437 18 100         if (!ps->cb_on_stream_close || !SvOK(ps->cb_on_stream_close)) {
    50          
438 10           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 15           nghttp2_info *info = nghttp2_version(0);
460 15 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 99           nghttp2_option *option = NULL;
651             int rv;
652             SV **svp;
653 99           HV *options_hv = NULL;
654             CODE:
655             /* Check for optional options hash (4th argument) */
656 99 100         if (items > 3 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVHV) {
    50          
    50          
657 2           options_hv = (HV *)SvRV(ST(3));
658             }
659              
660             /* Allocate our wrapper structure */
661 99           Newxz(ps, 1, nghttp2_perl_session);
662              
663             /* Initialize send buffer */
664 99           ps->send_buf_cap = 16384;
665 99           ps->send_buf = (char *)malloc(ps->send_buf_cap);
666 99           ps->send_buf_len = 0;
667              
668             /* Store user data */
669 99 50         if (SvOK(user_data)) {
670 0           ps->user_data = newSVsv(user_data);
671             }
672              
673             /* Extract callbacks from hash */
674 99 50         if ((svp = hv_fetch(callbacks_hv, "on_begin_headers", 16, 0))) {
675 99           ps->cb_on_begin_headers = newSVsv(*svp);
676             }
677 99 50         if ((svp = hv_fetch(callbacks_hv, "on_header", 9, 0))) {
678 99           ps->cb_on_header = newSVsv(*svp);
679             }
680 99 50         if ((svp = hv_fetch(callbacks_hv, "on_frame_recv", 13, 0))) {
681 99           ps->cb_on_frame_recv = newSVsv(*svp);
682             }
683 99 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 99 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 99           nghttp2_session_callbacks_new(&callbacks);
692 99           nghttp2_session_callbacks_set_send_callback(callbacks, perl_send_callback);
693 99           nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, perl_on_begin_headers_callback);
694 99           nghttp2_session_callbacks_set_on_header_callback(callbacks, perl_on_header_callback);
695 99           nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, perl_on_frame_recv_callback);
696 99           nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, perl_on_data_chunk_recv_callback);
697 99           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 99 100         if (options_hv) {
701 2           rv = nghttp2_option_new(&option);
702 2 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 2 100         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 2 100         if ((svp = hv_fetch(options_hv, "stream_reset_burst", 18, 0))) {
720 1           SV **rate_svp = hv_fetch(options_hv, "stream_reset_rate", 17, 0);
721 1 50         if (rate_svp) {
722 1           nghttp2_option_set_stream_reset_rate_limit(
723 1           option, (uint64_t)SvUV(*svp), (uint64_t)SvUV(*rate_svp));
724             }
725             }
726              
727 2           rv = nghttp2_session_server_new2(&ps->session, callbacks, ps, option);
728 2           nghttp2_option_del(option);
729             } else {
730 97           rv = nghttp2_session_server_new(&ps->session, callbacks, ps);
731             }
732 99           nghttp2_session_callbacks_del(callbacks);
733              
734 99 50         if (rv != 0) {
735 0 0         if (ps->user_data) SvREFCNT_dec(ps->user_data);
736 0 0         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
737 0 0         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
738 0 0         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
739 0 0         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
740 0 0         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
741 0           free(ps->send_buf);
742 0           Safefree(ps);
743 0           croak("nghttp2_session_server_new failed: %s", nghttp2_strerror(rv));
744             }
745              
746             /* Bless and return */
747 99           RETVAL = sv_newmortal();
748 99           sv_setref_pv(RETVAL, class, (void *)ps);
749 99           SvREFCNT_inc(RETVAL);
750             OUTPUT:
751             RETVAL
752              
753             # Destructor
754             void
755             DESTROY(self)
756             SV *self
757             PREINIT:
758             nghttp2_perl_session *ps;
759             CODE:
760 121           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
761 121 50         if (ps) {
762             int i;
763 121 50         if (ps->session) {
764 121           nghttp2_session_del(ps->session);
765             }
766 121 50         if (ps->user_data) SvREFCNT_dec(ps->user_data);
767 121 100         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
768 121 100         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
769 121 50         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
770 121 100         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
771 121 100         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
772 121 50         if (ps->send_buf) free(ps->send_buf);
773             /* Clean up data providers */
774 129 100         for (i = 0; i < ps->data_providers_count; i++) {
775 8 100         if (ps->data_providers[i]) {
776 6           nghttp2_perl_data_provider *dp = ps->data_providers[i];
777 6 100         if (dp->callback) SvREFCNT_dec(dp->callback);
778 6 50         if (dp->user_data) SvREFCNT_dec(dp->user_data);
779 6           Safefree(dp);
780             }
781             }
782 121 100         if (ps->data_providers) free(ps->data_providers);
783 121           Safefree(ps);
784             }
785              
786             # Feed incoming data to session
787             int
788             mem_recv(self, data)
789             SV *self
790             SV *data
791             PREINIT:
792             nghttp2_perl_session *ps;
793             STRLEN len;
794             const char *buf;
795             ssize_t rv;
796             CODE:
797 183           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
798 183           buf = SvPVbyte(data, len);
799              
800 183           rv = nghttp2_session_mem_recv(ps->session, (const uint8_t *)buf, len);
801 183 100         if (rv < 0) {
802 1           croak("nghttp2_session_mem_recv failed: %s", nghttp2_strerror((int)rv));
803             }
804 182 100         RETVAL = (int)rv;
805             OUTPUT:
806             RETVAL
807              
808             # Get data to send
809             SV *
810             mem_send(self)
811             SV *self
812             PREINIT:
813             nghttp2_perl_session *ps;
814             int rv;
815             CODE:
816 284           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
817              
818             /* Clear send buffer */
819 284           ps->send_buf_len = 0;
820              
821             /* Trigger send callback to fill buffer */
822 284           rv = nghttp2_session_send(ps->session);
823 284 50         if (rv != 0) {
824 0           croak("nghttp2_session_send failed: %s", nghttp2_strerror(rv));
825             }
826              
827             /* Return buffered data */
828 284 100         if (ps->send_buf_len > 0) {
829 219           RETVAL = newSVpvn(ps->send_buf, ps->send_buf_len);
830             } else {
831 65           RETVAL = newSVpvn("", 0);
832             }
833             OUTPUT:
834             RETVAL
835              
836             # Check if session wants to read
837             int
838             want_read(self)
839             SV *self
840             PREINIT:
841             nghttp2_perl_session *ps;
842             CODE:
843 3           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
844 3           RETVAL = nghttp2_session_want_read(ps->session);
845             OUTPUT:
846             RETVAL
847              
848             # Check if session wants to write
849             int
850             want_write(self)
851             SV *self
852             PREINIT:
853             nghttp2_perl_session *ps;
854             CODE:
855 3           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
856 3           RETVAL = nghttp2_session_want_write(ps->session);
857             OUTPUT:
858             RETVAL
859              
860             # Submit SETTINGS frame
861             int
862             submit_settings(self, settings_hv)
863             SV *self
864             HV *settings_hv
865             PREINIT:
866             nghttp2_perl_session *ps;
867             nghttp2_settings_entry iv[16];
868 123 50         int niv = 0;
869             SV **svp;
870             int rv;
871             CODE:
872 123           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
873              
874 123 100         if ((svp = hv_fetch(settings_hv, "max_concurrent_streams", 22, 0))) {
875 111           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
876 111           iv[niv].value = SvUV(*svp);
877 111           niv++;
878             }
879 123 100         if ((svp = hv_fetch(settings_hv, "initial_window_size", 19, 0))) {
880 109           iv[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
881 109           iv[niv].value = SvUV(*svp);
882 109           niv++;
883             }
884 123 50         if ((svp = hv_fetch(settings_hv, "max_frame_size", 14, 0))) {
885 0           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
886 0           iv[niv].value = SvUV(*svp);
887 0           niv++;
888             }
889 123 50         if ((svp = hv_fetch(settings_hv, "enable_push", 11, 0))) {
890 0           iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
891 0           iv[niv].value = SvTRUE(*svp) ? 1 : 0;
892 0           niv++;
893             }
894 123 100         if ((svp = hv_fetch(settings_hv, "enable_connect_protocol", 23, 0))) {
895 10           iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
896 10           iv[niv].value = SvTRUE(*svp) ? 1 : 0;
897 10           niv++;
898             }
899 123 100         if ((svp = hv_fetch(settings_hv, "header_table_size", 17, 0))) {
900 1           iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
901 1           iv[niv].value = SvUV(*svp);
902 1           niv++;
903             }
904 123 100         if ((svp = hv_fetch(settings_hv, "max_header_list_size", 20, 0))) {
905 1           iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
906 1           iv[niv].value = SvUV(*svp);
907 1           niv++;
908             }
909              
910 123           rv = nghttp2_submit_settings(ps->session, NGHTTP2_FLAG_NONE, iv, niv);
911 123 50         if (rv != 0) {
912 0           croak("nghttp2_submit_settings failed: %s", nghttp2_strerror(rv));
913             }
914 123 100         RETVAL = rv;
915             OUTPUT:
916             RETVAL
917              
918             # Submit response (simple version with static body)
919             int
920             _submit_response_with_body(self, stream_id, headers_av, body)
921             SV *self
922             int stream_id
923             AV *headers_av
924             SV *body
925             PREINIT:
926             nghttp2_perl_session *ps;
927             nghttp2_nv *nva;
928             size_t nvlen;
929             nghttp2_data_provider data_prd;
930             int rv;
931             I32 i;
932             STRLEN body_len;
933             char *body_ptr;
934             CODE:
935 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
936              
937             /* Build name-value array from Perl array of arrayrefs */
938 0           nvlen = av_len(headers_av) + 1;
939 0 0         Newxz(nva, nvlen, nghttp2_nv);
940              
941 0 0         for (i = 0; i < (I32)nvlen; i++) {
942 0           SV **pair = av_fetch(headers_av, i, 0);
943 0 0         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    0          
    0          
944 0           AV *pair_av = (AV *)SvRV(*pair);
945 0           SV **name_sv = av_fetch(pair_av, 0, 0);
946 0           SV **value_sv = av_fetch(pair_av, 1, 0);
947              
948 0 0         if (name_sv && value_sv) {
    0          
949             STRLEN name_len, value_len;
950 0           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
951 0           nva[i].namelen = name_len;
952 0           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
953 0           nva[i].valuelen = value_len;
954 0           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
955             }
956             }
957             }
958              
959             /* For now, submit without data provider (headers only) */
960             /* TODO: Implement proper data provider for body */
961 0           body_ptr = SvPVbyte(body, body_len);
962              
963 0           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, NULL);
964              
965 0           Safefree(nva);
966              
967 0 0         if (rv != 0) {
968 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
969             }
970 0 0         RETVAL = rv;
971             OUTPUT:
972             RETVAL
973              
974             # Submit response without body
975             int
976             _submit_response_no_body(self, stream_id, headers_av)
977             SV *self
978             int stream_id
979             AV *headers_av
980             PREINIT:
981             nghttp2_perl_session *ps;
982             nghttp2_nv *nva;
983             size_t nvlen;
984             int rv;
985             I32 i;
986             CODE:
987 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
988              
989             /* Build name-value array */
990 0           nvlen = av_len(headers_av) + 1;
991 0 0         Newxz(nva, nvlen, nghttp2_nv);
992              
993 0 0         for (i = 0; i < (I32)nvlen; i++) {
994 0           SV **pair = av_fetch(headers_av, i, 0);
995 0 0         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    0          
    0          
996 0           AV *pair_av = (AV *)SvRV(*pair);
997 0           SV **name_sv = av_fetch(pair_av, 0, 0);
998 0           SV **value_sv = av_fetch(pair_av, 1, 0);
999              
1000 0 0         if (name_sv && value_sv) {
    0          
1001             STRLEN name_len, value_len;
1002 0           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
1003 0           nva[i].namelen = name_len;
1004 0           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
1005 0           nva[i].valuelen = value_len;
1006 0           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
1007             }
1008             }
1009             }
1010              
1011 0           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, NULL);
1012              
1013 0           Safefree(nva);
1014              
1015 0 0         if (rv != 0) {
1016 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
1017             }
1018 0 0         RETVAL = rv;
1019             OUTPUT:
1020             RETVAL
1021              
1022             # Resume data on a stream (after NGHTTP2_ERR_DEFERRED)
1023             int
1024             resume_data(self, stream_id)
1025             SV *self
1026             int stream_id
1027             PREINIT:
1028             nghttp2_perl_session *ps;
1029             int rv;
1030             CODE:
1031 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1032 2           rv = nghttp2_session_resume_data(ps->session, stream_id);
1033 2 50         if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
    0          
1034 0           croak("nghttp2_session_resume_data failed: %s", nghttp2_strerror(rv));
1035             }
1036 2 100         RETVAL = rv;
1037             OUTPUT:
1038             RETVAL
1039              
1040             # Get stream user data
1041             SV *
1042             get_stream_user_data(self, stream_id)
1043             SV *self
1044             int stream_id
1045             PREINIT:
1046             nghttp2_perl_session *ps;
1047             void *data;
1048             CODE:
1049 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1050 0           data = nghttp2_session_get_stream_user_data(ps->session, stream_id);
1051 0 0         if (data) {
1052 0           RETVAL = newSVsv((SV *)data);
1053             } else {
1054 0           RETVAL = &PL_sv_undef;
1055             }
1056             OUTPUT:
1057             RETVAL
1058              
1059             # Set stream user data
1060             int
1061             set_stream_user_data(self, stream_id, data)
1062             SV *self
1063             int stream_id
1064             SV *data
1065             PREINIT:
1066             nghttp2_perl_session *ps;
1067             int rv;
1068             CODE:
1069 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1070             /* Note: caller must ensure data SV survives */
1071 0           rv = nghttp2_session_set_stream_user_data(ps->session, stream_id,
1072 0 0         SvOK(data) ? newSVsv(data) : NULL);
1073 0 0         RETVAL = rv;
1074             OUTPUT:
1075             RETVAL
1076              
1077             # Terminate session with GOAWAY
1078             int
1079             terminate_session(self, error_code)
1080             SV *self
1081             int error_code
1082             PREINIT:
1083             nghttp2_perl_session *ps;
1084             int rv;
1085             CODE:
1086 1           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1087 1           rv = nghttp2_session_terminate_session(ps->session, error_code);
1088 1 50         RETVAL = rv;
1089             OUTPUT:
1090             RETVAL
1091              
1092             # Submit response with streaming data callback
1093             # Callback receives ($stream_id, $max_length, $user_data) and returns ($data, $eof)
1094             # Return undef or empty list to defer (call resume_data later)
1095             int
1096             _submit_response_streaming(self, stream_id, headers_av, data_callback, cb_user_data)
1097             SV *self
1098             int stream_id
1099             AV *headers_av
1100             SV *data_callback
1101             SV *cb_user_data
1102             PREINIT:
1103             nghttp2_perl_session *ps;
1104             nghttp2_nv *nva;
1105             size_t nvlen;
1106             nghttp2_data_provider data_prd;
1107             nghttp2_perl_data_provider *dp;
1108             int rv;
1109             I32 i;
1110             CODE:
1111 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1112              
1113             /* Build name-value array from Perl array of arrayrefs */
1114 2           nvlen = av_len(headers_av) + 1;
1115 2 50         Newxz(nva, nvlen, nghttp2_nv);
1116              
1117 6 100         for (i = 0; i < (I32)nvlen; i++) {
1118 4           SV **pair = av_fetch(headers_av, i, 0);
1119 4 50         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    50          
    50          
1120 4           AV *pair_av = (AV *)SvRV(*pair);
1121 4           SV **name_sv = av_fetch(pair_av, 0, 0);
1122 4           SV **value_sv = av_fetch(pair_av, 1, 0);
1123              
1124 4 50         if (name_sv && value_sv) {
    50          
1125             STRLEN name_len, value_len;
1126 4           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
1127 4           nva[i].namelen = name_len;
1128 4           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
1129 4           nva[i].valuelen = value_len;
1130 4           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
1131             }
1132             }
1133             }
1134              
1135             /* Create data provider state */
1136 2           Newxz(dp, 1, nghttp2_perl_data_provider);
1137 2           dp->stream_id = stream_id;
1138 2           dp->callback = newSVsv(data_callback);
1139 2 50         if (SvOK(cb_user_data)) {
1140 0           dp->user_data = newSVsv(cb_user_data);
1141             }
1142 2           dp->eof = 0;
1143 2           dp->deferred = 0;
1144              
1145             /* Track the data provider */
1146 2           add_data_provider(ps, dp);
1147              
1148             /* Set up nghttp2 data provider */
1149 2           data_prd.source.ptr = dp;
1150 2           data_prd.read_callback = perl_data_source_read_callback;
1151              
1152 2           rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, &data_prd);
1153              
1154 2           Safefree(nva);
1155              
1156 2 50         if (rv != 0) {
1157 0           remove_data_provider(ps, stream_id);
1158 0           croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
1159             }
1160 2 50         RETVAL = rv;
1161             OUTPUT:
1162             RETVAL
1163              
1164             # Queue data to send on an existing stream.
1165             # The stream must already have a data provider (from submit_request or
1166             # submit_response with a streaming body callback). This sets the data
1167             # provider's user_data to the given data and eof flag, clears the deferred
1168             # state, and calls nghttp2_session_resume_data so the next mem_send will
1169             # invoke the read callback which returns this data.
1170             int
1171             submit_data(self, stream_id, data_sv, eof)
1172             SV *self
1173             int stream_id
1174             SV *data_sv
1175             int eof
1176             PREINIT:
1177             nghttp2_perl_session *ps;
1178             nghttp2_perl_data_provider *dp;
1179             int rv;
1180             CODE:
1181 4           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1182              
1183 4           dp = find_data_provider(ps, stream_id);
1184 4 50         if (!dp) {
1185 0           croak("submit_data: no data provider for stream %d "
1186             "(submit_request or submit_response with body callback first)",
1187             stream_id);
1188             }
1189              
1190             /* Replace the callback with NULL (one-shot static body mode) and
1191             store the data in user_data for the read callback to pick up */
1192 4 100         if (dp->callback) {
1193 3           SvREFCNT_dec(dp->callback);
1194 3           dp->callback = NULL;
1195             }
1196 4 50         if (dp->user_data) {
1197 0           SvREFCNT_dec(dp->user_data);
1198             }
1199 4 50         dp->user_data = SvOK(data_sv) ? newSVsv(data_sv) : NULL;
1200 4           dp->eof = eof ? 1 : 0;
1201 4           dp->deferred = 0;
1202              
1203             /* Resume the stream so nghttp2 calls the read callback */
1204 4           rv = nghttp2_session_resume_data(ps->session, stream_id);
1205 4 50         if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
    0          
1206 0           croak("submit_data: resume failed: %s", nghttp2_strerror(rv));
1207             }
1208 4 50         RETVAL = 0;
1209             OUTPUT:
1210             RETVAL
1211              
1212             # Check if stream is deferred (waiting for data)
1213             int
1214             is_stream_deferred(self, stream_id)
1215             SV *self
1216             int stream_id
1217             PREINIT:
1218             nghttp2_perl_session *ps;
1219             nghttp2_perl_data_provider *dp;
1220             CODE:
1221 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1222 0           dp = find_data_provider(ps, stream_id);
1223 0 0         RETVAL = dp ? dp->deferred : 0;
    0          
1224             OUTPUT:
1225             RETVAL
1226              
1227             # Clear deferred flag for a stream (internal use after resume_data)
1228             void
1229             _clear_deferred(self, stream_id)
1230             SV *self
1231             int stream_id
1232             PREINIT:
1233             nghttp2_perl_session *ps;
1234             nghttp2_perl_data_provider *dp;
1235             CODE:
1236 2           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1237 2           dp = find_data_provider(ps, stream_id);
1238 2 50         if (dp) {
1239 2           dp->deferred = 0;
1240             }
1241              
1242             # Create new client session
1243             SV *
1244             _new_client_xs(class, callbacks_hv, user_data)
1245             char *class
1246             HV *callbacks_hv
1247             SV *user_data
1248             PREINIT:
1249             nghttp2_perl_session *ps;
1250             nghttp2_session_callbacks *callbacks;
1251             int rv;
1252             SV **svp;
1253             CODE:
1254             /* Allocate our wrapper structure */
1255 22           Newxz(ps, 1, nghttp2_perl_session);
1256              
1257             /* Initialize send buffer */
1258 22           ps->send_buf_cap = 16384;
1259 22           ps->send_buf = (char *)malloc(ps->send_buf_cap);
1260 22           ps->send_buf_len = 0;
1261              
1262             /* Store user data */
1263 22 50         if (SvOK(user_data)) {
1264 0           ps->user_data = newSVsv(user_data);
1265             }
1266              
1267             /* Extract callbacks from hash */
1268 22 50         if (callbacks_hv) {
1269 22 100         if ((svp = hv_fetch(callbacks_hv, "on_begin_headers", 16, 0))) {
1270 13           ps->cb_on_begin_headers = newSVsv(*svp);
1271             }
1272 22 100         if ((svp = hv_fetch(callbacks_hv, "on_header", 9, 0))) {
1273 14           ps->cb_on_header = newSVsv(*svp);
1274             }
1275 22 50         if ((svp = hv_fetch(callbacks_hv, "on_frame_recv", 13, 0))) {
1276 22           ps->cb_on_frame_recv = newSVsv(*svp);
1277             }
1278 22 100         if ((svp = hv_fetch(callbacks_hv, "on_data_chunk_recv", 18, 0))) {
1279 9           ps->cb_on_data_chunk_recv = newSVsv(*svp);
1280             }
1281 22 100         if ((svp = hv_fetch(callbacks_hv, "on_stream_close", 15, 0))) {
1282 9           ps->cb_on_stream_close = newSVsv(*svp);
1283             }
1284             }
1285              
1286             /* Create nghttp2 callbacks */
1287 22           nghttp2_session_callbacks_new(&callbacks);
1288 22           nghttp2_session_callbacks_set_send_callback(callbacks, perl_send_callback);
1289 22           nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, perl_on_begin_headers_callback);
1290 22           nghttp2_session_callbacks_set_on_header_callback(callbacks, perl_on_header_callback);
1291 22           nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, perl_on_frame_recv_callback);
1292 22           nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, perl_on_data_chunk_recv_callback);
1293 22           nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, perl_on_stream_close_callback);
1294              
1295             /* Create CLIENT session (difference from server) */
1296 22           rv = nghttp2_session_client_new(&ps->session, callbacks, ps);
1297 22           nghttp2_session_callbacks_del(callbacks);
1298              
1299 22 50         if (rv != 0) {
1300 0 0         if (ps->user_data) SvREFCNT_dec(ps->user_data);
1301 0 0         if (ps->cb_on_begin_headers) SvREFCNT_dec(ps->cb_on_begin_headers);
1302 0 0         if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
1303 0 0         if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
1304 0 0         if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
1305 0 0         if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
1306 0           free(ps->send_buf);
1307 0           Safefree(ps);
1308 0           croak("nghttp2_session_client_new failed: %s", nghttp2_strerror(rv));
1309             }
1310              
1311             /* Bless and return */
1312 22           RETVAL = sv_newmortal();
1313 22           sv_setref_pv(RETVAL, class, (void *)ps);
1314 22           SvREFCNT_inc(RETVAL);
1315             OUTPUT:
1316             RETVAL
1317              
1318             # Submit request (client-side)
1319             # Returns stream ID on success
1320             int
1321             _submit_request_xs(self, headers_av, body_sv)
1322             SV *self
1323             AV *headers_av
1324             SV *body_sv
1325             PREINIT:
1326             nghttp2_perl_session *ps;
1327             nghttp2_nv *nva;
1328             size_t nvlen;
1329             nghttp2_data_provider data_prd;
1330 24           nghttp2_data_provider *data_prd_ptr = NULL;
1331 24           nghttp2_perl_data_provider *dp = NULL;
1332             int32_t stream_id;
1333             I32 i;
1334 24 50         STRLEN body_len = 0;
1335             CODE:
1336 24           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1337              
1338             /* Build name-value array from Perl array of arrayrefs */
1339 24           nvlen = av_len(headers_av) + 1;
1340 24 50         Newxz(nva, nvlen, nghttp2_nv);
1341              
1342 179 100         for (i = 0; i < (I32)nvlen; i++) {
1343 155           SV **pair = av_fetch(headers_av, i, 0);
1344 155 50         if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
    50          
    50          
1345 155           AV *pair_av = (AV *)SvRV(*pair);
1346 155           SV **name_sv = av_fetch(pair_av, 0, 0);
1347 155           SV **value_sv = av_fetch(pair_av, 1, 0);
1348              
1349 155 50         if (name_sv && value_sv) {
    50          
1350             STRLEN name_len, value_len;
1351 155           nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
1352 155           nva[i].namelen = name_len;
1353 155           nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
1354 155           nva[i].valuelen = value_len;
1355 155           nva[i].flags = NGHTTP2_NV_FLAG_NONE;
1356             }
1357             }
1358             }
1359              
1360             /* Check if we have a body to send */
1361 24 100         if (SvOK(body_sv) && SvROK(body_sv) && SvTYPE(SvRV(body_sv)) == SVt_PVCV) {
    100          
    50          
1362             /* CODE ref body: streaming callback data provider */
1363 4           Newxz(dp, 1, nghttp2_perl_data_provider);
1364 4           dp->stream_id = 0; /* Will be set after submit */
1365 4           dp->eof = 0;
1366 4           dp->deferred = 0;
1367 4           dp->callback = newSVsv(body_sv);
1368              
1369 4           data_prd.source.ptr = dp;
1370 4           data_prd.read_callback = perl_data_source_read_callback;
1371 4           data_prd_ptr = &data_prd;
1372             }
1373 20 100         else if (SvOK(body_sv) && SvPOK(body_sv)) {
    50          
1374 2           const char *body_ptr = SvPVbyte(body_sv, body_len);
1375 2 50         if (body_len > 0) {
1376             /* Static string body: one-shot data provider */
1377 2           Newxz(dp, 1, nghttp2_perl_data_provider);
1378 2           dp->stream_id = 0; /* Will be set after submit */
1379 2           dp->eof = 1; /* Static bodies always want EOF after sending */
1380 2           dp->deferred = 0;
1381 2           dp->user_data = newSVsv(body_sv);
1382 2           dp->callback = NULL; /* Use user_data as body */
1383              
1384 2           data_prd.source.ptr = dp;
1385 2           data_prd.read_callback = perl_data_source_read_callback;
1386 2           data_prd_ptr = &data_prd;
1387             }
1388             }
1389              
1390 24           stream_id = nghttp2_submit_request(ps->session, NULL, nva, nvlen, data_prd_ptr, NULL);
1391              
1392 24           Safefree(nva);
1393              
1394 24 50         if (stream_id < 0) {
1395 0 0         if (dp) {
1396 0 0         if (dp->callback) SvREFCNT_dec(dp->callback);
1397 0 0         if (dp->user_data) SvREFCNT_dec(dp->user_data);
1398 0           Safefree(dp);
1399             }
1400 0           croak("nghttp2_submit_request failed: %s", nghttp2_strerror(stream_id));
1401             }
1402              
1403             /* Track data provider if we have one */
1404 24 100         if (dp) {
1405 6           dp->stream_id = stream_id;
1406 6           add_data_provider(ps, dp);
1407             }
1408              
1409 24 100         RETVAL = stream_id;
1410             OUTPUT:
1411             RETVAL
1412              
1413             # Submit RST_STREAM (reset a stream)
1414             int
1415             submit_rst_stream(self, stream_id, error_code)
1416             SV *self
1417             int stream_id
1418             unsigned int error_code
1419             PREINIT:
1420             nghttp2_perl_session *ps;
1421             int rv;
1422             CODE:
1423 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1424 0           rv = nghttp2_submit_rst_stream(ps->session, NGHTTP2_FLAG_NONE, stream_id, error_code);
1425 0 0         if (rv != 0) {
1426 0           croak("nghttp2_submit_rst_stream failed: %s", nghttp2_strerror(rv));
1427             }
1428 0 0         RETVAL = rv;
1429             OUTPUT:
1430             RETVAL
1431              
1432             # Submit PING frame
1433             int
1434             submit_ping(self, ack, opaque_data)
1435             SV *self
1436             int ack
1437             SV *opaque_data
1438             PREINIT:
1439             nghttp2_perl_session *ps;
1440             STRLEN len;
1441             const uint8_t *data;
1442             uint8_t flags;
1443             int rv;
1444             CODE:
1445 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1446 0           flags = ack ? NGHTTP2_FLAG_ACK : NGHTTP2_FLAG_NONE;
1447              
1448 0 0         if (SvOK(opaque_data)) {
1449 0           data = (const uint8_t *)SvPVbyte(opaque_data, len);
1450 0 0         if (len != 8) {
1451 0           croak("PING opaque_data must be exactly 8 bytes");
1452             }
1453             } else {
1454 0           data = NULL;
1455             }
1456              
1457 0           rv = nghttp2_submit_ping(ps->session, flags, data);
1458 0 0         if (rv != 0) {
1459 0           croak("nghttp2_submit_ping failed: %s", nghttp2_strerror(rv));
1460             }
1461 0 0         RETVAL = rv;
1462             OUTPUT:
1463             RETVAL
1464              
1465             # Submit WINDOW_UPDATE frame
1466             int
1467             submit_window_update(self, stream_id, window_size_increment)
1468             SV *self
1469             int stream_id
1470             int window_size_increment
1471             PREINIT:
1472             nghttp2_perl_session *ps;
1473             int rv;
1474             CODE:
1475 0           ps = (nghttp2_perl_session *)SvIV(SvRV(self));
1476 0           rv = nghttp2_submit_window_update(ps->session, NGHTTP2_FLAG_NONE, stream_id, window_size_increment);
1477 0 0         if (rv != 0) {
1478 0           croak("nghttp2_submit_window_update failed: %s", nghttp2_strerror(rv));
1479             }
1480 0 0         RETVAL = rv;
1481             OUTPUT:
1482             RETVAL