LCOV - code coverage report
Current view: top level - waltz/tlsrec - fd_tlsrec.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 472 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 33 0.0 %

          Line data    Source code
       1             : #include "fd_tlsrec.h"
       2             : #include "../tls/fd_tls.h"
       3             : #include "../tls/fd_tls_proto.h"
       4             : #include "../../ballet/aes/fd_aes_gcm.h"
       5             : 
       6             : FD_FN_PURE char const *
       7           0 : fd_tlsrec_strerror( int err ) {
       8           0 :   switch( err ) {
       9           0 :   case FD_TLSREC_SUCCESS:    return "success";
      10           0 :   case FD_TLSREC_ERR_OOM:    return "out of memory";
      11           0 :   case FD_TLSREC_ERR_PROTO:  return "protocol error";
      12           0 :   case FD_TLSREC_ERR_STATE:  return "unexpected state";
      13           0 :   case FD_TLSREC_ERR_CRYPTO: return "crypto error";
      14           0 :   default:                   return "unknown";
      15           0 :   }
      16           0 : }
      17             : 
      18             : /* RFC 8446 §5.3: per-record nonce = write_iv XOR (0-padded seq) */
      19             : 
      20             : static void
      21           0 : fd_tlsrec_nonce( uchar iv[ static 12 ], uchar const base[ static 12 ], ulong seq ) {
      22           0 :   memcpy( iv, base, 12 );
      23           0 :   for( uint i=0; i<8; i++ ) iv[11-i] ^= (uchar)(seq>>(8*i));
      24           0 : }
      25             : 
      26             : /* RFC 8446 §5.2: AEAD decrypt/encrypt with 5-byte record header as AAD */
      27             : 
      28             : static int
      29             : fd_tlsrec_decrypt( uchar * p, uchar const * c, ulong sz,
      30             :                    fd_tlsrec_hdr_t const * hdr, ulong seq,
      31           0 :                    uchar const tag[16], fd_tlsrec_keys_t * k ) {
      32           0 :   uchar iv[12]; fd_tlsrec_nonce( iv, k->read_iv, seq );
      33           0 :   fd_aes_gcm_set_iv( &k->read_gcm, iv );
      34           0 :   return fd_aes_gcm_decrypt( &k->read_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
      35           0 : }
      36             : 
      37             : static void
      38             : fd_tlsrec_encrypt( uchar * c, uchar const * p, ulong sz,
      39             :                    fd_tlsrec_hdr_t const * hdr, ulong seq,
      40           0 :                    uchar tag[16], fd_tlsrec_keys_t * k ) {
      41           0 :   uchar iv[12]; fd_tlsrec_nonce( iv, k->write_iv, seq );
      42           0 :   fd_aes_gcm_set_iv( &k->write_gcm, iv );
      43           0 :   fd_aes_gcm_encrypt( &k->write_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
      44           0 : }
      45             : 
      46             : /* Transmit path ********************************************************/
      47             : 
      48             : static int
      49             : fd_tlsrec_tx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx,
      50             :               uchar const * payload, ulong payload_sz,
      51           0 :               uint content_type, uint enc_level ) {
      52             : 
      53           0 :   if( FD_UNLIKELY( conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
      54             : 
      55             :   /* Size checks precede the first write so that on ERR_OOM tcp_tx is
      56             :      untouched and the caller can retry later. */
      57             : 
      58           0 :   if( enc_level==FD_TLS_LEVEL_INITIAL ) {
      59           0 :     if( FD_UNLIKELY( fd_tlsrec_slice_sz(tcp_tx) < sizeof(fd_tlsrec_hdr_t)+payload_sz ) )
      60           0 :       return FD_TLSREC_ERR_OOM;
      61           0 :     fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
      62           0 :     *hdr = (fd_tlsrec_hdr_t){
      63           0 :       .content_type = (uchar)content_type,
      64           0 :       .legacy_record_version = fd_ushort_bswap(0x0303),
      65           0 :       .length = fd_ushort_bswap((ushort)payload_sz),
      66           0 :     };
      67           0 :     fd_memcpy( fd_tlsrec_slice_pop(tcp_tx, payload_sz), payload, payload_sz );
      68           0 :     return FD_TLSREC_SUCCESS;
      69           0 :   }
      70             : 
      71             :   /* Encrypted record: TLSInnerPlaintext = payload || content_type */
      72           0 :   ulong inner_sz = payload_sz + 1 + FD_AES_GCM_TAG_SZ;
      73           0 :   ulong outer_sz = sizeof(fd_tlsrec_hdr_t) + inner_sz;
      74           0 :   if( FD_UNLIKELY( outer_sz > FD_TLSREC_CAP || outer_sz > fd_tlsrec_slice_sz(tcp_tx) ) )
      75           0 :     return FD_TLSREC_ERR_OOM;
      76             : 
      77           0 :   fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
      78           0 :   *hdr = (fd_tlsrec_hdr_t){
      79           0 :     .content_type = FD_TLS_REC_APPLICATION_DATA,
      80           0 :     .legacy_record_version = fd_ushort_bswap(0x0303),
      81           0 :     .length = fd_ushort_bswap((ushort)inner_sz),
      82           0 :   };
      83             : 
      84           0 :   fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
      85           0 :   uchar * c   = fd_tlsrec_slice_pop( tcp_tx, payload_sz+1 );
      86           0 :   uchar * tag = fd_tlsrec_slice_pop( tcp_tx, FD_AES_GCM_TAG_SZ );
      87           0 :   fd_memcpy( c, payload, payload_sz );
      88           0 :   c[ payload_sz ] = (uchar)content_type;
      89           0 :   fd_tlsrec_encrypt( c, c, payload_sz+1, hdr, conn->write_seq, tag, keys );
      90           0 :   conn->write_seq++;
      91           0 :   return FD_TLSREC_SUCCESS;
      92           0 : }
      93             : 
      94             : /* RFC 8446 §7.3 */
      95             : 
      96             : static void
      97             : fd_tlsrec_derive_traffic_key( fd_aes_gcm_t * gcm,
      98             :                               uchar          key[ static 16 ],
      99             :                               uchar          iv[ static 12 ],
     100           0 :                               uchar const    secret[ static 32 ] ) {
     101           0 :   fd_tls_hkdf_expand_label( key, 16UL, secret, "key", 3UL, NULL, 0UL );
     102           0 :   fd_tls_hkdf_expand_label( iv,  12UL, secret, "iv",  2UL, NULL, 0UL );
     103           0 :   fd_aes_gcm_init( gcm, key, 16UL, iv );
     104           0 : }
     105             : 
     106             : static void
     107             : fd_tlsrec_update_traffic_secret( fd_aes_gcm_t * gcm,
     108             :                                  uchar          secret[ static 32 ],
     109             :                                  uchar          key[ static 16 ],
     110           0 :                                  uchar          iv[ static 12 ] ) {
     111           0 :   uchar next_secret[ 32 ];
     112           0 :   fd_tls_hkdf_expand_label( next_secret, 32UL, secret, "traffic upd", 11UL, NULL, 0UL );
     113           0 :   fd_memcpy( secret, next_secret, 32UL );
     114           0 :   fd_tlsrec_derive_traffic_key( gcm, key, iv, secret );
     115           0 : }
     116             : 
     117             : static int
     118             : fd_tlsrec_send_key_update( fd_tlsrec_conn_t *  conn,
     119             :                            fd_tlsrec_slice_t * tcp_tx,
     120           0 :                            uchar               request_peer_update ) {
     121           0 :   struct __attribute__((packed)) {
     122           0 :     fd_tls_msg_hdr_t hdr;
     123           0 :     uchar            request_update;
     124           0 :   } msg = {
     125           0 :     .hdr = {
     126           0 :       .type   = FD_TLS_MSG_KEY_UPDATE,
     127           0 :       .sz     = fd_uint_to_tls_u24( 1U ),
     128           0 :     },
     129           0 :     .request_update = request_peer_update,
     130           0 :   };
     131           0 :   fd_tls_msg_hdr_bswap( &msg.hdr );
     132             : 
     133           0 :   int rc = fd_tlsrec_tx( conn, tcp_tx, (uchar const *)&msg, sizeof(msg),
     134           0 :                          FD_TLS_REC_HANDSHAKE, FD_TLS_LEVEL_APPLICATION );
     135           0 :   if( FD_UNLIKELY( rc ) ) return rc;
     136             : 
     137           0 :   fd_tlsrec_keys_t * keys = &conn->keys[1];
     138           0 :   fd_tlsrec_update_traffic_secret( &keys->write_gcm, keys->write_secret, keys->write_key,
     139           0 :                                    keys->write_iv );
     140           0 :   conn->write_seq = 0UL;
     141           0 :   return FD_TLSREC_SUCCESS;
     142           0 : }
     143             : 
     144             : /* fd_tlsrec_answer_key_update sends the one KeyUpdate that answers every
     145             :    update the peer requested since the last one (RFC 8446 Section 4.6.3
     146             :    lets a silent receiver collapse them).  Not having room in tcp_tx is
     147             :    not an error: the reply stays pending. */
     148             : 
     149             : static int
     150           0 : fd_tlsrec_answer_key_update( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx ) {
     151           0 :   if( conn->tx_closed || !conn->key_update_pending ) return FD_TLSREC_SUCCESS;
     152           0 :   int rc = fd_tlsrec_send_key_update( conn, tcp_tx, 0U );
     153           0 :   if( rc==FD_TLSREC_ERR_OOM ) return FD_TLSREC_SUCCESS;
     154           0 :   if( FD_UNLIKELY( rc ) ) return rc;
     155           0 :   conn->key_update_pending = 0;
     156           0 :   return FD_TLSREC_SUCCESS;
     157           0 : }
     158             : 
     159             : /* Handshake message reassembly *****************************************/
     160             : 
     161             : static inline ulong
     162           0 : fd_tlsrec_peek_msg_sz( uchar const * buf, ulong buf_sz ) {
     163           0 :   if( buf_sz < sizeof(fd_tls_msg_hdr_t) ) return 0;
     164           0 :   fd_tls_msg_hdr_t hdr;
     165           0 :   fd_memcpy( &hdr, buf, sizeof(hdr) );
     166           0 :   fd_tls_msg_hdr_bswap( &hdr );
     167           0 :   ulong payload = fd_tls_u24_to_uint( hdr.sz );
     168           0 :   ulong msg_sz  = sizeof(fd_tls_msg_hdr_t) + payload;
     169           0 :   return ( msg_sz <= FD_TLSREC_HS_MSG_CAP ) ? msg_sz : 0;
     170           0 : }
     171             : 
     172             : /* Thread-local buffer coalescing handshake messages into one record */
     173             : static FD_TL struct {
     174             :   uchar buf[ FD_TLSREC_CAP ];
     175             :   uint  sz;
     176             :   uint  enc_level;
     177             :   fd_tlsrec_slice_t tcp_tx;
     178             : } hs_tbuf;
     179             : 
     180             : static void
     181           0 : hs_tbuf_init( fd_tlsrec_slice_t const * tx ) {
     182           0 :   hs_tbuf.sz     = 0U;
     183           0 :   hs_tbuf.tcp_tx = *tx;
     184           0 : }
     185             : 
     186             : static int
     187           0 : hs_tbuf_push( uchar const * msg, ulong msg_sz, uint enc_level ) {
     188           0 :   if( hs_tbuf.sz && hs_tbuf.enc_level != enc_level ) return FD_TLSREC_ERR_PROTO;
     189           0 :   if( hs_tbuf.sz + msg_sz > FD_TLSREC_CAP )          return FD_TLSREC_ERR_OOM;
     190           0 :   fd_memcpy( hs_tbuf.buf + hs_tbuf.sz, msg, msg_sz );
     191           0 :   hs_tbuf.sz += (uint)msg_sz;
     192           0 :   hs_tbuf.enc_level = enc_level;
     193           0 :   return FD_TLSREC_SUCCESS;
     194           0 : }
     195             : 
     196             : /* Emits coalesced handshake messages as records of at most 2^14 bytes
     197             :    of plaintext (RFC 8446 Section 5.1). */
     198             : 
     199             : static int
     200           0 : hs_tbuf_flush( fd_tlsrec_conn_t * conn ) {
     201           0 :   ulong off = 0UL;
     202           0 :   while( off < hs_tbuf.sz ) {
     203           0 :     ulong sz = fd_ulong_min( hs_tbuf.sz - off, FD_TLSREC_PLAINTEXT_MAX );
     204           0 :     int rc = fd_tlsrec_tx( conn, &hs_tbuf.tcp_tx, hs_tbuf.buf + off, sz,
     205           0 :                            FD_TLS_REC_HANDSHAKE, hs_tbuf.enc_level );
     206           0 :     if( FD_UNLIKELY( rc ) ) { hs_tbuf.sz = 0; return rc; }
     207           0 :     off += sz;
     208           0 :   }
     209             :   /* The server moves off plaintext when its encrypted flight goes out.
     210             :      Never lower the write epoch or advance it on an empty flush, such
     211             :      as when discarding CCS before ServerHello. */
     212           0 :   if( hs_tbuf.sz && hs_tbuf.enc_level==FD_TLS_LEVEL_HANDSHAKE && conn->tx_level<FD_TLS_LEVEL_HANDSHAKE )
     213           0 :     conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
     214           0 :   hs_tbuf.sz = 0;
     215           0 :   return FD_TLSREC_SUCCESS;
     216           0 : }
     217             : 
     218             : /* fd_tlsrec_send_alert writes an alert record (RFC 8446 Section 6) at
     219             :    the current write level into tcp_tx and closes the write side. */
     220             : 
     221             : static int
     222           0 : fd_tlsrec_send_alert( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx, uchar level, uchar desc ) {
     223           0 :   uchar const alert[2] = { level, desc };
     224           0 :   int rc = fd_tlsrec_tx( conn, tcp_tx, alert, sizeof(alert), FD_TLS_REC_ALERT, conn->tx_level );
     225           0 :   if( FD_UNLIKELY( rc ) ) return rc;
     226           0 :   conn->tx_closed = 1;
     227           0 :   conn->key_update_pending = 0;
     228           0 :   return FD_TLSREC_SUCCESS;
     229           0 : }
     230             : 
     231             : /* fd_tlsrec_fail marks conn failed and queues the fatal alert for the
     232             :    peer behind whatever fd_tlsrec_conn_rx already produced in this call.
     233             :    Pending handshake output is dropped: the peer only needs the alert.
     234             :    Called from the receive path only, where hs_tbuf is initialized. */
     235             : 
     236             : static int
     237           0 : fd_tlsrec_fail( fd_tlsrec_conn_t * conn, uint alert, ushort reason ) {
     238           0 :   conn->hs.base.state  = FD_TLS_HS_FAIL;
     239           0 :   conn->hs.base.reason = reason;
     240           0 :   FD_LOG_WARNING(( "TLS connection failed (alert %u-%s; reason %u-%s)",
     241           0 :                    alert, fd_tls_alert_cstr( alert ),
     242           0 :                    reason, fd_tls_reason_cstr( reason ) ));
     243           0 :   hs_tbuf.sz = 0U;
     244           0 :   if( !conn->tx_closed ) fd_tlsrec_send_alert( conn, &hs_tbuf.tcp_tx, 2U, (uchar)alert );
     245           0 :   return FD_TLSREC_ERR_PROTO;
     246           0 : }
     247             : 
     248             : /* fd_tlsrec_alert_rx handles an alert record payload (plaintext or
     249             :    decrypted).  RFC 8446 Section 5.1: alerts may not be fragmented or
     250             :    coalesced, so the payload is exactly two bytes.  close_notify marks
     251             :    the receive side closed (RFC 8446 Section 6.1).  user_canceled is
     252             :    ignored; every other alert is fatal regardless of the level byte. */
     253             : 
     254             : static int
     255           0 : fd_tlsrec_alert_rx( fd_tlsrec_conn_t * conn, uchar const * pt, ulong p_sz ) {
     256           0 :   if( FD_UNLIKELY( p_sz!=2UL ) )
     257           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_ALERT_PARSE );
     258           0 :   uint level = pt[0];
     259           0 :   uint desc  = pt[1];
     260           0 :   if( desc==FD_TLS_ALERT_CLOSE_NOTIFY ) {
     261           0 :     conn->rx_closed = 1;
     262           0 :     return FD_TLSREC_SUCCESS;
     263           0 :   }
     264           0 :   if( desc==FD_TLS_ALERT_USER_CANCELED ) return FD_TLSREC_SUCCESS;
     265           0 :   FD_LOG_WARNING(( "TLS peer sent alert (level %u; alert %u-%s)",
     266           0 :                    level, desc, fd_tls_alert_cstr( desc ) ));
     267           0 :   conn->hs.base.state  = FD_TLS_HS_FAIL;
     268           0 :   conn->hs.base.reason = FD_TLS_REASON_PEER_ALERT;
     269           0 :   return FD_TLSREC_ERR_PROTO;
     270           0 : }
     271             : 
     272             : static int
     273           0 : fd_tlsrec_post_hs_rx( fd_tlsrec_conn_t * conn, uchar const * msg, ulong msg_sz ) {
     274           0 :   switch( msg[0] ) {
     275             : 
     276           0 :   case FD_TLS_MSG_KEY_UPDATE: {
     277           0 :     if( FD_UNLIKELY( msg_sz!=sizeof(fd_tls_msg_hdr_t)+1UL ) )
     278           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_KEY_UPDATE_PARSE );
     279           0 :     if( FD_UNLIKELY( msg[4]>1U ) )
     280           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_ILLEGAL_PARAMETER, FD_TLS_REASON_KEY_UPDATE_PARSE );
     281             : 
     282           0 :     fd_tlsrec_keys_t * keys = &conn->keys[1];
     283           0 :     fd_tlsrec_update_traffic_secret( &keys->read_gcm, keys->read_secret, keys->read_key,
     284           0 :                                      keys->read_iv );
     285           0 :     conn->read_seq = 0UL;
     286             : 
     287           0 :     if( msg[4] && !conn->tx_closed ) conn->key_update_pending = 1;
     288           0 :     return FD_TLSREC_SUCCESS;
     289           0 :   }
     290             : 
     291           0 :   case FD_TLS_MSG_NEW_SESSION_TICKET:
     292           0 :     if( !conn->hs.base.server ) return FD_TLSREC_SUCCESS;
     293           0 :     __attribute__((fallthrough));
     294             : 
     295           0 :   default:
     296           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_POST_HS_MSG );
     297           0 :   }
     298           0 : }
     299             : 
     300             : /* Handshake message delivery *******************************************/
     301             : 
     302             : /* fd_tlsrec_read_keys_plaintext is 1 while the peer's records arrive
     303             :    unencrypted (before it has our ServerHello, or before we have its). */
     304             : 
     305             : static inline int
     306           0 : fd_tlsrec_read_keys_plaintext( fd_tlsrec_conn_t const * conn ) {
     307           0 :   uint state = conn->hs.base.state;
     308           0 :   return state==FD_TLS_HS_START || state==FD_TLS_HS_WAIT_SH;
     309           0 : }
     310             : 
     311             : /* fd_tlsrec_hs_rx consumes handshake bytes from rx, at most one message
     312             :    completion per call.  *key_change is set to 1 if that message changed
     313             :    the read keys (ServerHello, Finished, KeyUpdate): RFC 8446 Section
     314             :    5.1 requires such a message to end its record. */
     315             : 
     316             : static int
     317           0 : fd_tlsrec_hs_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * rx, uint enc_level, int * key_change ) {
     318           0 :   fd_tlsrec_hs_rbuf_t * rbuf = &conn->hs_rbuf;
     319           0 :   *key_change = 0;
     320             : 
     321             :   /* Reassemble message header */
     322           0 :   if( rbuf->sz < sizeof(fd_tls_msg_hdr_t) ) {
     323           0 :     ulong want = sizeof(fd_tls_msg_hdr_t);
     324           0 :     ulong have = fd_ulong_min( want, rbuf->sz + fd_tlsrec_slice_sz(rx) );
     325           0 :     ulong n    = have - rbuf->sz;
     326           0 :     fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
     327           0 :     rbuf->sz = have; rx->data += n;
     328           0 :     if( have < want ) return FD_TLSREC_SUCCESS;
     329           0 :   }
     330             : 
     331             :   /* Reassemble message body */
     332           0 :   ulong msg_sz = fd_tlsrec_peek_msg_sz( rbuf->buf, sizeof(fd_tls_msg_hdr_t) );
     333           0 :   if( FD_UNLIKELY( !msg_sz ) )
     334           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_HS_MSG_SIZE );
     335             : 
     336           0 :   ulong have = fd_ulong_min( msg_sz, rbuf->sz + fd_tlsrec_slice_sz(rx) );
     337           0 :   ulong n    = have - rbuf->sz;
     338           0 :   fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
     339           0 :   rbuf->sz = have; rx->data += n;
     340           0 :   if( have < msg_sz ) return FD_TLSREC_SUCCESS;
     341             : 
     342           0 :   rbuf->sz = 0;
     343             : 
     344           0 :   if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
     345           0 :     *key_change = rbuf->buf[0]==FD_TLS_MSG_KEY_UPDATE;
     346           0 :     return fd_tlsrec_post_hs_rx( conn, rbuf->buf, msg_sz );
     347           0 :   }
     348             : 
     349             :   /* Dispatch to fd_tls */
     350           0 :   int plaintext_0 = fd_tlsrec_read_keys_plaintext( conn );
     351           0 :   long rc = fd_tls_handshake( &conn->tls, &conn->hs, rbuf->buf, msg_sz, enc_level );
     352           0 :   if( FD_UNLIKELY( rc<0 ) ) return fd_tlsrec_fail( conn, (uint)(-rc), conn->hs.base.reason );
     353           0 :   if( FD_UNLIKELY( (ulong)rc != msg_sz ) )
     354           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_HS_MSG_SIZE );
     355           0 :   if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
     356           0 :     conn->read_seq = 0;
     357           0 :     if( !conn->hs.base.server ) {
     358           0 :       conn->write_seq = 0;
     359           0 :       conn->tx_level = FD_TLS_LEVEL_APPLICATION;
     360           0 :     }
     361           0 :     *key_change = 1;
     362           0 :   }
     363           0 :   if( plaintext_0 != fd_tlsrec_read_keys_plaintext( conn ) ) *key_change = 1;
     364           0 :   return FD_TLSREC_SUCCESS;
     365           0 : }
     366             : 
     367             : /* fd_tlsrec_hs_rx_record delivers the handshake payload of one record. */
     368             : 
     369             : static int
     370           0 : fd_tlsrec_hs_rx_record( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * payload, uint enc_level ) {
     371           0 :   if( FD_UNLIKELY( fd_tlsrec_slice_is_empty(payload) ) )
     372           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_MSG_SIZE );
     373           0 :   while( !fd_tlsrec_slice_is_empty(payload) ) {
     374           0 :     int key_change;
     375           0 :     int rc = fd_tlsrec_hs_rx( conn, payload, enc_level, &key_change );
     376           0 :     if( FD_UNLIKELY( rc ) ) return rc;
     377             :     /* RFC 8446 Section 5.1: a message that changes keys ends its record.
     378             :        Anything after it in this record was authenticated under the old
     379             :        keys yet would be handled under the new ones.  This also bounds
     380             :        KeyUpdate processing to one per record. */
     381           0 :     if( FD_UNLIKELY( key_change && !fd_tlsrec_slice_is_empty(payload) ) )
     382           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_KEY_CHANGE );
     383           0 :   }
     384           0 :   return FD_TLSREC_SUCCESS;
     385           0 : }
     386             : 
     387             : /* TLS callbacks ********************************************************/
     388             : 
     389             : FD_FN_CONST static fd_tlsrec_conn_t *
     390           0 : cb_ctx( void const * hs ) {
     391           0 :   return (fd_tlsrec_conn_t *)((ulong)hs - offsetof(fd_tlsrec_conn_t, hs));
     392           0 : }
     393             : 
     394             : static void
     395           0 : cb_secrets( void const * hs, void const * rx_secret, void const * tx_secret, uint level ) {
     396           0 :   fd_tlsrec_conn_t * conn = cb_ctx( hs );
     397             : 
     398           0 :   fd_tlsrec_keys_t * out = &conn->keys[ level==FD_TLS_LEVEL_APPLICATION ];
     399           0 :   fd_memcpy( out->read_secret,  rx_secret, 32UL );
     400           0 :   fd_memcpy( out->write_secret, tx_secret, 32UL );
     401             : 
     402           0 :   fd_tlsrec_derive_traffic_key( &out->read_gcm,  out->read_key,  out->read_iv,  rx_secret );
     403           0 :   fd_tlsrec_derive_traffic_key( &out->write_gcm, out->write_key, out->write_iv, tx_secret );
     404             : 
     405             :   /* The server has sent Finished before deriving application secrets,
     406             :      so its write epoch changes now, independently of the read epoch.
     407             :      The client still has its own Finished to send with handshake keys. */
     408           0 :   if( level==FD_TLS_LEVEL_HANDSHAKE && !conn->hs.base.server ) conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
     409           0 :   if( level==FD_TLS_LEVEL_APPLICATION && conn->hs.base.server ) {
     410           0 :     conn->tx_level  = FD_TLS_LEVEL_APPLICATION;
     411           0 :     conn->write_seq = 0UL;
     412           0 :   }
     413             : 
     414           0 :   if( conn->secrets_fn ) conn->secrets_fn( hs, rx_secret, tx_secret, level );
     415           0 : }
     416             : 
     417             : static int
     418           0 : cb_sendmsg( void const * hs, void const * msg, ulong msg_sz, uint enc_level, int flush ) {
     419           0 :   fd_tlsrec_conn_t * conn = cb_ctx( hs );
     420           0 :   int rc = hs_tbuf_push( msg, msg_sz, enc_level );
     421           0 :   if( FD_UNLIKELY( rc ) ) return 0;
     422           0 :   if( flush ) { rc = hs_tbuf_flush( conn ); if( FD_UNLIKELY(rc) ) return 0; }
     423           0 :   return 1;
     424           0 : }
     425             : 
     426             : /* Record layer (receive path) ******************************************/
     427             : 
     428             : static inline ulong
     429           0 : fd_tlsrec_peek_rec_sz( uchar const * buf, ulong buf_sz ) {
     430           0 :   if( buf_sz < sizeof(fd_tlsrec_hdr_t) ) return 0;
     431           0 :   fd_tlsrec_hdr_t hdr;
     432           0 :   fd_memcpy( &hdr, buf, sizeof(hdr) );
     433           0 :   fd_tlsrec_hdr_bswap( &hdr );
     434           0 :   ulong payload = hdr.length;
     435           0 :   if( payload > FD_TLSREC_PAYLOAD_MAX ) return 0;
     436           0 :   return sizeof(fd_tlsrec_hdr_t) + payload;
     437           0 : }
     438             : 
     439             : static int
     440           0 : fd_tlsrec_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_rx, fd_tlsrec_slice_t * app_rx ) {
     441           0 :   fd_tlsrec_buf_t * rb = &conn->rec_buf;
     442             : 
     443             :   /* Skip rec_buf if tcp_rx already holds a whole record */
     444           0 :   uchar * rec;
     445           0 :   ulong   rec_sz;
     446           0 :   if( !rb->sz &&
     447           0 :       (rec_sz = fd_tlsrec_peek_rec_sz( tcp_rx->data, fd_tlsrec_slice_sz(tcp_rx) )) &&
     448           0 :       rec_sz <= fd_tlsrec_slice_sz(tcp_rx) ) {
     449           0 :     rec = fd_tlsrec_slice_pop( tcp_rx, rec_sz );
     450           0 :   } else {
     451             :     /* Reassemble record header (5 bytes) */
     452           0 :     if( rb->sz < sizeof(fd_tlsrec_hdr_t) ) {
     453           0 :       ulong want = sizeof(fd_tlsrec_hdr_t);
     454           0 :       ulong have = fd_ulong_min( want, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
     455           0 :       ulong n    = have - rb->sz;
     456           0 :       fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
     457           0 :       rb->sz = have; tcp_rx->data += n;
     458           0 :       if( have < want ) return FD_TLSREC_SUCCESS;
     459           0 :     }
     460             : 
     461             :     /* Reassemble full record */
     462           0 :     rec_sz = fd_tlsrec_peek_rec_sz( rb->buf, sizeof(fd_tlsrec_hdr_t) );
     463           0 :     if( FD_UNLIKELY( !rec_sz ) )
     464           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
     465             : 
     466           0 :     ulong have = fd_ulong_min( rec_sz, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
     467           0 :     ulong n    = have - rb->sz;
     468           0 :     fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
     469           0 :     rb->sz = have; tcp_rx->data += n;
     470           0 :     if( have < rec_sz ) return FD_TLSREC_SUCCESS;
     471             : 
     472           0 :     rb->sz = 0;  /* consume record */
     473           0 :     rec = rb->buf;
     474           0 :   }
     475             : 
     476           0 :   fd_tlsrec_hdr_t *      hdr = fd_type_pun( rec );
     477           0 :   fd_tls_estate_base_t * hs  = &conn->hs.base;
     478             : 
     479             :   /* RFC 8446 Sections 5.1-5.2: ignore legacy_record_version on receive,
     480             :      but preserve its wire bytes for AEAD additional data. */
     481           0 :   int plaintext      = fd_tlsrec_read_keys_plaintext( conn );
     482           0 :   int peer_plaintext = hs->server ? hs->state!=FD_TLS_HS_CONNECTED : plaintext;
     483             : 
     484             :   /* RFC 8446 Section 5: discard compatibility CCS throughout the
     485             :      window after the first ClientHello and before the peer Finished,
     486             :      even between fragments of a handshake message. */
     487           0 :   if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_CHANGE_CIPHER_SPEC ) ) {
     488           0 :     int allowed = hs->server
     489           0 :       ? ( hs->state!=FD_TLS_HS_CONNECTED && ( hs->state!=FD_TLS_HS_START || conn->hs.srv.hello_retry ) )
     490           0 :       : ( hs->state!=FD_TLS_HS_CONNECTED && hs->state!=FD_TLS_HS_START );
     491           0 :     if( FD_UNLIKELY( !allowed ||
     492           0 :                      rec_sz != sizeof(fd_tlsrec_hdr_t)+1UL ||
     493           0 :                      rec[ sizeof(fd_tlsrec_hdr_t) ] != 0x01 ) )
     494           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_CCS );
     495           0 :     return FD_TLSREC_SUCCESS;
     496           0 :   }
     497             : 
     498           0 :   if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_ALERT && peer_plaintext ) ) {
     499           0 :     if( FD_UNLIKELY( conn->hs_rbuf.sz ) )
     500           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
     501           0 :     return fd_tlsrec_alert_rx( conn, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
     502           0 :   }
     503             : 
     504             :   /* Unencrypted handshake records (pre-ServerHello) */
     505           0 :   if( FD_UNLIKELY( plaintext ) ) {
     506             :     /* RFC 8446 Section 5.1: TLSPlaintext.length may not exceed 2^14 */
     507           0 :     if( FD_UNLIKELY( rec_sz > sizeof(fd_tlsrec_hdr_t)+FD_TLSREC_PLAINTEXT_MAX ) )
     508           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
     509           0 :     fd_tlsrec_slice_t payload[1];
     510           0 :     fd_tlsrec_slice_init( payload, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
     511           0 :     if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_HANDSHAKE ) )
     512           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     513           0 :     return fd_tlsrec_hs_rx_record( conn, payload, FD_TLS_LEVEL_INITIAL );
     514           0 :   }
     515             : 
     516             :   /* RFC 8446 Section 5.1: handshake messages MUST NOT be interleaved
     517             :      with other record types.  hs_rbuf holds the head of a handshake
     518             :      message until the next handshake record completes it. */
     519           0 :   if( FD_UNLIKELY( conn->hs_rbuf.sz && hdr->content_type != FD_TLS_REC_APPLICATION_DATA ) )
     520           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
     521             : 
     522             :   /* Encrypted record (RFC 8446 Section 5.2: TLSCiphertext has outer
     523             :      type application_data).  The header is also AEAD additional data,
     524             :      so a wrong outer type would fail authentication anyway; checking
     525             :      first gives the right alert. */
     526           0 :   if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_APPLICATION_DATA ) )
     527           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     528           0 :   if( FD_UNLIKELY( rec_sz < sizeof(fd_tlsrec_hdr_t) + FD_AES_GCM_TAG_SZ ) )
     529           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_BAD_RECORD_MAC, FD_TLS_REASON_REC_MAC );
     530             : 
     531           0 :   uint enc_level = ( hs->state == FD_TLS_HS_CONNECTED )
     532           0 :                    ? FD_TLS_LEVEL_APPLICATION : FD_TLS_LEVEL_HANDSHAKE;
     533           0 :   fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
     534             : 
     535           0 :   uchar const * tag  = rec + rec_sz - FD_AES_GCM_TAG_SZ;
     536           0 :   uchar *       c    = rec + sizeof(fd_tlsrec_hdr_t);
     537           0 :   ulong         c_sz = (ulong)(tag - c);
     538             : 
     539             :   /* Decrypt into app_rx if it fits (content type is only known after
     540             :      decrypting; non-app records don't advance the cursor).  Otherwise
     541             :      decrypt in place in rec_buf. */
     542           0 :   uchar * pt = app_rx->data;
     543           0 :   if( FD_UNLIKELY( fd_tlsrec_slice_sz(app_rx) < c_sz ) ) {
     544           0 :     if( rec != rb->buf ) {
     545           0 :       fd_memcpy( rb->buf, rec, rec_sz );
     546           0 :       rec = rb->buf;
     547           0 :       hdr = fd_type_pun( rec );
     548           0 :       tag = rec + rec_sz - FD_AES_GCM_TAG_SZ;
     549           0 :       c   = rec + sizeof(fd_tlsrec_hdr_t);
     550           0 :     }
     551           0 :     pt = c;
     552           0 :   }
     553           0 :   if( FD_UNLIKELY( !fd_tlsrec_decrypt( pt, c, c_sz, hdr, conn->read_seq, tag, keys ) ) ) {
     554           0 :     fd_tlsrec_fail( conn, FD_TLS_ALERT_BAD_RECORD_MAC, FD_TLS_REASON_REC_MAC );
     555           0 :     return FD_TLSREC_ERR_CRYPTO;
     556           0 :   }
     557           0 :   conn->read_seq++;
     558             : 
     559             :   /* RFC 8446 Section 5.4: the entire TLSInnerPlaintext, including
     560             :      content type and padding, must fit in 2^14+1 bytes. */
     561           0 :   if( FD_UNLIKELY( c_sz > FD_TLSREC_PLAINTEXT_MAX+1UL ) )
     562           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
     563             : 
     564             :   /* Strip padding and content type (RFC 8446 §5.4) */
     565           0 :   ulong p_sz = c_sz;
     566           0 :   while( p_sz > 0 && pt[p_sz-1] == 0 ) p_sz--;
     567           0 :   if( FD_UNLIKELY( !p_sz ) )
     568           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_PADDING );
     569           0 :   uint ct = pt[--p_sz];
     570           0 :   if( FD_UNLIKELY( conn->hs_rbuf.sz && ct != FD_TLS_REC_HANDSHAKE ) )
     571           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
     572             : 
     573             :   /* Dispatch by inner content type */
     574           0 :   switch( ct ) {
     575             : 
     576           0 :   case FD_TLS_REC_HANDSHAKE:
     577           0 :     { fd_tlsrec_slice_t payload[1];
     578           0 :       fd_tlsrec_slice_init( payload, pt, p_sz );
     579           0 :       int rc = fd_tlsrec_hs_rx_record( conn, payload, enc_level );
     580           0 :       if( FD_UNLIKELY( rc ) ) return rc;
     581           0 :     }
     582           0 :     break;
     583             : 
     584           0 :   case FD_TLS_REC_APPLICATION_DATA:
     585           0 :     if( FD_UNLIKELY( hs->state != FD_TLS_HS_CONNECTED ) )
     586           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     587           0 :     if( pt != app_rx->data ) {
     588           0 :       if( FD_UNLIKELY( p_sz > fd_tlsrec_slice_sz(app_rx) ) ) return FD_TLSREC_ERR_OOM;
     589           0 :       fd_memcpy( app_rx->data, pt, p_sz );
     590           0 :     }
     591           0 :     app_rx->data += p_sz;
     592           0 :     break;
     593             : 
     594           0 :   case FD_TLS_REC_ALERT:
     595           0 :     return fd_tlsrec_alert_rx( conn, pt, p_sz );
     596             : 
     597           0 :   default:
     598           0 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     599           0 :   }
     600             : 
     601           0 :   return FD_TLSREC_SUCCESS;
     602           0 : }
     603             : 
     604             : /* Public API ***********************************************************/
     605             : 
     606             : fd_tlsrec_conn_t *
     607           0 : fd_tlsrec_conn_init( fd_tlsrec_conn_t * conn, fd_tls_t const * tls, int is_server ) {
     608           0 :   fd_memset( conn, 0, sizeof(*conn) );
     609           0 :   fd_memcpy( &conn->tls, tls, sizeof(*tls) );
     610           0 :   conn->secrets_fn       = conn->tls.secrets_fn;
     611           0 :   conn->tls.quic         = 0;
     612           0 :   conn->tls.secrets_fn   = cb_secrets;
     613           0 :   conn->tls.sendmsg_fn   = cb_sendmsg;
     614           0 :   if( is_server ) fd_tls_estate_srv_new( &conn->hs.srv );
     615           0 :   else            fd_tls_estate_cli_new( &conn->hs.cli );
     616           0 :   return conn;
     617           0 : }
     618             : 
     619             : int
     620             : fd_tlsrec_conn_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_rx,
     621             :                    uchar * tcp_tx, ulong * tcp_tx_sz,
     622           0 :                    uchar * app_rx, ulong * app_rx_sz ) {
     623           0 :   ulong _z = 0; if( !tcp_tx_sz ) tcp_tx_sz = &_z; if( !app_rx_sz ) app_rx_sz = &_z;
     624             : 
     625           0 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     626           0 :   fd_tlsrec_slice_t rx[1]; fd_tlsrec_slice_init( rx, app_rx, *app_rx_sz );
     627           0 :   *tcp_tx_sz = *app_rx_sz = 0;
     628             : 
     629           0 :   if( FD_UNLIKELY( fd_tlsrec_conn_is_failed( conn ) ) ) return FD_TLSREC_ERR_STATE;
     630             : 
     631           0 :   hs_tbuf_init( tx );
     632             : 
     633             :   /* Client: send ClientHello on first call */
     634           0 :   if( FD_UNLIKELY( conn->hs.base.state==FD_TLS_HS_START && !conn->hs.base.server ) ) {
     635           0 :     long rc = fd_tls_handshake( &conn->tls, &conn->hs, NULL, 0, FD_TLS_LEVEL_INITIAL );
     636           0 :     if( FD_UNLIKELY( rc<0 ) ) return FD_TLSREC_ERR_PROTO;
     637           0 :   }
     638             : 
     639             :   /* Process incoming TLS records.  RFC 8446 Section 6.1: anything
     640             :      received after close_notify is ignored. */
     641           0 :   int rc = FD_TLSREC_SUCCESS;
     642           0 :   if( tcp_rx ) {
     643           0 :     while( !fd_tlsrec_slice_is_empty(tcp_rx) && !conn->rx_closed ) {
     644           0 :       rc = fd_tlsrec_rx( conn, tcp_rx, rx );
     645           0 :       if( FD_UNLIKELY(rc) ) break;
     646           0 :     }
     647           0 :     if( conn->rx_closed ) tcp_rx->data = tcp_rx->data_end;
     648           0 :   }
     649             : 
     650           0 :   if( !rc ) rc = hs_tbuf_flush( conn );
     651           0 :   if( !rc ) rc = fd_tlsrec_answer_key_update( conn, &hs_tbuf.tcp_tx );
     652             : 
     653             :   /* Output (including a fatal alert) is reported even on error so the
     654             :      caller can send it before tearing down; plaintext is not. */
     655           0 :   *tx = hs_tbuf.tcp_tx;
     656           0 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     657           0 :   *app_rx_sz = rc ? 0UL : (ulong)(rx->data - app_rx);
     658           0 :   return rc;
     659           0 : }
     660             : 
     661             : int
     662             : fd_tlsrec_conn_tx( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
     663           0 :                    fd_tlsrec_slice_t * app_tx ) {
     664           0 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     665           0 :   *tcp_tx_sz = 0;
     666             : 
     667           0 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     668           0 :   if( !fd_tlsrec_slice_sz(app_tx) ) return FD_TLSREC_SUCCESS;
     669             : 
     670           0 :   ulong overhead = sizeof(fd_tlsrec_hdr_t) + 1 + FD_AES_GCM_TAG_SZ;
     671           0 :   if( FD_UNLIKELY( fd_tlsrec_slice_sz(tx) < overhead + 128 ) ) return FD_TLSREC_ERR_OOM;
     672             : 
     673             :   /* RFC 8446 Section 4.6.3: a requested KeyUpdate goes out before the
     674             :      next application data record */
     675           0 :   if( FD_UNLIKELY( conn->key_update_pending ) ) {
     676           0 :     int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
     677           0 :     if( FD_UNLIKELY( rc ) ) return rc;
     678           0 :     conn->key_update_pending = 0;
     679           0 :   } else if( FD_UNLIKELY( conn->write_seq >= FD_TLSREC_KEY_UPDATE_SEQ ) ) {
     680           0 :     int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
     681           0 :     if( FD_UNLIKELY( rc ) ) return rc;
     682           0 :   }
     683             : 
     684           0 :   ulong sz = fd_ulong_min( fd_tlsrec_slice_sz(tx) - overhead, FD_TLSREC_PLAINTEXT_MAX );
     685           0 :         sz = fd_ulong_min( sz, fd_tlsrec_slice_sz(app_tx) );
     686             : 
     687           0 :   int rc = fd_tlsrec_tx( conn, tx, fd_tlsrec_slice_pop(app_tx, sz), sz,
     688           0 :                          FD_TLS_REC_APPLICATION_DATA, FD_TLS_LEVEL_APPLICATION );
     689           0 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     690           0 :   return rc;
     691           0 : }
     692             : 
     693             : int
     694             : fd_tlsrec_conn_key_update( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
     695           0 :                            int request_peer_update ) {
     696           0 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     697           0 :   *tcp_tx_sz = 0UL;
     698             : 
     699           0 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     700           0 :   if( FD_UNLIKELY( request_peer_update<0 || request_peer_update>1 ) ) return FD_TLSREC_ERR_PROTO;
     701             : 
     702           0 :   int rc = fd_tlsrec_send_key_update( conn, tx, (uchar)request_peer_update );
     703           0 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     704           0 :   return rc;
     705           0 : }
     706             : 
     707             : int
     708           0 : fd_tlsrec_conn_close( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz ) {
     709           0 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     710           0 :   *tcp_tx_sz = 0UL;
     711             : 
     712           0 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     713             : 
     714           0 :   int rc = fd_tlsrec_send_alert( conn, tx, 1U, FD_TLS_ALERT_CLOSE_NOTIFY );
     715           0 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     716           0 :   return rc;
     717           0 : }
     718             : 
     719           0 : FD_FN_PURE int fd_tlsrec_conn_is_server( fd_tlsrec_conn_t const * c ) { return c->hs.base.server; }
     720           0 : FD_FN_PURE int fd_tlsrec_conn_is_ready ( fd_tlsrec_conn_t const * c ) { return c->hs.base.state == FD_TLS_HS_CONNECTED; }
     721           0 : FD_FN_PURE int fd_tlsrec_conn_is_failed( fd_tlsrec_conn_t const * c ) { return c->hs.base.state == FD_TLS_HS_FAIL; }

Generated by: LCOV version 1.14