File Coverage

feersum_tls.c.inc
Criterion Covered Total %
statement 322 657 49.0
branch 173 474 36.5
condition n/a
subroutine n/a
pod n/a
total 495 1131 43.7


line stmt bran cond sub pod time code
1             /*
2             * feersum_tls.c.inc - TLS 1.3 support via picotls for Feersum
3             *
4             * This file is #included into Feersum.xs when FEERSUM_HAS_TLS is defined.
5             * It provides separate read/write callbacks for TLS connections so that
6             * plain HTTP connections are never disturbed.
7             *
8             * Flow:
9             * Plain: accept -> try_conn_read/write (unchanged)
10             * TLS+H1: accept -> try_tls_conn_read/write -> decrypt -> H1 parser
11             * TLS+H2: accept -> try_tls_conn_read/write -> decrypt -> nghttp2
12             */
13              
14             #ifdef FEERSUM_HAS_TLS
15              
16             /* ALPN protocols list for negotiation */
17             static ptls_iovec_t tls_alpn_protos[] = {
18             #ifdef FEERSUM_HAS_H2
19             { (uint8_t *)ALPN_H2 + 1, 2 }, /* "h2" */
20             #endif
21             { (uint8_t *)ALPN_HTTP11 + 1, 8 }, /* "http/1.1" */
22             };
23              
24             /* HTTP/1.1-only ALPN — used when h2 is not enabled on a listener */
25             static ptls_iovec_t tls_alpn_h1_only[] = {
26             { (uint8_t *)ALPN_HTTP11 + 1, 8 }, /* "http/1.1" */
27             };
28              
29             static int
30 51           negotiate_alpn(ptls_t *tls, const ptls_iovec_t *protos, size_t num_protos,
31             ptls_on_client_hello_parameters_t *params)
32             {
33             size_t i, j;
34 102 100         for (i = 0; i < num_protos; i++) {
35 51 50         for (j = 0; j < params->negotiated_protocols.count; j++) {
36 0 0         if (params->negotiated_protocols.list[j].len == protos[i].len &&
37 0           memcmp(params->negotiated_protocols.list[j].base, protos[i].base,
38 0 0         protos[i].len) == 0) {
39 0           ptls_set_negotiated_protocol(tls,
40 0           (const char *)protos[i].base, protos[i].len);
41 0           return 0;
42             }
43             }
44             }
45             /* No matching protocol; proceed without ALPN (will default to HTTP/1.1) */
46 51           return 0;
47             }
48              
49             /*
50             * SNI lookup: find a matching context for the client's server_name.
51             * If found, swap tls->ctx so picotls uses the right cert for the handshake.
52             */
53             static void
54 51           sni_lookup(ptls_t *tls, ptls_on_client_hello_parameters_t *params)
55             {
56 51           struct feer_listen *lsnr = *(struct feer_listen **)ptls_get_data_ptr(tls);
57 51 50         if (!lsnr || lsnr->n_sni_entries == 0 || !params->server_name.base)
    50          
    0          
58 51           return;
59              
60             /* Case-insensitive match against SNI entries */
61 0           size_t name_len = params->server_name.len;
62             int i;
63 0 0         for (i = 0; i < lsnr->n_sni_entries; i++) {
64 0           struct feer_sni_entry *e = &lsnr->sni_entries[i];
65 0 0         if (strlen(e->hostname) == name_len &&
66 0 0         strncasecmp(e->hostname, (const char *)params->server_name.base, name_len) == 0) {
67 0           ptls_set_context(tls, e->ctx_ref->ctx);
68 0           return;
69             }
70             }
71             /* No match — default context stays */
72             }
73              
74             static int
75 0           on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
76             ptls_on_client_hello_parameters_t *params)
77             {
78             PERL_UNUSED_VAR(self);
79 0           sni_lookup(tls, params);
80 0           return negotiate_alpn(tls, tls_alpn_protos,
81             sizeof(tls_alpn_protos) / sizeof(tls_alpn_protos[0]), params);
82             }
83              
84             static ptls_on_client_hello_t on_client_hello = { on_client_hello_cb };
85              
86             static int
87 51           on_client_hello_no_h2_cb(ptls_on_client_hello_t *self, ptls_t *tls,
88             ptls_on_client_hello_parameters_t *params)
89             {
90             PERL_UNUSED_VAR(self);
91 51           sni_lookup(tls, params);
92 51           return negotiate_alpn(tls, tls_alpn_h1_only,
93             sizeof(tls_alpn_h1_only) / sizeof(tls_alpn_h1_only[0]), params);
94             }
95              
96             static ptls_on_client_hello_t on_client_hello_no_h2 = { on_client_hello_no_h2_cb };
97              
98             static ptls_key_exchange_algorithm_t *feer_tls_key_exchanges[] = {
99             #if PTLS_OPENSSL_HAVE_X25519
100             &ptls_openssl_x25519,
101             #endif
102             &ptls_openssl_secp256r1,
103             NULL
104             };
105              
106             static ptls_cipher_suite_t *feer_tls_cipher_suites[] = {
107             &ptls_openssl_aes256gcmsha384,
108             &ptls_openssl_aes128gcmsha256,
109             #if PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
110             &ptls_openssl_chacha20poly1305sha256,
111             #endif
112             NULL
113             };
114              
115             /*
116             * Create a picotls server context from certificate and key files.
117             * Returns NULL on failure (with warnings emitted).
118             */
119             static ptls_context_t *
120 91           feer_tls_create_context(pTHX_ const char *cert_file, const char *key_file, int h2)
121             {
122             ptls_context_t *ctx;
123             FILE *fp;
124             int ret;
125              
126 91           Newxz(ctx, 1, ptls_context_t);
127              
128 91           ctx->random_bytes = ptls_openssl_random_bytes;
129 91           ctx->get_time = &ptls_get_time;
130 91           ctx->key_exchanges = feer_tls_key_exchanges;
131 91           ctx->cipher_suites = feer_tls_cipher_suites;
132              
133             /* Load certificate chain */
134 91           ret = ptls_load_certificates(ctx, cert_file);
135 91 100         if (ret != 0) {
136 3           warn("Feersum TLS: failed to load certificate from '%s' (error %d)\n",
137             cert_file, ret);
138 3           feer_tls_free_context(ctx);
139 3           return NULL;
140             }
141              
142             /* Load private key */
143 88           fp = fopen(key_file, "r");
144 88 100         if (!fp) {
145 2           warn("Feersum TLS: failed to open key file '%s': %s\n",
146             key_file, strerror(errno));
147 2           goto cert_cleanup;
148             }
149              
150 86           EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
151 86           fclose(fp);
152 86 50         if (!pkey) {
153 0           warn("Feersum TLS: failed to read private key from '%s'\n", key_file);
154 0           goto cert_cleanup;
155             }
156              
157             ptls_openssl_sign_certificate_t *sign_cert;
158 86           Newx(sign_cert, 1, ptls_openssl_sign_certificate_t);
159 86 50         if (ptls_openssl_init_sign_certificate(sign_cert, pkey) != 0) {
160 0           Safefree(sign_cert);
161 0           EVP_PKEY_free(pkey);
162 0           warn("Feersum TLS: incompatible private key type in '%s'\n", key_file);
163 0           goto cert_cleanup;
164             }
165 86           EVP_PKEY_free(pkey);
166 86           ctx->sign_certificate = &sign_cert->super;
167              
168             /* ALPN negotiation via on_client_hello callback */
169 86 50         if (h2)
170 0           ctx->on_client_hello = &on_client_hello;
171             else
172 86           ctx->on_client_hello = &on_client_hello_no_h2;
173              
174             trace("TLS context created: cert=%s key=%s h2=%d\n", cert_file, key_file, h2);
175 86           return ctx;
176              
177 2           cert_cleanup:
178 2           feer_tls_free_context(ctx);
179 2           return NULL;
180             }
181              
182             /*
183             * Free a TLS context and its resources.
184             */
185             static void
186 90           feer_tls_free_context(ptls_context_t *ctx)
187             {
188 90 50         if (!ctx) return;
189 90 50         if (ctx->certificates.list) {
190             size_t i;
191 177 100         for (i = 0; i < ctx->certificates.count; i++)
192 87           free(ctx->certificates.list[i].base);
193 90           free(ctx->certificates.list);
194             }
195             /* Free per-context sign certificate (allocated in feer_tls_create_context) */
196 90 100         if (ctx->sign_certificate) {
197 85           ptls_openssl_sign_certificate_t *sign_cert =
198             (ptls_openssl_sign_certificate_t *)ctx->sign_certificate;
199 85           ptls_openssl_dispose_sign_certificate(sign_cert);
200 85           Safefree(sign_cert);
201             }
202 90           Safefree(ctx);
203             }
204              
205             /*
206             * Reference-counted TLS context wrapper.
207             * Prevents use-after-free when set_tls rotates certs or accept_on_fd
208             * reuses a listener slot while active connections still hold ptls_t
209             * objects that reference the old context.
210             */
211             static struct feer_tls_ctx_ref *
212 86           feer_tls_ctx_ref_new(ptls_context_t *ctx)
213             {
214             struct feer_tls_ctx_ref *ref;
215 86           Newx(ref, 1, struct feer_tls_ctx_ref);
216 86           ref->ctx = ctx;
217 86           ref->refcount = 1;
218 86           return ref;
219             }
220              
221             INLINE_UNLESS_DEBUG static void
222 68           feer_tls_ctx_ref_inc(struct feer_tls_ctx_ref *ref)
223             {
224 68           ref->refcount++;
225 68           }
226              
227             static void
228 149           feer_tls_ctx_ref_dec(struct feer_tls_ctx_ref *ref)
229             {
230 149 100         if (--ref->refcount <= 0) {
231 85           feer_tls_free_context(ref->ctx);
232 85           Safefree(ref);
233             }
234 149           }
235              
236             /*
237             * Free all TLS state on a listener (default context + SNI entries).
238             */
239             static void
240 225           feer_tls_cleanup_listener(struct feer_listen *lsnr)
241             {
242             int i;
243 225 100         if (lsnr->tls_ctx_ref) {
244 83           feer_tls_ctx_ref_dec(lsnr->tls_ctx_ref);
245 83           lsnr->tls_ctx_ref = NULL;
246             }
247 227 100         for (i = 0; i < lsnr->n_sni_entries; i++) {
248 2           Safefree(lsnr->sni_entries[i].hostname);
249 2           feer_tls_ctx_ref_dec(lsnr->sni_entries[i].ctx_ref);
250             }
251 225           lsnr->n_sni_entries = 0;
252 225           }
253              
254             /*
255             * Initialize TLS state on a newly accepted connection.
256             */
257             static void
258 68           feer_tls_init_conn(struct feer_conn *c, struct feer_tls_ctx_ref *ref)
259             {
260 68           c->tls = ptls_new(ref->ctx, 1 /* is_server */);
261 68 50         if (unlikely(!c->tls)) {
262 0           trouble("ptls_new failed for fd=%d\n", c->fd);
263 0           return;
264             }
265             /* Store listener pointer for SNI lookup in on_client_hello */
266 68           *ptls_get_data_ptr(c->tls) = c->listener;
267 68           feer_tls_ctx_ref_inc(ref);
268 68           c->tls_ctx_ref = ref;
269 68           ptls_buffer_init(&c->tls_wbuf, "", 0);
270 68           c->tls_tunnel_sv0 = -1;
271 68           c->tls_tunnel_sv1 = -1;
272             }
273              
274             /*
275             * Free TLS state on connection destruction.
276             */
277             static void
278 625           feer_tls_free_conn(struct feer_conn *c)
279             {
280 625 100         if (c->tls) {
281 63           ptls_free(c->tls);
282 63           c->tls = NULL;
283             }
284 625 100         if (c->tls_ctx_ref) {
285 63           feer_tls_ctx_ref_dec(c->tls_ctx_ref);
286 63           c->tls_ctx_ref = NULL;
287             }
288 625           ptls_buffer_dispose(&c->tls_wbuf);
289 625 50         if (c->tls_rbuf) {
290 0           Safefree(c->tls_rbuf);
291 0           c->tls_rbuf = NULL;
292 0           c->tls_rbuf_len = 0;
293             }
294 625 100         if (c->tls_tunnel) {
295 9           ev_io_stop(feersum_ev_loop, &c->tls_tunnel_read_w);
296 9           ev_io_stop(feersum_ev_loop, &c->tls_tunnel_write_w);
297 9           c->tls_tunnel = 0;
298 9 50         if (c->tls_tunnel_sv0 >= 0) {
299 9           close(c->tls_tunnel_sv0);
300 9           c->tls_tunnel_sv0 = -1;
301             }
302 9 50         if (c->tls_tunnel_sv1 >= 0) {
303 0           close(c->tls_tunnel_sv1);
304 0           c->tls_tunnel_sv1 = -1;
305             }
306 9 50         if (c->tls_tunnel_wbuf) {
307             dTHX;
308 0           SvREFCNT_dec(c->tls_tunnel_wbuf);
309 0           c->tls_tunnel_wbuf = NULL;
310 0           c->tls_tunnel_wbuf_pos = 0;
311             }
312             }
313 625           }
314              
315             /*
316             * Flush accumulated encrypted data from tls_wbuf to the socket.
317             * Returns: number of bytes written, 0 if nothing to write, -1 on EAGAIN, -2 on error.
318             */
319             static int
320 140           feer_tls_flush_wbuf(struct feer_conn *c)
321             {
322 140 50         if (c->tls_wbuf.off == 0)
323 0           return 0;
324              
325 140           ssize_t written = write(c->fd, c->tls_wbuf.base, c->tls_wbuf.off);
326 140 50         if (written < 0) {
327 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
    0          
    0          
328 0           return -1;
329             }
330             trace("TLS flush write error fd=%d: %s\n", c->fd, strerror(errno));
331 0           return -2;
332             }
333              
334 140 50         if ((size_t)written < c->tls_wbuf.off) {
335             /* Partial write: shift remaining data to front */
336 0           memmove(c->tls_wbuf.base, c->tls_wbuf.base + written,
337 0           c->tls_wbuf.off - written);
338 0           c->tls_wbuf.off -= written;
339 0           return (int)written;
340             }
341              
342             /* Full write */
343 140           c->tls_wbuf.off = 0;
344 140           return (int)written;
345             }
346              
347             #define tls_wbuf_append(buf, src, len) ptls_buffer__do_pushv(buf, src, len)
348              
349             /*
350             * ======= TLS Tunnel (socketpair relay for io()/psgix.io over TLS) =======
351             *
352             * When io() is called on a TLS connection, we create a socketpair:
353             * sv[0] = Feersum's end (with ev_io watchers)
354             * sv[1] = handler's end (returned as IO handle)
355             *
356             * Data flow:
357             * App writes to sv[1] -> sv[0] readable -> encrypt via ptls -> send to TLS fd
358             * TLS fd readable -> decrypt via ptls -> write to sv[0] -> sv[1] readable for app
359             */
360              
361              
362             /*
363             * Try to write data to sv[0]; buffer any remainder in tls_tunnel_wbuf.
364             * Returns 0 on success, -1 on hard write error.
365             */
366             static int
367 13           tls_tunnel_write_or_buffer(struct feer_conn *c, const char *data, size_t len)
368             {
369             dTHX;
370 13 50         if (c->tls_tunnel_sv0 < 0) return -1;
371              
372 13           ssize_t nw = write(c->tls_tunnel_sv0, data, len);
373 13 50         if (nw == (ssize_t)len)
374 13           return 0;
375              
376 0 0         if (nw < 0) {
377 0 0         if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
    0          
    0          
378 0           return -1;
379 0           nw = 0;
380             }
381              
382 0           size_t remaining = len - nw;
383 0 0         if (!c->tls_tunnel_wbuf) {
384 0           c->tls_tunnel_wbuf = newSV_buf(remaining + 256);
385             }
386              
387             /* Compact consumed prefix to prevent unbounded SV growth */
388 0 0         if (c->tls_tunnel_wbuf_pos > 0) {
389 0           STRLEN remain = SvCUR(c->tls_tunnel_wbuf) - c->tls_tunnel_wbuf_pos;
390 0 0         if (remain > 0)
391 0           memmove(SvPVX(c->tls_tunnel_wbuf),
392 0           SvPVX(c->tls_tunnel_wbuf) + c->tls_tunnel_wbuf_pos,
393             remain);
394 0           SvCUR_set(c->tls_tunnel_wbuf, remain);
395 0           c->tls_tunnel_wbuf_pos = 0;
396             }
397              
398 0 0         if (SvCUR(c->tls_tunnel_wbuf) + remaining > FEER_TUNNEL_MAX_WBUF) {
399 0           trouble("TLS tunnel wbuf overflow fd=%d\n", c->fd);
400 0           return -1;
401             }
402              
403 0           sv_catpvn(c->tls_tunnel_wbuf, data + nw, remaining);
404 0 0         if (!ev_is_active(&c->tls_tunnel_write_w))
405 0           ev_io_start(feersum_ev_loop, &c->tls_tunnel_write_w);
406 0           return 0;
407             }
408              
409             /*
410             * ev_io callback: sv[0] is readable — app wrote data to sv[1].
411             * Read from sv[0], encrypt via picotls, send to TLS fd.
412             */
413             static int feer_tls_drain_one_record(struct feer_conn *c, ptls_buffer_t *decbuf);
414              
415             static void
416 13           drain_tls_tunnel_rbuf(struct feer_conn *c)
417             {
418 13 50         while (c->tls_tunnel && c->tls_rbuf_len > 0) {
    50          
419 0 0         if (ev_is_active(&c->tls_tunnel_write_w))
420 0           break; /* sv[0] full — let write watcher drain first */
421             ptls_buffer_t db;
422 0           int drain_rv = feer_tls_drain_one_record(c, &db);
423 0 0         if (drain_rv < 0) break;
424 0 0         if (drain_rv == 1) continue; /* non-data (e.g. KeyUpdate) */
425 0 0         if (tls_tunnel_write_or_buffer(c, (const char *)db.base, db.off) < 0) {
426 0           ptls_buffer_dispose(&db);
427 0           stop_all_watchers(c);
428 0           safe_close_conn(c, "TLS tunnel drain error");
429 0           change_responding_state(c, RESPOND_SHUTDOWN);
430 0           return;
431             }
432 0           ptls_buffer_dispose(&db);
433             }
434             }
435              
436             static void
437 23           tls_tunnel_sv0_read_cb(EV_P_ struct ev_io *w, int revents)
438             {
439 23           struct feer_conn *c = (struct feer_conn *)w->data;
440             PERL_UNUSED_VAR(revents);
441 23           SvREFCNT_inc_void_NN(c->self);
442              
443             char buf[FEER_TUNNEL_BUFSZ];
444 23           ssize_t nread = read(c->tls_tunnel_sv0, buf, sizeof(buf));
445              
446 23 100         if (nread == 0) {
447             /* App closed sv[1] — EOF; close connection */
448 2           stop_all_watchers(c);
449 2           safe_close_conn(c, "TLS tunnel EOF");
450 2           goto cleanup;
451             }
452 21 50         if (nread < 0) {
453 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
    0          
    0          
454 0           goto cleanup;
455 0           stop_all_watchers(c);
456 0           safe_close_conn(c, "TLS tunnel read error");
457 0           goto cleanup;
458             }
459              
460             /* Encrypt and queue for sending */
461 21 50         if (feer_tls_send(c, buf, nread) != 0) {
462 0           stop_all_watchers(c);
463 0           safe_close_conn(c, "TLS tunnel encrypt error");
464 0           goto cleanup;
465             }
466              
467             /* Flush encrypted data to socket */
468 21           int flush_ret = feer_tls_flush_wbuf(c);
469 21 50         if (flush_ret == -2) {
470 0           stop_all_watchers(c);
471 0           safe_close_conn(c, "TLS tunnel flush error");
472 0           goto cleanup;
473             }
474 21 50         if (c->tls_wbuf.off > 0)
475 0           start_write_watcher(c);
476              
477 21           cleanup:
478 23           SvREFCNT_dec(c->self);
479 23           }
480              
481             /*
482             * ev_io callback: sv[0] is writable — drain tls_tunnel_wbuf
483             * (decrypted client data -> app).
484             */
485             static void
486 0           tls_tunnel_sv0_write_cb(EV_P_ struct ev_io *w, int revents)
487             {
488             PERL_UNUSED_VAR(revents);
489 0           struct feer_conn *c = (struct feer_conn *)w->data;
490 0           SvREFCNT_inc_void_NN(c->self);
491              
492 0 0         if (!c->tls_tunnel_wbuf ||
493 0 0         SvCUR(c->tls_tunnel_wbuf) <= c->tls_tunnel_wbuf_pos) {
494 0           ev_io_stop(feersum_ev_loop, &c->tls_tunnel_write_w);
495 0 0         if (c->tls_tunnel_wbuf) {
496 0           SvCUR_set(c->tls_tunnel_wbuf, 0);
497 0           c->tls_tunnel_wbuf_pos = 0;
498             }
499 0           goto cleanup;
500             }
501              
502 0           STRLEN avail = SvCUR(c->tls_tunnel_wbuf) - c->tls_tunnel_wbuf_pos;
503 0           const char *ptr = SvPVX(c->tls_tunnel_wbuf) + c->tls_tunnel_wbuf_pos;
504 0           ssize_t nw = write(c->tls_tunnel_sv0, ptr, avail);
505              
506 0 0         if (nw < 0) {
507 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
    0          
    0          
508 0           goto cleanup;
509 0           stop_all_watchers(c);
510 0           safe_close_conn(c, "TLS tunnel write error");
511 0           goto cleanup;
512             }
513              
514 0           c->tls_tunnel_wbuf_pos += nw;
515 0 0         if (c->tls_tunnel_wbuf_pos >= SvCUR(c->tls_tunnel_wbuf)) {
516 0           ev_io_stop(feersum_ev_loop, &c->tls_tunnel_write_w);
517 0           SvCUR_set(c->tls_tunnel_wbuf, 0);
518 0           c->tls_tunnel_wbuf_pos = 0;
519              
520             /* Resume draining tls_rbuf now that sv[0] has space */
521 0 0         if (c->tls_rbuf_len > 0)
522 0           drain_tls_tunnel_rbuf(c);
523             }
524              
525 0           cleanup:
526 0           SvREFCNT_dec(c->self);
527 0           }
528              
529             /*
530             * Set up TLS tunnel socketpair for io()/psgix.io over TLS.
531             */
532             static void
533 9           feer_tls_setup_tunnel(struct feer_conn *c)
534             {
535 9 50         if (c->tls_tunnel) return;
536              
537             int sv[2];
538 9 50         if (feer_socketpair_nb(sv) < 0) {
539 0           trouble("socketpair/fcntl failed for TLS tunnel fd=%d: %s\n",
540             c->fd, strerror(errno));
541 0           return;
542             }
543              
544 9           c->tls_tunnel_sv0 = sv[0];
545 9           c->tls_tunnel_sv1 = sv[1];
546              
547             /* Read watcher: fires when app writes to sv[1] */
548 9           ev_io_init(&c->tls_tunnel_read_w, tls_tunnel_sv0_read_cb, sv[0], EV_READ);
549 9           c->tls_tunnel_read_w.data = (void *)c;
550 9           ev_io_start(feersum_ev_loop, &c->tls_tunnel_read_w);
551              
552             /* Write watcher: initialized but NOT started until we have data to write */
553 9           ev_io_init(&c->tls_tunnel_write_w, tls_tunnel_sv0_write_cb, sv[0], EV_WRITE);
554 9           c->tls_tunnel_write_w.data = (void *)c;
555              
556 9           c->tls_tunnel = 1;
557              
558             /* Flush any existing rbuf data through the tunnel */
559 9 50         if (c->rbuf && SvOK(c->rbuf) && SvCUR(c->rbuf) > 0) {
    50          
    50          
560             dTHX;
561 0 0         if (tls_tunnel_write_or_buffer(c, SvPVX(c->rbuf), SvCUR(c->rbuf)) < 0) {
562 0           trouble("TLS tunnel rbuf flush failed fd=%d\n", c->fd);
563             }
564 0           SvCUR_set(c->rbuf, 0);
565             }
566              
567             /* Keep TLS read watcher active to receive and relay client data */
568 9           start_read_watcher(c);
569              
570             trace("TLS tunnel socketpair established fd=%d sv0=%d sv1=%d\n",
571             c->fd, sv[0], sv[1]);
572             }
573              
574             /*
575             * Decrypt one TLS record from tls_rbuf into decbuf.
576             * Returns: 0 = success (caller must ptls_buffer_dispose)
577             * 1 = no app data (KeyUpdate/NewSessionTicket) but no error;
578             * caller should retry if tls_rbuf still has data
579             * -1 = error or no data available
580             */
581             static int
582 60           feer_tls_drain_one_record(struct feer_conn *c, ptls_buffer_t *decbuf)
583             {
584 60 50         if (!c->tls_rbuf || c->tls_rbuf_len == 0) return -1;
    0          
585              
586 0           uint8_t *saved = c->tls_rbuf;
587 0           size_t saved_len = c->tls_rbuf_len;
588 0           c->tls_rbuf = NULL;
589 0           c->tls_rbuf_len = 0;
590              
591 0           ptls_buffer_init(decbuf, "", 0);
592 0           size_t consumed = saved_len;
593 0           int ret = ptls_receive(c->tls, decbuf, saved, &consumed);
594 0 0         if (ret == 0 && consumed < saved_len) {
    0          
595 0           size_t rem = saved_len - consumed;
596 0           Newx(c->tls_rbuf, rem, uint8_t);
597 0           memcpy(c->tls_rbuf, saved + consumed, rem);
598 0           c->tls_rbuf_len = rem;
599             }
600 0           Safefree(saved);
601              
602 0 0         if (ret != 0) {
603 0           ptls_buffer_dispose(decbuf);
604 0           return -1;
605             }
606 0 0         if (decbuf->off == 0) {
607             /* TLS 1.3 non-data record (KeyUpdate, NewSessionTicket) — no app
608             * bytes but not an error. Caller should retry if tls_rbuf remains. */
609 0           ptls_buffer_dispose(decbuf);
610 0           return 1;
611             }
612 0           return 0;
613             }
614              
615             #ifdef FEERSUM_HAS_H2
616             /* Drain buffered TLS records and feed them to nghttp2.
617             * Called after initial decrypt to process any remaining records in tls_rbuf. */
618             static void
619             drain_h2_tls_records(struct feer_conn *c)
620             {
621             dTHX;
622             while (c->h2_session) {
623             ptls_buffer_t db;
624             int drain_rv = feer_tls_drain_one_record(c, &db);
625             if (drain_rv < 0) break;
626             if (drain_rv == 1) continue; /* non-data TLS record */
627             feer_h2_session_recv(c, db.base, db.off);
628             ptls_buffer_dispose(&db);
629             if (c->fd < 0) break;
630             feer_h2_session_send(c);
631             h2_check_stream_poll_cbs(aTHX_ c);
632             restart_read_timer(c);
633             }
634             }
635             #endif
636              
637             /*
638             * Reads encrypted data from socket, performs TLS handshake or decryption,
639             * then feeds plaintext to either H1 parser or nghttp2.
640             */
641             static void
642 348           try_tls_conn_read(EV_P_ ev_io *w, int revents)
643             {
644 348           struct feer_conn *c = (struct feer_conn *)w->data;
645             PERL_UNUSED_VAR(revents);
646             PERL_UNUSED_VAR(loop);
647              
648             dTHX;
649 348           SvREFCNT_inc_void_NN(c->self); /* prevent premature free during callback */
650 348           feer_conn_set_busy(c);
651             trace("tls_conn_read fd=%d hs_done=%d\n", c->fd, c->tls_handshake_done);
652              
653 348           ssize_t got_n = 0;
654              
655 348 100         if (unlikely(c->pipelined)) goto tls_pipelined;
656              
657 347 50         if (unlikely(!c->tls)) {
658 0           trouble("tls_conn_read: no TLS context fd=%d\n", c->fd);
659 0           change_receiving_state(c, RECEIVE_SHUTDOWN);
660 0           stop_all_watchers(c);
661 0           safe_close_conn(c, "no TLS context");
662 0           goto tls_read_cleanup;
663             }
664              
665             uint8_t rawbuf[TLS_RAW_BUFSZ];
666 347           ssize_t nread = read(c->fd, rawbuf, sizeof(rawbuf));
667              
668 347 100         if (nread == 0) {
669             /* EOF */
670             trace("TLS EOF fd=%d\n", c->fd);
671 7           change_receiving_state(c, RECEIVE_SHUTDOWN);
672 7           stop_all_watchers(c);
673 7           safe_close_conn(c, "TLS EOF");
674 7           goto tls_read_cleanup;
675             }
676              
677 340 50         if (nread < 0) {
678 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
    0          
    0          
679 0           goto tls_read_cleanup;
680             trace("TLS read error fd=%d: %s\n", c->fd, strerror(errno));
681 0           change_receiving_state(c, RECEIVE_SHUTDOWN);
682 0           stop_all_watchers(c);
683 0           safe_close_conn(c, "TLS read error");
684 0           goto tls_read_cleanup;
685             }
686              
687             /* PROXY protocol: raw PROXY header arrives before the TLS handshake.
688             * Buffer bytes into c->rbuf, parse the PROXY header, then either:
689             * - fall through to TLS handshake if leftover bytes are present, or
690             * - wait for next read event if the PROXY header consumed all data. */
691 340 100         if (unlikely(c->receiving == RECEIVE_PROXY_HEADER)) {
692 152 100         if (!c->rbuf) {
693 23           c->rbuf = newSV_buf(nread + 256);
694             }
695 152           sv_catpvn(c->rbuf, (const char *)rawbuf, nread);
696              
697 152           int ret = try_parse_proxy_header(c);
698 152 100         if (ret == -1) {
699 4           stop_all_watchers(c);
700 4           safe_close_conn(c, "Invalid PROXY header on TLS listener");
701 4           goto tls_read_cleanup;
702             }
703 148 100         if (ret == -2) goto tls_read_cleanup; /* need more data */
704              
705             /* PROXY header parsed successfully — consume parsed bytes */
706 19           STRLEN remaining = SvCUR(c->rbuf) - ret;
707              
708             /* Clear cached remote addr/port so they regenerate from new sockaddr */
709 19 50         feer_clear_remote_cache(c);
    50          
710              
711 19           change_receiving_state(c, RECEIVE_HEADERS);
712              
713 19 50         if (remaining > 0) {
714             /* Leftover bytes are the start of the TLS ClientHello.
715             * Save in tls_rbuf (heap) to avoid overflowing stack rawbuf
716             * when the PROXY header spans multiple reads. */
717 0           Newx(c->tls_rbuf, remaining, uint8_t);
718 0           memcpy(c->tls_rbuf, SvPVX(c->rbuf) + ret, remaining);
719 0           c->tls_rbuf_len = remaining;
720 0           nread = 0;
721             }
722 19           SvREFCNT_dec(c->rbuf);
723 19           c->rbuf = NULL;
724 19 50         if (remaining == 0)
725 19           goto tls_read_cleanup; /* wait for TLS ClientHello on next read */
726             /* Fall through to TLS handshake with rawbuf containing TLS data */
727             }
728              
729             /* Merge any saved partial TLS record bytes with new data.
730             * ptls_receive/ptls_handshake may not consume all input when a TLS record
731             * spans two socket reads. Unconsumed bytes are saved in tls_rbuf and
732             * prepended to the next read here. */
733 188           uint8_t *inbuf = rawbuf;
734 188           size_t inlen = (size_t)nread;
735 188           uint8_t *merged = NULL;
736              
737 188 50         if (c->tls_rbuf_len > 0) {
738 0 0         if (nread > 0) {
739 0           inlen = c->tls_rbuf_len + (size_t)nread;
740 0           Newx(merged, inlen, uint8_t);
741 0           memcpy(merged, c->tls_rbuf, c->tls_rbuf_len);
742 0           memcpy(merged + c->tls_rbuf_len, rawbuf, (size_t)nread);
743 0           inbuf = merged;
744 0           Safefree(c->tls_rbuf);
745             } else {
746             /* PROXY leftover only — transfer ownership to merged, no copy */
747 0           merged = c->tls_rbuf;
748 0           inbuf = merged;
749 0           inlen = c->tls_rbuf_len;
750             }
751 0           c->tls_rbuf = NULL;
752 0           c->tls_rbuf_len = 0;
753             }
754              
755 188 100         if (!c->tls_handshake_done) {
756             /* TLS handshake in progress */
757 64           size_t consumed = inlen;
758             ptls_buffer_t hsbuf;
759 64           ptls_buffer_init(&hsbuf, "", 0);
760              
761 64           int ret = ptls_handshake(c->tls, &hsbuf, inbuf, &consumed, NULL);
762              
763 64 100         if (hsbuf.off > 0) {
764 55 50         if (tls_wbuf_append(&c->tls_wbuf, hsbuf.base, hsbuf.off) != 0) {
765 0           trouble("TLS wbuf alloc failed during handshake fd=%d\n", c->fd);
766 0           ptls_buffer_dispose(&hsbuf);
767 0 0         if (merged) Safefree(merged);
768 0           safe_close_conn(c, "TLS allocation failure");
769 0           goto tls_read_cleanup;
770             }
771 55           int flush_ret = feer_tls_flush_wbuf(c);
772 55 50         if (flush_ret == -2) {
773 0           trouble("TLS flush error during handshake fd=%d\n", c->fd);
774 0           ptls_buffer_dispose(&hsbuf);
775 0 0         if (merged) Safefree(merged);
776 0           safe_close_conn(c, "TLS handshake flush error");
777 0           goto tls_read_cleanup;
778             }
779 55 50         if (flush_ret == -1 || c->tls_wbuf.off > 0) {
    50          
780             /* Need to wait for write readiness (EAGAIN or partial write) */
781 0           start_write_watcher(c);
782             }
783             }
784 64           ptls_buffer_dispose(&hsbuf);
785              
786 64 100         if (ret == 0) {
787             /* Handshake complete */
788 51           c->tls_handshake_done = 1;
789             trace("TLS handshake complete fd=%d\n", c->fd);
790              
791             /* Check ALPN result */
792 51           const char *proto = ptls_get_negotiated_protocol(c->tls);
793 51 50         if (proto && strlen(proto) == 2 && memcmp(proto, "h2", 2) == 0) {
    0          
    0          
794 0           c->tls_alpn_h2 = 1;
795             trace("TLS ALPN: h2 negotiated fd=%d\n", c->fd);
796             #ifdef FEERSUM_HAS_H2
797             feer_h2_init_session(c);
798             #endif
799             } else {
800             trace("TLS ALPN: http/1.1 (or none) fd=%d\n", c->fd);
801             }
802              
803             /* Process any remaining data after handshake */
804 51 50         if (consumed < inlen) {
805 0           size_t remaining = inlen - consumed;
806 0           uint8_t *extra = inbuf + consumed;
807              
808             #ifdef FEERSUM_HAS_H2
809             if (c->h2_session) {
810             /* Feed to nghttp2 */
811             ptls_buffer_t decbuf;
812             ptls_buffer_init(&decbuf, "", 0);
813             size_t dec_consumed = remaining;
814             int dec_ret = ptls_receive(c->tls, &decbuf, extra, &dec_consumed);
815             if (dec_ret != 0) {
816             ptls_buffer_dispose(&decbuf);
817             if (merged) Safefree(merged);
818             stop_all_watchers(c);
819             safe_close_conn(c, "TLS receive error (post-handshake H2)");
820             goto tls_read_cleanup;
821             }
822             if (dec_consumed < remaining) {
823             size_t leftover = remaining - dec_consumed;
824             Newx(c->tls_rbuf, leftover, uint8_t);
825             memcpy(c->tls_rbuf, extra + dec_consumed, leftover);
826             c->tls_rbuf_len = leftover;
827             }
828             if (decbuf.off > 0) {
829             feer_h2_session_recv(c, decbuf.base, decbuf.off);
830             }
831             ptls_buffer_dispose(&decbuf);
832             if (c->fd < 0) {
833             if (merged) Safefree(merged);
834             goto tls_read_cleanup;
835             }
836             /* Send any pending nghttp2 frames (SETTINGS etc.) */
837             feer_h2_session_send(c);
838             h2_check_stream_poll_cbs(aTHX_ c);
839             restart_read_timer(c);
840              
841             drain_h2_tls_records(c);
842              
843             if (merged) Safefree(merged);
844             goto tls_read_cleanup;
845             }
846             #endif
847             /* H1: decrypt and feed to HTTP parser */
848             ptls_buffer_t decbuf;
849 0           ptls_buffer_init(&decbuf, "", 0);
850 0           size_t dec_consumed = remaining;
851 0           int dec_ret = ptls_receive(c->tls, &decbuf, extra, &dec_consumed);
852 0 0         if (dec_ret != 0) {
853 0           ptls_buffer_dispose(&decbuf);
854 0 0         if (merged) Safefree(merged);
855 0           stop_all_watchers(c);
856 0           safe_close_conn(c, "TLS receive error (post-handshake H1)");
857 0           goto tls_read_cleanup;
858             }
859 0 0         if (dec_consumed < remaining) {
860 0           size_t leftover = remaining - dec_consumed;
861 0           Newx(c->tls_rbuf, leftover, uint8_t);
862 0           memcpy(c->tls_rbuf, extra + dec_consumed, leftover);
863 0           c->tls_rbuf_len = leftover;
864             }
865 0 0         if (decbuf.off > 0) {
866 0           size_t decrypted_len = decbuf.off;
867 0 0         if (!c->rbuf) {
868 0           c->rbuf = newSV_buf(decrypted_len + READ_BUFSZ);
869             }
870 0           sv_catpvn(c->rbuf, (const char *)decbuf.base, decrypted_len);
871 0           ptls_buffer_dispose(&decbuf);
872              
873 0           restart_read_timer(c);
874 0           int parse_ret = try_parse_http(c, decrypted_len);
875 0 0         if (parse_ret == -1) {
876 0           respond_with_server_error(c, "Malformed request\n", 400);
877 0           finish_receiving(c);
878 0 0         if (merged) Safefree(merged);
879 0           goto tls_read_cleanup;
880             }
881 0 0         if (parse_ret > 0) {
882 0 0         if (!process_request_headers(c, parse_ret))
883 0           finish_receiving(c);
884             }
885             /* Drain remaining tls_rbuf records so data from the same
886             * TCP segment as the handshake doesn't sit unprocessed.
887             * Handles both incomplete headers (parse_ret == -2) and
888             * body data following complete headers (parse_ret > 0). */
889 0 0         if (c->tls_rbuf_len > 0) {
890             ptls_buffer_t db;
891 0 0         while (c->tls_rbuf_len > 0) {
892 0           int drain_rv = feer_tls_drain_one_record(c, &db);
893 0 0         if (drain_rv < 0) break;
894 0 0         if (drain_rv == 1) continue;
895 0           sv_catpvn(c->rbuf, (const char *)db.base, db.off);
896 0           size_t added = db.off;
897 0           ptls_buffer_dispose(&db);
898 0 0         if (parse_ret == -2) {
899             /* Still parsing headers — feed to HTTP parser */
900 0           restart_read_timer(c);
901 0           int pr = try_parse_http(c, added);
902 0 0         if (pr == -1) {
903 0           respond_with_server_error(c, "Malformed request\n", 400);
904 0           finish_receiving(c);
905 0           break;
906             }
907 0 0         if (pr > 0) {
908 0 0         if (!process_request_headers(c, pr))
909 0           finish_receiving(c);
910 0           parse_ret = pr; /* headers now complete */
911             }
912             }
913             /* parse_ret > 0: body data appended to rbuf */
914             }
915             /* If headers are done and body was drained, check
916             * if the full body is now in rbuf. */
917 0 0         if (parse_ret > 0 && c->receiving == RECEIVE_BODY) {
    0          
918 0           c->received_cl = SvCUR(c->rbuf);
919 0 0         if (c->expected_cl > 0
920 0 0         && c->received_cl >= c->expected_cl) {
921 0           sched_request_callback(c);
922 0           finish_receiving(c);
923             }
924             }
925             }
926             } else {
927 0           ptls_buffer_dispose(&decbuf);
928             }
929             }
930 13 100         } else if (ret == PTLS_ERROR_IN_PROGRESS) {
931             /* Handshake still in progress, wait for more data */
932             trace("TLS handshake in progress fd=%d\n", c->fd);
933             /* Save unconsumed bytes (partial TLS handshake record) */
934 9 50         if (consumed < inlen) {
935 0           size_t leftover = inlen - consumed;
936 0           Newx(c->tls_rbuf, leftover, uint8_t);
937 0           memcpy(c->tls_rbuf, inbuf + consumed, leftover);
938 0           c->tls_rbuf_len = leftover;
939             }
940             } else {
941             /* Handshake error */
942             trace("TLS handshake error fd=%d ret=%d\n", c->fd, ret);
943 4           stop_all_watchers(c);
944 4           safe_close_conn(c, "TLS handshake error");
945             }
946 64 50         if (merged) Safefree(merged);
947 64           goto tls_read_cleanup;
948             }
949              
950             /* Handshake is done - decrypt application data */
951             {
952             ptls_buffer_t decbuf;
953 124           ptls_buffer_init(&decbuf, "", 0);
954 124           size_t consumed = inlen;
955 124           int ret = ptls_receive(c->tls, &decbuf, inbuf, &consumed);
956              
957             /* Save unconsumed bytes for next read (partial TLS record) */
958 124 50         if (ret == 0 && consumed < inlen) {
    50          
959 0           size_t remaining = inlen - consumed;
960 0           Newx(c->tls_rbuf, remaining, uint8_t);
961 0           memcpy(c->tls_rbuf, inbuf + consumed, remaining);
962 0           c->tls_rbuf_len = remaining;
963             }
964              
965 124 50         if (merged) Safefree(merged);
966              
967 124 50         if (ret != 0) {
968             trace("TLS receive error fd=%d ret=%d\n", c->fd, ret);
969             if (ret == PTLS_ALERT_CLOSE_NOTIFY) {
970             trace("TLS close_notify fd=%d\n", c->fd);
971             }
972 0           ptls_buffer_dispose(&decbuf);
973 0           change_receiving_state(c, RECEIVE_SHUTDOWN);
974 0           stop_all_watchers(c);
975 0           safe_close_conn(c, "TLS receive error");
976 64           goto tls_read_cleanup;
977             }
978              
979 124 100         if (decbuf.off == 0) {
980 51           ptls_buffer_dispose(&decbuf);
981 51           goto tls_read_cleanup; /* No application data yet */
982             }
983              
984             #ifdef FEERSUM_HAS_H2
985             if (c->h2_session) {
986             /* Feed decrypted data to nghttp2 */
987             feer_h2_session_recv(c, decbuf.base, decbuf.off);
988             ptls_buffer_dispose(&decbuf);
989             if (c->fd < 0) goto tls_read_cleanup;
990             /* Send any pending nghttp2 frames */
991             feer_h2_session_send(c);
992             h2_check_stream_poll_cbs(aTHX_ c);
993             restart_read_timer(c);
994              
995             drain_h2_tls_records(c);
996              
997             goto tls_read_cleanup;
998             }
999             #endif
1000              
1001             /* TLS tunnel: relay decrypted data to sv[0] for the app */
1002 73 100         if (c->tls_tunnel) {
1003 13 50         if (tls_tunnel_write_or_buffer(c, (const char *)decbuf.base, decbuf.off) < 0) {
1004 0           ptls_buffer_dispose(&decbuf);
1005 0           stop_all_watchers(c);
1006 0           safe_close_conn(c, "TLS tunnel write error");
1007 0           change_responding_state(c, RESPOND_SHUTDOWN);
1008 0           goto tls_read_cleanup;
1009             }
1010 13           ptls_buffer_dispose(&decbuf);
1011              
1012             /* Drain remaining TLS records from tls_rbuf into the tunnel.
1013             * Multiple records may have been read in one syscall above;
1014             * without draining, they sit in tls_rbuf until new socket data
1015             * arrives — deadlocking if the remote is waiting for a reply. */
1016 13           drain_tls_tunnel_rbuf(c);
1017              
1018 13           goto tls_read_cleanup;
1019             }
1020              
1021             /* HTTP/1.1 over TLS: append decrypted data to rbuf */
1022 60           got_n = (ssize_t)decbuf.off;
1023 60 100         if (!c->rbuf) {
1024 51           c->rbuf = newSV_buf(got_n + READ_BUFSZ);
1025             }
1026 60           sv_catpvn(c->rbuf, (const char *)decbuf.base, decbuf.off);
1027 60           ptls_buffer_dispose(&decbuf);
1028              
1029             /* Drain remaining TLS records from tls_rbuf */
1030             {
1031             ptls_buffer_t db;
1032             int drain_rv;
1033 60 50         while ((drain_rv = feer_tls_drain_one_record(c, &db)) >= 0) {
1034 0 0         if (drain_rv == 1) continue; /* non-data TLS record */
1035 0           got_n += (ssize_t)db.off;
1036 0           sv_catpvn(c->rbuf, (const char *)db.base, db.off);
1037 0           ptls_buffer_dispose(&db);
1038             }
1039             }
1040             }
1041 60           goto tls_parse;
1042              
1043 1           tls_pipelined:
1044 1           got_n = c->pipelined;
1045 1           c->pipelined = 0;
1046              
1047 61           tls_parse:
1048 61           restart_read_timer(c);
1049 61 100         if (c->receiving == RECEIVE_WAIT)
1050 8           change_receiving_state(c, RECEIVE_HEADERS);
1051              
1052 61 100         if (likely(c->receiving <= RECEIVE_HEADERS)) {
1053 59           int parse_ret = try_parse_http(c, (size_t)got_n);
1054 59 50         if (parse_ret == -1) {
1055 0           respond_with_server_error(c, "Malformed request\n", 400);
1056 0           finish_receiving(c);
1057 0           goto tls_read_cleanup;
1058             }
1059 59 50         if (parse_ret == -2) {
1060             /* Incomplete, wait for more data (read watcher already active) */
1061 0           goto tls_read_cleanup;
1062             }
1063             /* Headers complete. parse_ret = body offset */
1064 59 100         if (!process_request_headers(c, parse_ret))
1065 57           finish_receiving(c);
1066             }
1067 2 50         else if (likely(c->receiving == RECEIVE_BODY)) {
1068 2           c->received_cl += got_n;
1069 2 50         if (c->received_cl >= c->expected_cl) {
1070 2           sched_request_callback(c);
1071 2           finish_receiving(c);
1072             }
1073             }
1074 0 0         else if (c->receiving == RECEIVE_CHUNKED) {
1075 0           int ret = try_parse_chunked(c);
1076 0 0         if (ret == -1) {
1077 0           respond_with_server_error(c, "Malformed chunked encoding\n", 400);
1078 0           finish_receiving(c);
1079             }
1080 0 0         else if (ret == 0) {
1081 0           sched_request_callback(c);
1082 0           finish_receiving(c);
1083             }
1084             /* ret == 1: need more data, watcher stays active */
1085             }
1086 0 0         else if (c->receiving == RECEIVE_STREAMING) {
1087 0           c->received_cl += got_n;
1088 0 0         if (c->poll_read_cb) {
1089 0           call_poll_callback(c, 0);
1090             }
1091 0 0         if (c->receiving >= RECEIVE_SHUTDOWN) {
1092 0           finish_receiving(c);
1093 0           goto tls_read_cleanup;
1094             }
1095 0 0         if (c->expected_cl > 0 && c->received_cl >= c->expected_cl)
    0          
1096 0           finish_receiving(c);
1097             }
1098              
1099 0           tls_read_cleanup:
1100 348           SvREFCNT_dec(c->self);
1101 348           }
1102              
1103             /*
1104             * try_tls_conn_write - libev write callback for TLS connections.
1105             *
1106             * Encrypts pending response data via ptls and writes to socket.
1107             * Also handles sendfile-over-TLS (pread + encrypt + write).
1108             */
1109             static void
1110 60           try_tls_conn_write(EV_P_ ev_io *w, int revents)
1111             {
1112 60           struct feer_conn *c = (struct feer_conn *)w->data;
1113             PERL_UNUSED_VAR(revents);
1114             PERL_UNUSED_VAR(loop);
1115              
1116             dTHX;
1117 60           SvREFCNT_inc_void_NN(c->self); /* prevent premature free during callback */
1118             trace("tls_conn_write fd=%d\n", c->fd);
1119              
1120 60 50         if (unlikely(!c->tls)) {
1121 0           trouble("tls_conn_write: no TLS context fd=%d\n", c->fd);
1122 0           stop_write_watcher(c);
1123 0           goto tls_write_cleanup;
1124             }
1125              
1126             /* First, flush any pending encrypted data from TLS handshake or previous writes */
1127 60 50         if (c->tls_wbuf.off > 0) {
1128 0           int flush_ret = feer_tls_flush_wbuf(c);
1129 0 0         if (flush_ret == -1) goto tls_write_cleanup; /* EAGAIN, keep write watcher active */
1130 0 0         if (flush_ret == -2) goto tls_write_error;
1131             /* If there's still data after partial flush, keep trying */
1132 0 0         if (c->tls_wbuf.off > 0) goto tls_write_cleanup;
1133             }
1134              
1135             #ifdef FEERSUM_HAS_H2
1136             if (c->h2_session) {
1137             /* For H2, nghttp2 manages the write buffer.
1138             * Call session_send to generate frames, encrypt, and write. */
1139             feer_h2_session_send(c);
1140             h2_check_stream_poll_cbs(aTHX_ c);
1141             if (c->tls_wbuf.off > 0) {
1142             int flush_ret = feer_tls_flush_wbuf(c);
1143             if (flush_ret == -1) goto tls_write_cleanup;
1144             if (flush_ret == -2) goto tls_write_error;
1145             }
1146             if (c->tls_wbuf.off == 0) {
1147             stop_write_watcher(c);
1148             }
1149             goto tls_write_cleanup;
1150             }
1151             #endif
1152              
1153             /* HTTP/1.1 over TLS: encrypt wbuf_rinq (headers/body) first, then sendfile */
1154              
1155             /* Pre-encryption low-water check: if buffer is below threshold, let
1156             * poll_cb refill before we start encrypting (matches H1 plain path). */
1157 60 50         if (c->wbuf_rinq && c->cached_wbuf_low_water > 0
    100          
1158 1 50         && c->wbuf_len <= c->cached_wbuf_low_water
1159 1 50         && c->responding == RESPOND_STREAMING && c->poll_write_cb) {
    50          
1160 1 50         if (c->poll_write_cb_is_io_handle)
1161 0           pump_io_handle(c);
1162             else
1163 1           call_poll_callback(c, 1);
1164             }
1165              
1166             /* Encrypt data from wbuf_rinq (must come before sendfile to send headers first) */
1167 60 50         if (c->wbuf_rinq) {
1168             struct iomatrix *m;
1169 123 100         while ((m = (struct iomatrix *)rinq_shift(&c->wbuf_rinq)) != NULL) {
1170             unsigned int i;
1171 128 100         for (i = 0; i < m->count; i++) {
1172 65 50         if (m->iov[i].iov_len == 0) continue;
1173              
1174 65           c->wbuf_len -= m->iov[i].iov_len;
1175              
1176             ptls_buffer_t encbuf;
1177 65           ptls_buffer_init(&encbuf, "", 0);
1178 65           int ret = ptls_send(c->tls, &encbuf,
1179 65           m->iov[i].iov_base, m->iov[i].iov_len);
1180 65 50         if (ret != 0) {
1181             unsigned int j;
1182 0           ptls_buffer_dispose(&encbuf);
1183 0           trouble("ptls_send error fd=%d ret=%d\n", c->fd, ret);
1184 0 0         for (j = 0; j < m->count; j++) {
1185 0 0         if (m->sv[j]) SvREFCNT_dec(m->sv[j]);
1186             }
1187 0 0         IOMATRIX_FREE(m);
1188 0           goto tls_write_error;
1189             }
1190 65 50         if (encbuf.off > 0) {
1191 65 50         if (tls_wbuf_append(&c->tls_wbuf, encbuf.base, encbuf.off) != 0) {
1192             unsigned int j;
1193 0           ptls_buffer_dispose(&encbuf);
1194 0           trouble("TLS wbuf alloc failed fd=%d\n", c->fd);
1195 0 0         for (j = 0; j < m->count; j++) {
1196 0 0         if (m->sv[j]) SvREFCNT_dec(m->sv[j]);
1197             }
1198 0 0         IOMATRIX_FREE(m);
1199 0           goto tls_write_error;
1200             }
1201             }
1202 65           ptls_buffer_dispose(&encbuf);
1203             }
1204              
1205             /* Free the iomatrix SVs */
1206 128 100         for (i = 0; i < m->count; i++) {
1207 65 50         if (m->sv[i]) SvREFCNT_dec(m->sv[i]);
1208             }
1209 63 50         IOMATRIX_FREE(m);
1210              
1211             /* Low-water-mark: fire poll_cb to refill before encrypting more */
1212 63 100         if (c->cached_wbuf_low_water > 0
1213 4 50         && c->wbuf_len <= c->cached_wbuf_low_water
1214 4 100         && c->responding == RESPOND_STREAMING && c->poll_write_cb) {
    50          
1215 3 50         if (c->poll_write_cb_is_io_handle)
1216 0           pump_io_handle(c);
1217             else
1218 3           call_poll_callback(c, 1);
1219             /* poll_cb may have added more data — loop continues */
1220             }
1221             }
1222              
1223             /* Flush all encrypted data */
1224 60           int flush_ret = feer_tls_flush_wbuf(c);
1225 60 50         if (flush_ret == -1) goto tls_write_cleanup; /* EAGAIN */
1226 60 50         if (flush_ret == -2) goto tls_write_error;
1227 60 50         if (flush_ret > 0) restart_write_timer(c);
1228             }
1229              
1230             /* Handle sendfile over TLS: pread + encrypt + write */
1231 60 100         if (c->sendfile_fd >= 0 && c->sendfile_remain > 0) {
    50          
1232             uint8_t filebuf[TLS_RAW_BUFSZ];
1233 2           size_t to_read = c->sendfile_remain;
1234 2 50         if (to_read > sizeof(filebuf)) to_read = sizeof(filebuf);
1235              
1236 2           ssize_t file_nread = pread(c->sendfile_fd, filebuf, to_read, c->sendfile_off);
1237 2 50         if (file_nread < 0) {
1238 0 0         if (errno == EINTR)
1239 0           goto tls_write_cleanup; /* retry on next watcher fire */
1240 0           trouble("TLS pread(sendfile_fd) fd=%d: %s\n", c->fd, strerror(errno));
1241 0 0         CLOSE_SENDFILE_FD(c);
    0          
1242 0           change_responding_state(c, RESPOND_SHUTDOWN);
1243 2           goto tls_write_finished;
1244             }
1245 2 50         if (file_nread == 0) {
1246 0 0         CLOSE_SENDFILE_FD(c);
    0          
1247 0           change_responding_state(c, RESPOND_SHUTDOWN);
1248 0           goto tls_write_finished;
1249             }
1250              
1251             /* Encrypt file data */
1252             ptls_buffer_t encbuf;
1253 2           ptls_buffer_init(&encbuf, "", 0);
1254 2           int ret = ptls_send(c->tls, &encbuf, filebuf, file_nread);
1255 2 50         if (ret != 0) {
1256 0           ptls_buffer_dispose(&encbuf);
1257 0           trouble("ptls_send(sendfile) error fd=%d ret=%d\n", c->fd, ret);
1258 0 0         CLOSE_SENDFILE_FD(c);
    0          
1259 0           goto tls_write_error;
1260             }
1261              
1262             /* Queue encrypted data */
1263 2 50         if (encbuf.off > 0) {
1264 2 50         if (tls_wbuf_append(&c->tls_wbuf, encbuf.base, encbuf.off) != 0) {
1265 0           ptls_buffer_dispose(&encbuf);
1266 0           trouble("TLS wbuf alloc failed (sendfile) fd=%d\n", c->fd);
1267 0 0         CLOSE_SENDFILE_FD(c);
    0          
1268 0           goto tls_write_error;
1269             }
1270             }
1271 2           ptls_buffer_dispose(&encbuf);
1272              
1273 2           c->sendfile_off += file_nread;
1274 2           c->sendfile_remain -= file_nread;
1275              
1276 2 50         if (c->sendfile_remain == 0) {
1277 2 50         CLOSE_SENDFILE_FD(c);
    50          
1278 2           change_responding_state(c, RESPOND_SHUTDOWN);
1279             }
1280              
1281             /* Flush encrypted data */
1282             {
1283 2           int sf_flush_ret = feer_tls_flush_wbuf(c);
1284 2 50         if (sf_flush_ret == -1) goto tls_write_cleanup; /* EAGAIN */
1285 2 50         if (sf_flush_ret == -2) goto tls_write_error;
1286             }
1287 2 50         if (c->sendfile_remain > 0 || c->tls_wbuf.off > 0)
    50          
1288 0           goto tls_write_cleanup; /* More to send, keep watcher active */
1289 2           goto tls_write_finished;
1290             }
1291              
1292 58           tls_write_finished:
1293 60 50         if ((!c->wbuf_rinq || (c->cached_wbuf_low_water > 0
    0          
1294 0 0         && c->wbuf_len <= c->cached_wbuf_low_water))
1295 60 50         && c->sendfile_fd < 0 && c->tls_wbuf.off == 0) {
    50          
1296 60 100         if (c->responding == RESPOND_SHUTDOWN || c->responding == RESPOND_NORMAL) {
    50          
1297 50           handle_keepalive_or_close(c, try_tls_conn_read);
1298 10 50         } else if (c->responding == RESPOND_STREAMING && c->poll_write_cb) {
    50          
1299 10 50         if (c->poll_write_cb_is_io_handle)
1300 0           pump_io_handle(c);
1301             else
1302 10           call_poll_callback(c, 1 /* is_write */);
1303 0 0         } else if (c->responding == RESPOND_STREAMING) {
1304 0           stop_write_watcher(c);
1305 0           stop_write_timer(c);
1306 0 0         } else if (c->responding == RESPOND_NOT_STARTED) {
1307             /* tls_wbuf drained — nothing else to write. Covers both
1308             * TLS tunnel (io() called, no HTTP response) and handshake
1309             * flush (EAGAIN during handshake, now completed). */
1310 0           stop_write_watcher(c);
1311             }
1312             }
1313 60           goto tls_write_cleanup;
1314              
1315 0           tls_write_error:
1316 0           stop_all_watchers(c);
1317 0           change_responding_state(c, RESPOND_SHUTDOWN);
1318 0           safe_close_conn(c, "TLS write error");
1319              
1320 60           tls_write_cleanup:
1321 60           SvREFCNT_dec(c->self);
1322 60           }
1323              
1324             /*
1325             * Encrypt response data and queue for TLS writing.
1326             * Returns 0 on success, -1 on error.
1327             */
1328             static int
1329 23           feer_tls_send(struct feer_conn *c, const void *data, size_t len)
1330             {
1331 23 50         if (!c->tls || len == 0) return 0;
    50          
1332              
1333             ptls_buffer_t encbuf;
1334 23           ptls_buffer_init(&encbuf, "", 0);
1335 23           int ret = ptls_send(c->tls, &encbuf, data, len);
1336 23 50         if (ret != 0) {
1337 0           ptls_buffer_dispose(&encbuf);
1338 0           trouble("feer_tls_send error fd=%d ret=%d\n", c->fd, ret);
1339 0           return -1;
1340             }
1341 23 50         if (encbuf.off > 0) {
1342 23 50         if (tls_wbuf_append(&c->tls_wbuf, encbuf.base, encbuf.off) != 0) {
1343 0           ptls_buffer_dispose(&encbuf);
1344 0           trouble("TLS wbuf alloc failed (send) fd=%d\n", c->fd);
1345 0           return -1;
1346             }
1347             }
1348 23           ptls_buffer_dispose(&encbuf);
1349 23           return 0;
1350             }
1351              
1352             #endif /* FEERSUM_HAS_TLS */