File Coverage

feersum_tls.c.inc
Criterion Covered Total %
statement 317 635 49.9
branch 172 462 37.2
condition n/a
subroutine n/a
pod n/a
total 489 1097 44.5


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