File Coverage

feersum_tls.c.inc
Criterion Covered Total %
statement 318 646 49.2
branch 173 470 36.8
condition n/a
subroutine n/a
pod n/a
total 491 1116 44.0


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