LCOV - code coverage report
Current view: top level - waltz/tlsrec - fd_tlsrec.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 444 472 94.1 %
Date: 2026-09-17 04:28:31 Functions: 31 33 93.9 %

          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      167424 : fd_tlsrec_nonce( uchar iv[ static 12 ], uchar const base[ static 12 ], ulong seq ) {
      22      167424 :   memcpy( iv, base, 12 );
      23     1506816 :   for( uint i=0; i<8; i++ ) iv[11-i] ^= (uchar)(seq>>(8*i));
      24      167424 : }
      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      144948 :                    uchar const tag[16], fd_tlsrec_keys_t * k ) {
      32      144948 :   uchar iv[12]; fd_tlsrec_nonce( iv, k->read_iv, seq );
      33      144948 :   fd_aes_gcm_set_iv( &k->read_gcm, iv );
      34      144948 :   return fd_aes_gcm_decrypt( &k->read_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
      35      144948 : }
      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       22476 :                    uchar tag[16], fd_tlsrec_keys_t * k ) {
      41       22476 :   uchar iv[12]; fd_tlsrec_nonce( iv, k->write_iv, seq );
      42       22476 :   fd_aes_gcm_set_iv( &k->write_gcm, iv );
      43       22476 :   fd_aes_gcm_encrypt( &k->write_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
      44       22476 : }
      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       27276 :               uint content_type, uint enc_level ) {
      52             : 
      53       27276 :   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       27276 :   if( enc_level==FD_TLS_LEVEL_INITIAL ) {
      59        4791 :     if( FD_UNLIKELY( fd_tlsrec_slice_sz(tcp_tx) < sizeof(fd_tlsrec_hdr_t)+payload_sz ) )
      60           0 :       return FD_TLSREC_ERR_OOM;
      61        4791 :     fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
      62        4791 :     *hdr = (fd_tlsrec_hdr_t){
      63        4791 :       .content_type = (uchar)content_type,
      64        4791 :       .legacy_record_version = fd_ushort_bswap(0x0303),
      65        4791 :       .length = fd_ushort_bswap((ushort)payload_sz),
      66        4791 :     };
      67        4791 :     fd_memcpy( fd_tlsrec_slice_pop(tcp_tx, payload_sz), payload, payload_sz );
      68        4791 :     return FD_TLSREC_SUCCESS;
      69        4791 :   }
      70             : 
      71             :   /* Encrypted record: TLSInnerPlaintext = payload || content_type */
      72       22485 :   ulong inner_sz = payload_sz + 1 + FD_AES_GCM_TAG_SZ;
      73       22485 :   ulong outer_sz = sizeof(fd_tlsrec_hdr_t) + inner_sz;
      74       22485 :   if( FD_UNLIKELY( outer_sz > FD_TLSREC_CAP || outer_sz > fd_tlsrec_slice_sz(tcp_tx) ) )
      75           9 :     return FD_TLSREC_ERR_OOM;
      76             : 
      77       22476 :   fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
      78       22476 :   *hdr = (fd_tlsrec_hdr_t){
      79       22476 :     .content_type = FD_TLS_REC_APPLICATION_DATA,
      80       22476 :     .legacy_record_version = fd_ushort_bswap(0x0303),
      81       22476 :     .length = fd_ushort_bswap((ushort)inner_sz),
      82       22476 :   };
      83             : 
      84       22476 :   fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
      85       22476 :   uchar * c   = fd_tlsrec_slice_pop( tcp_tx, payload_sz+1 );
      86       22476 :   uchar * tag = fd_tlsrec_slice_pop( tcp_tx, FD_AES_GCM_TAG_SZ );
      87       22476 :   fd_memcpy( c, payload, payload_sz );
      88       22476 :   c[ payload_sz ] = (uchar)content_type;
      89       22476 :   fd_tlsrec_encrypt( c, c, payload_sz+1, hdr, conn->write_seq, tag, keys );
      90       22476 :   conn->write_seq++;
      91       22476 :   return FD_TLSREC_SUCCESS;
      92       22485 : }
      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       22068 :                               uchar const    secret[ static 32 ] ) {
     101       22068 :   fd_tls_hkdf_expand_label( key, 16UL, secret, "key", 3UL, NULL, 0UL );
     102       22068 :   fd_tls_hkdf_expand_label( iv,  12UL, secret, "iv",  2UL, NULL, 0UL );
     103       22068 :   fd_aes_gcm_init( gcm, key, 16UL, iv );
     104       22068 : }
     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        5718 :                                  uchar          iv[ static 12 ] ) {
     111        5718 :   uchar next_secret[ 32 ];
     112        5718 :   fd_tls_hkdf_expand_label( next_secret, 32UL, secret, "traffic upd", 11UL, NULL, 0UL );
     113        5718 :   fd_memcpy( secret, next_secret, 32UL );
     114        5718 :   fd_tlsrec_derive_traffic_key( gcm, key, iv, secret );
     115        5718 : }
     116             : 
     117             : static int
     118             : fd_tlsrec_send_key_update( fd_tlsrec_conn_t *  conn,
     119             :                            fd_tlsrec_slice_t * tcp_tx,
     120        2859 :                            uchar               request_peer_update ) {
     121        2859 :   struct __attribute__((packed)) {
     122        2859 :     fd_tls_msg_hdr_t hdr;
     123        2859 :     uchar            request_update;
     124        2859 :   } msg = {
     125        2859 :     .hdr = {
     126        2859 :       .type   = FD_TLS_MSG_KEY_UPDATE,
     127        2859 :       .sz     = fd_uint_to_tls_u24( 1U ),
     128        2859 :     },
     129        2859 :     .request_update = request_peer_update,
     130        2859 :   };
     131        2859 :   fd_tls_msg_hdr_bswap( &msg.hdr );
     132             : 
     133        2859 :   int rc = fd_tlsrec_tx( conn, tcp_tx, (uchar const *)&msg, sizeof(msg),
     134        2859 :                          FD_TLS_REC_HANDSHAKE, FD_TLS_LEVEL_APPLICATION );
     135        2859 :   if( FD_UNLIKELY( rc ) ) return rc;
     136             : 
     137        2853 :   fd_tlsrec_keys_t * keys = &conn->keys[1];
     138        2853 :   fd_tlsrec_update_traffic_secret( &keys->write_gcm, keys->write_secret, keys->write_key,
     139        2853 :                                    keys->write_iv );
     140        2853 :   conn->write_seq = 0UL;
     141        2853 :   return FD_TLSREC_SUCCESS;
     142        2859 : }
     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   128346408 : fd_tlsrec_answer_key_update( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx ) {
     151   128346408 :   if( conn->tx_closed || !conn->key_update_pending ) return FD_TLSREC_SUCCESS;
     152         912 :   int rc = fd_tlsrec_send_key_update( conn, tcp_tx, 0U );
     153         912 :   if( rc==FD_TLSREC_ERR_OOM ) return FD_TLSREC_SUCCESS;
     154         906 :   if( FD_UNLIKELY( rc ) ) return rc;
     155         906 :   conn->key_update_pending = 0;
     156         906 :   return FD_TLSREC_SUCCESS;
     157         906 : }
     158             : 
     159             : /* Handshake message reassembly *****************************************/
     160             : 
     161             : static inline ulong
     162       31998 : fd_tlsrec_peek_msg_sz( uchar const * buf, ulong buf_sz ) {
     163       31998 :   if( buf_sz < sizeof(fd_tls_msg_hdr_t) ) return 0;
     164       31998 :   fd_tls_msg_hdr_t hdr;
     165       31998 :   fd_memcpy( &hdr, buf, sizeof(hdr) );
     166       31998 :   fd_tls_msg_hdr_bswap( &hdr );
     167       31998 :   ulong payload = fd_tls_u24_to_uint( hdr.sz );
     168       31998 :   ulong msg_sz  = sizeof(fd_tls_msg_hdr_t) + payload;
     169       31998 :   return ( msg_sz <= FD_TLSREC_HS_MSG_CAP ) ? msg_sz : 0;
     170       31998 : }
     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   128346957 : hs_tbuf_init( fd_tlsrec_slice_t const * tx ) {
     182   128346957 :   hs_tbuf.sz     = 0U;
     183   128346957 :   hs_tbuf.tcp_tx = *tx;
     184   128346957 : }
     185             : 
     186             : static int
     187       15342 : hs_tbuf_push( uchar const * msg, ulong msg_sz, uint enc_level ) {
     188       15342 :   if( hs_tbuf.sz && hs_tbuf.enc_level != enc_level ) return FD_TLSREC_ERR_PROTO;
     189       15342 :   if( hs_tbuf.sz + msg_sz > FD_TLSREC_CAP )          return FD_TLSREC_ERR_OOM;
     190       15342 :   fd_memcpy( hs_tbuf.buf + hs_tbuf.sz, msg, msg_sz );
     191       15342 :   hs_tbuf.sz += (uint)msg_sz;
     192       15342 :   hs_tbuf.enc_level = enc_level;
     193       15342 :   return FD_TLSREC_SUCCESS;
     194       15342 : }
     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   128355072 : hs_tbuf_flush( fd_tlsrec_conn_t * conn ) {
     201   128355072 :   ulong off = 0UL;
     202   128363736 :   while( off < hs_tbuf.sz ) {
     203        8664 :     ulong sz = fd_ulong_min( hs_tbuf.sz - off, FD_TLSREC_PLAINTEXT_MAX );
     204        8664 :     int rc = fd_tlsrec_tx( conn, &hs_tbuf.tcp_tx, hs_tbuf.buf + off, sz,
     205        8664 :                            FD_TLS_REC_HANDSHAKE, hs_tbuf.enc_level );
     206        8664 :     if( FD_UNLIKELY( rc ) ) { hs_tbuf.sz = 0; return rc; }
     207        8664 :     off += sz;
     208        8664 :   }
     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   128355072 :   if( hs_tbuf.sz && hs_tbuf.enc_level==FD_TLS_LEVEL_HANDSHAKE && conn->tx_level<FD_TLS_LEVEL_HANDSHAKE )
     213         819 :     conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
     214   128355072 :   hs_tbuf.sz = 0;
     215   128355072 :   return FD_TLSREC_SUCCESS;
     216   128355072 : }
     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         399 : fd_tlsrec_send_alert( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx, uchar level, uchar desc ) {
     223         399 :   uchar const alert[2] = { level, desc };
     224         399 :   int rc = fd_tlsrec_tx( conn, tcp_tx, alert, sizeof(alert), FD_TLS_REC_ALERT, conn->tx_level );
     225         399 :   if( FD_UNLIKELY( rc ) ) return rc;
     226         396 :   conn->tx_closed = 1;
     227         396 :   conn->key_update_pending = 0;
     228         396 :   return FD_TLSREC_SUCCESS;
     229         399 : }
     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         381 : fd_tlsrec_fail( fd_tlsrec_conn_t * conn, uint alert, ushort reason ) {
     238         381 :   conn->hs.base.state  = FD_TLS_HS_FAIL;
     239         381 :   conn->hs.base.reason = reason;
     240         381 :   FD_LOG_WARNING(( "TLS connection failed (alert %u-%s; reason %u-%s)",
     241         381 :                    alert, fd_tls_alert_cstr( alert ),
     242         381 :                    reason, fd_tls_reason_cstr( reason ) ));
     243         381 :   hs_tbuf.sz = 0U;
     244         381 :   if( !conn->tx_closed ) fd_tlsrec_send_alert( conn, &hs_tbuf.tcp_tx, 2U, (uchar)alert );
     245         381 :   return FD_TLSREC_ERR_PROTO;
     246         381 : }
     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         315 : fd_tlsrec_alert_rx( fd_tlsrec_conn_t * conn, uchar const * pt, ulong p_sz ) {
     256         315 :   if( FD_UNLIKELY( p_sz!=2UL ) )
     257          60 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_ALERT_PARSE );
     258         255 :   uint level = pt[0];
     259         255 :   uint desc  = pt[1];
     260         255 :   if( desc==FD_TLS_ALERT_CLOSE_NOTIFY ) {
     261          36 :     conn->rx_closed = 1;
     262          36 :     return FD_TLSREC_SUCCESS;
     263          36 :   }
     264         219 :   if( desc==FD_TLS_ALERT_USER_CANCELED ) return FD_TLSREC_SUCCESS;
     265         168 :   FD_LOG_WARNING(( "TLS peer sent alert (level %u; alert %u-%s)",
     266         168 :                    level, desc, fd_tls_alert_cstr( desc ) ));
     267         168 :   conn->hs.base.state  = FD_TLS_HS_FAIL;
     268         168 :   conn->hs.base.reason = FD_TLS_REASON_PEER_ALERT;
     269         168 :   return FD_TLSREC_ERR_PROTO;
     270         219 : }
     271             : 
     272             : static int
     273        8922 : fd_tlsrec_post_hs_rx( fd_tlsrec_conn_t * conn, uchar const * msg, ulong msg_sz ) {
     274        8922 :   switch( msg[0] ) {
     275             : 
     276        2874 :   case FD_TLS_MSG_KEY_UPDATE: {
     277        2874 :     if( FD_UNLIKELY( msg_sz!=sizeof(fd_tls_msg_hdr_t)+1UL ) )
     278           6 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_KEY_UPDATE_PARSE );
     279        2868 :     if( FD_UNLIKELY( msg[4]>1U ) )
     280           3 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_ILLEGAL_PARAMETER, FD_TLS_REASON_KEY_UPDATE_PARSE );
     281             : 
     282        2865 :     fd_tlsrec_keys_t * keys = &conn->keys[1];
     283        2865 :     fd_tlsrec_update_traffic_secret( &keys->read_gcm, keys->read_secret, keys->read_key,
     284        2865 :                                      keys->read_iv );
     285        2865 :     conn->read_seq = 0UL;
     286             : 
     287        2865 :     if( msg[4] && !conn->tx_closed ) conn->key_update_pending = 1;
     288        2865 :     return FD_TLSREC_SUCCESS;
     289        2868 :   }
     290             : 
     291        6048 :   case FD_TLS_MSG_NEW_SESSION_TICKET:
     292        6048 :     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        8922 :   }
     298        8922 : }
     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      196872 : fd_tlsrec_read_keys_plaintext( fd_tlsrec_conn_t const * conn ) {
     307      196872 :   uint state = conn->hs.base.state;
     308      196872 :   return state==FD_TLS_HS_START || state==FD_TLS_HS_WAIT_SH;
     309      196872 : }
     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       32031 : fd_tlsrec_hs_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * rx, uint enc_level, int * key_change ) {
     318       32031 :   fd_tlsrec_hs_rbuf_t * rbuf = &conn->hs_rbuf;
     319       32031 :   *key_change = 0;
     320             : 
     321             :   /* Reassemble message header */
     322       32031 :   if( rbuf->sz < sizeof(fd_tls_msg_hdr_t) ) {
     323       30879 :     ulong want = sizeof(fd_tls_msg_hdr_t);
     324       30879 :     ulong have = fd_ulong_min( want, rbuf->sz + fd_tlsrec_slice_sz(rx) );
     325       30879 :     ulong n    = have - rbuf->sz;
     326       30879 :     fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
     327       30879 :     rbuf->sz = have; rx->data += n;
     328       30879 :     if( have < want ) return FD_TLSREC_SUCCESS;
     329       30879 :   }
     330             : 
     331             :   /* Reassemble message body */
     332       31998 :   ulong msg_sz = fd_tlsrec_peek_msg_sz( rbuf->buf, sizeof(fd_tls_msg_hdr_t) );
     333       31998 :   if( FD_UNLIKELY( !msg_sz ) )
     334           3 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_HS_MSG_SIZE );
     335             : 
     336       31995 :   ulong have = fd_ulong_min( msg_sz, rbuf->sz + fd_tlsrec_slice_sz(rx) );
     337       31995 :   ulong n    = have - rbuf->sz;
     338       31995 :   fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
     339       31995 :   rbuf->sz = have; rx->data += n;
     340       31995 :   if( have < msg_sz ) return FD_TLSREC_SUCCESS;
     341             : 
     342       30840 :   rbuf->sz = 0;
     343             : 
     344       30840 :   if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
     345        8922 :     *key_change = rbuf->buf[0]==FD_TLS_MSG_KEY_UPDATE;
     346        8922 :     return fd_tlsrec_post_hs_rx( conn, rbuf->buf, msg_sz );
     347        8922 :   }
     348             : 
     349             :   /* Dispatch to fd_tls */
     350       21918 :   int plaintext_0 = fd_tlsrec_read_keys_plaintext( conn );
     351       21918 :   long rc = fd_tls_handshake( &conn->tls, &conn->hs, rbuf->buf, msg_sz, enc_level );
     352       21918 :   if( FD_UNLIKELY( rc<0 ) ) return fd_tlsrec_fail( conn, (uint)(-rc), conn->hs.base.reason );
     353       21726 :   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       21726 :   if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
     356        3957 :     conn->read_seq = 0;
     357        3957 :     if( !conn->hs.base.server ) {
     358        3219 :       conn->write_seq = 0;
     359        3219 :       conn->tx_level = FD_TLS_LEVEL_APPLICATION;
     360        3219 :     }
     361        3957 :     *key_change = 1;
     362        3957 :   }
     363       21726 :   if( plaintext_0 != fd_tlsrec_read_keys_plaintext( conn ) ) *key_change = 1;
     364       21726 :   return FD_TLSREC_SUCCESS;
     365       21726 : }
     366             : 
     367             : /* fd_tlsrec_hs_rx_record delivers the handshake payload of one record. */
     368             : 
     369             : static int
     370       31032 : fd_tlsrec_hs_rx_record( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * payload, uint enc_level ) {
     371       31032 :   if( FD_UNLIKELY( fd_tlsrec_slice_is_empty(payload) ) )
     372           9 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_MSG_SIZE );
     373       62844 :   while( !fd_tlsrec_slice_is_empty(payload) ) {
     374       32031 :     int key_change;
     375       32031 :     int rc = fd_tlsrec_hs_rx( conn, payload, enc_level, &key_change );
     376       32031 :     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       31827 :     if( FD_UNLIKELY( key_change && !fd_tlsrec_slice_is_empty(payload) ) )
     382           6 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_KEY_CHANGE );
     383       31827 :   }
     384       30813 :   return FD_TLSREC_SUCCESS;
     385       31023 : }
     386             : 
     387             : /* TLS callbacks ********************************************************/
     388             : 
     389             : FD_FN_CONST static fd_tlsrec_conn_t *
     390       23517 : cb_ctx( void const * hs ) {
     391       23517 :   return (fd_tlsrec_conn_t *)((ulong)hs - offsetof(fd_tlsrec_conn_t, hs));
     392       23517 : }
     393             : 
     394             : static void
     395        8175 : cb_secrets( void const * hs, void const * rx_secret, void const * tx_secret, uint level ) {
     396        8175 :   fd_tlsrec_conn_t * conn = cb_ctx( hs );
     397             : 
     398        8175 :   fd_tlsrec_keys_t * out = &conn->keys[ level==FD_TLS_LEVEL_APPLICATION ];
     399        8175 :   fd_memcpy( out->read_secret,  rx_secret, 32UL );
     400        8175 :   fd_memcpy( out->write_secret, tx_secret, 32UL );
     401             : 
     402        8175 :   fd_tlsrec_derive_traffic_key( &out->read_gcm,  out->read_key,  out->read_iv,  rx_secret );
     403        8175 :   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        8175 :   if( level==FD_TLS_LEVEL_HANDSHAKE && !conn->hs.base.server ) conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
     409        8175 :   if( level==FD_TLS_LEVEL_APPLICATION && conn->hs.base.server ) {
     410         819 :     conn->tx_level  = FD_TLS_LEVEL_APPLICATION;
     411         819 :     conn->write_seq = 0UL;
     412         819 :   }
     413             : 
     414        8175 :   if( conn->secrets_fn ) conn->secrets_fn( hs, rx_secret, tx_secret, level );
     415        8175 : }
     416             : 
     417             : static int
     418       15342 : cb_sendmsg( void const * hs, void const * msg, ulong msg_sz, uint enc_level, int flush ) {
     419       15342 :   fd_tlsrec_conn_t * conn = cb_ctx( hs );
     420       15342 :   int rc = hs_tbuf_push( msg, msg_sz, enc_level );
     421       15342 :   if( FD_UNLIKELY( rc ) ) return 0;
     422       15342 :   if( flush ) { rc = hs_tbuf_flush( conn ); if( FD_UNLIKELY(rc) ) return 0; }
     423       15342 :   return 1;
     424       15342 : }
     425             : 
     426             : /* Record layer (receive path) ******************************************/
     427             : 
     428             : static inline ulong
     429   128013345 : fd_tlsrec_peek_rec_sz( uchar const * buf, ulong buf_sz ) {
     430   128013345 :   if( buf_sz < sizeof(fd_tlsrec_hdr_t) ) return 0;
     431   127889606 :   fd_tlsrec_hdr_t hdr;
     432   127889606 :   fd_memcpy( &hdr, buf, sizeof(hdr) );
     433   127889606 :   fd_tlsrec_hdr_bswap( &hdr );
     434   127889606 :   ulong payload = hdr.length;
     435   127889606 :   if( payload > FD_TLSREC_PAYLOAD_MAX ) return 0;
     436   127889606 :   return sizeof(fd_tlsrec_hdr_t) + payload;
     437   127889606 : }
     438             : 
     439             : static int
     440   128364805 : fd_tlsrec_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_rx, fd_tlsrec_slice_t * app_rx ) {
     441   128364805 :   fd_tlsrec_buf_t * rb = &conn->rec_buf;
     442             : 
     443             :   /* Skip rec_buf if tcp_rx already holds a whole record */
     444   128364805 :   uchar * rec;
     445   128364805 :   ulong   rec_sz;
     446   128364805 :   if( !rb->sz &&
     447   128364805 :       (rec_sz = fd_tlsrec_peek_rec_sz( tcp_rx->data, fd_tlsrec_slice_sz(tcp_rx) )) &&
     448   128364805 :       rec_sz <= fd_tlsrec_slice_sz(tcp_rx) ) {
     449       18111 :     rec = fd_tlsrec_slice_pop( tcp_rx, rec_sz );
     450   128346694 :   } else {
     451             :     /* Reassemble record header (5 bytes) */
     452   128346694 :     if( rb->sz < sizeof(fd_tlsrec_hdr_t) ) {
     453      621694 :       ulong want = sizeof(fd_tlsrec_hdr_t);
     454      621694 :       ulong have = fd_ulong_min( want, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
     455      621694 :       ulong n    = have - rb->sz;
     456      621694 :       fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
     457      621694 :       rb->sz = have; tcp_rx->data += n;
     458      621694 :       if( have < want ) return FD_TLSREC_SUCCESS;
     459      621694 :     }
     460             : 
     461             :     /* Reassemble full record */
     462   127860117 :     rec_sz = fd_tlsrec_peek_rec_sz( rb->buf, sizeof(fd_tlsrec_hdr_t) );
     463   127860117 :     if( FD_UNLIKELY( !rec_sz ) )
     464           0 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
     465             : 
     466   127860117 :     ulong have = fd_ulong_min( rec_sz, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
     467   127860117 :     ulong n    = have - rb->sz;
     468   127860117 :     fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
     469   127860117 :     rb->sz = have; tcp_rx->data += n;
     470   127860117 :     if( have < rec_sz ) return FD_TLSREC_SUCCESS;
     471             : 
     472      135117 :     rb->sz = 0;  /* consume record */
     473      135117 :     rec = rb->buf;
     474      135117 :   }
     475             : 
     476      153228 :   fd_tlsrec_hdr_t *      hdr = fd_type_pun( rec );
     477      153228 :   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      153228 :   int plaintext      = fd_tlsrec_read_keys_plaintext( conn );
     482      153228 :   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      153228 :   if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_CHANGE_CIPHER_SPEC ) ) {
     488        3597 :     int allowed = hs->server
     489        3597 :       ? ( hs->state!=FD_TLS_HS_CONNECTED && ( hs->state!=FD_TLS_HS_START || conn->hs.srv.hello_retry ) )
     490        3597 :       : ( hs->state!=FD_TLS_HS_CONNECTED && hs->state!=FD_TLS_HS_START );
     491        3597 :     if( FD_UNLIKELY( !allowed ||
     492        3597 :                      rec_sz != sizeof(fd_tlsrec_hdr_t)+1UL ||
     493        3597 :                      rec[ sizeof(fd_tlsrec_hdr_t) ] != 0x01 ) )
     494          42 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_CCS );
     495        3555 :     return FD_TLSREC_SUCCESS;
     496        3597 :   }
     497             : 
     498      149631 :   if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_ALERT && peer_plaintext ) ) {
     499         249 :     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         249 :     return fd_tlsrec_alert_rx( conn, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
     502         249 :   }
     503             : 
     504             :   /* Unencrypted handshake records (pre-ServerHello) */
     505      149382 :   if( FD_UNLIKELY( plaintext ) ) {
     506             :     /* RFC 8446 Section 5.1: TLSPlaintext.length may not exceed 2^14 */
     507        4422 :     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        4422 :     fd_tlsrec_slice_t payload[1];
     510        4422 :     fd_tlsrec_slice_init( payload, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
     511        4422 :     if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_HANDSHAKE ) )
     512           3 :       return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     513        4419 :     return fd_tlsrec_hs_rx_record( conn, payload, FD_TLS_LEVEL_INITIAL );
     514        4422 :   }
     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      144960 :   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      144960 :   if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_APPLICATION_DATA ) )
     527          12 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     528      144948 :   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      144948 :   uint enc_level = ( hs->state == FD_TLS_HS_CONNECTED )
     532      144948 :                    ? FD_TLS_LEVEL_APPLICATION : FD_TLS_LEVEL_HANDSHAKE;
     533      144948 :   fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
     534             : 
     535      144948 :   uchar const * tag  = rec + rec_sz - FD_AES_GCM_TAG_SZ;
     536      144948 :   uchar *       c    = rec + sizeof(fd_tlsrec_hdr_t);
     537      144948 :   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      144948 :   uchar * pt = app_rx->data;
     543      144948 :   if( FD_UNLIKELY( fd_tlsrec_slice_sz(app_rx) < c_sz ) ) {
     544         321 :     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         321 :     pt = c;
     552         321 :   }
     553      144948 :   if( FD_UNLIKELY( !fd_tlsrec_decrypt( pt, c, c_sz, hdr, conn->read_seq, tag, keys ) ) ) {
     554          15 :     fd_tlsrec_fail( conn, FD_TLS_ALERT_BAD_RECORD_MAC, FD_TLS_REASON_REC_MAC );
     555          15 :     return FD_TLSREC_ERR_CRYPTO;
     556          15 :   }
     557      144933 :   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      144933 :   if( FD_UNLIKELY( c_sz > FD_TLSREC_PLAINTEXT_MAX+1UL ) )
     562          15 :     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      144918 :   ulong p_sz = c_sz;
     566      243270 :   while( p_sz > 0 && pt[p_sz-1] == 0 ) p_sz--;
     567      144918 :   if( FD_UNLIKELY( !p_sz ) )
     568           3 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_PADDING );
     569      144915 :   uint ct = pt[--p_sz];
     570      144915 :   if( FD_UNLIKELY( conn->hs_rbuf.sz && ct != FD_TLS_REC_HANDSHAKE ) )
     571           6 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
     572             : 
     573             :   /* Dispatch by inner content type */
     574      144909 :   switch( ct ) {
     575             : 
     576       26613 :   case FD_TLS_REC_HANDSHAKE:
     577       26613 :     { fd_tlsrec_slice_t payload[1];
     578       26613 :       fd_tlsrec_slice_init( payload, pt, p_sz );
     579       26613 :       int rc = fd_tlsrec_hs_rx_record( conn, payload, enc_level );
     580       26613 :       if( FD_UNLIKELY( rc ) ) return rc;
     581       26613 :     }
     582       26472 :     break;
     583             : 
     584      118224 :   case FD_TLS_REC_APPLICATION_DATA:
     585      118224 :     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      118224 :     if( pt != app_rx->data ) {
     588           6 :       if( FD_UNLIKELY( p_sz > fd_tlsrec_slice_sz(app_rx) ) ) return FD_TLSREC_ERR_OOM;
     589           6 :       fd_memcpy( app_rx->data, pt, p_sz );
     590           6 :     }
     591      118224 :     app_rx->data += p_sz;
     592      118224 :     break;
     593             : 
     594          66 :   case FD_TLS_REC_ALERT:
     595          66 :     return fd_tlsrec_alert_rx( conn, pt, p_sz );
     596             : 
     597           6 :   default:
     598           6 :     return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
     599      144909 :   }
     600             : 
     601      144696 :   return FD_TLSREC_SUCCESS;
     602      144909 : }
     603             : 
     604             : /* Public API ***********************************************************/
     605             : 
     606             : fd_tlsrec_conn_t *
     607        4530 : fd_tlsrec_conn_init( fd_tlsrec_conn_t * conn, fd_tls_t const * tls, int is_server ) {
     608        4530 :   fd_memset( conn, 0, sizeof(*conn) );
     609        4530 :   fd_memcpy( &conn->tls, tls, sizeof(*tls) );
     610        4530 :   conn->secrets_fn       = conn->tls.secrets_fn;
     611        4530 :   conn->tls.quic         = 0;
     612        4530 :   conn->tls.secrets_fn   = cb_secrets;
     613        4530 :   conn->tls.sendmsg_fn   = cb_sendmsg;
     614        4530 :   if( is_server ) fd_tls_estate_srv_new( &conn->hs.srv );
     615        3618 :   else            fd_tls_estate_cli_new( &conn->hs.cli );
     616        4530 :   return conn;
     617        4530 : }
     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   128347125 :                    uchar * app_rx, ulong * app_rx_sz ) {
     623   128347125 :   ulong _z = 0; if( !tcp_tx_sz ) tcp_tx_sz = &_z; if( !app_rx_sz ) app_rx_sz = &_z;
     624             : 
     625   128347125 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     626   128347125 :   fd_tlsrec_slice_t rx[1]; fd_tlsrec_slice_init( rx, app_rx, *app_rx_sz );
     627   128347125 :   *tcp_tx_sz = *app_rx_sz = 0;
     628             : 
     629   128347125 :   if( FD_UNLIKELY( fd_tlsrec_conn_is_failed( conn ) ) ) return FD_TLSREC_ERR_STATE;
     630             : 
     631   128346957 :   hs_tbuf_init( tx );
     632             : 
     633             :   /* Client: send ClientHello on first call */
     634   128346957 :   if( FD_UNLIKELY( conn->hs.base.state==FD_TLS_HS_START && !conn->hs.base.server ) ) {
     635        3615 :     long rc = fd_tls_handshake( &conn->tls, &conn->hs, NULL, 0, FD_TLS_LEVEL_INITIAL );
     636        3615 :     if( FD_UNLIKELY( rc<0 ) ) return FD_TLSREC_ERR_PROTO;
     637        3615 :   }
     638             : 
     639             :   /* Process incoming TLS records.  RFC 8446 Section 6.1: anything
     640             :      received after close_notify is ignored. */
     641   128346957 :   int rc = FD_TLSREC_SUCCESS;
     642   128346957 :   if( tcp_rx ) {
     643   256707598 :     while( !fd_tlsrec_slice_is_empty(tcp_rx) && !conn->rx_closed ) {
     644   128364805 :       rc = fd_tlsrec_rx( conn, tcp_rx, rx );
     645   128364805 :       if( FD_UNLIKELY(rc) ) break;
     646   128364805 :     }
     647   128343342 :     if( conn->rx_closed ) tcp_rx->data = tcp_rx->data_end;
     648   128343342 :   }
     649             : 
     650   128346957 :   if( !rc ) rc = hs_tbuf_flush( conn );
     651   128346957 :   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   128346957 :   *tx = hs_tbuf.tcp_tx;
     656   128346957 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     657   128346957 :   *app_rx_sz = rc ? 0UL : (ulong)(rx->data - app_rx);
     658   128346957 :   return rc;
     659   128346957 : }
     660             : 
     661             : int
     662             : fd_tlsrec_conn_tx( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
     663       15525 :                    fd_tlsrec_slice_t * app_tx ) {
     664       15525 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     665       15525 :   *tcp_tx_sz = 0;
     666             : 
     667       15525 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     668       15354 :   if( !fd_tlsrec_slice_sz(app_tx) ) return FD_TLSREC_SUCCESS;
     669             : 
     670       15354 :   ulong overhead = sizeof(fd_tlsrec_hdr_t) + 1 + FD_AES_GCM_TAG_SZ;
     671       15354 :   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       15354 :   if( FD_UNLIKELY( conn->key_update_pending ) ) {
     676           3 :     int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
     677           3 :     if( FD_UNLIKELY( rc ) ) return rc;
     678           3 :     conn->key_update_pending = 0;
     679       15351 :   } else if( FD_UNLIKELY( conn->write_seq >= FD_TLSREC_KEY_UPDATE_SEQ ) ) {
     680           6 :     int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
     681           6 :     if( FD_UNLIKELY( rc ) ) return rc;
     682           6 :   }
     683             : 
     684       15354 :   ulong sz = fd_ulong_min( fd_tlsrec_slice_sz(tx) - overhead, FD_TLSREC_PLAINTEXT_MAX );
     685       15354 :         sz = fd_ulong_min( sz, fd_tlsrec_slice_sz(app_tx) );
     686             : 
     687       15354 :   int rc = fd_tlsrec_tx( conn, tx, fd_tlsrec_slice_pop(app_tx, sz), sz,
     688       15354 :                          FD_TLS_REC_APPLICATION_DATA, FD_TLS_LEVEL_APPLICATION );
     689       15354 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     690       15354 :   return rc;
     691       15354 : }
     692             : 
     693             : int
     694             : fd_tlsrec_conn_key_update( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
     695        1941 :                            int request_peer_update ) {
     696        1941 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     697        1941 :   *tcp_tx_sz = 0UL;
     698             : 
     699        1941 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     700        1938 :   if( FD_UNLIKELY( request_peer_update<0 || request_peer_update>1 ) ) return FD_TLSREC_ERR_PROTO;
     701             : 
     702        1938 :   int rc = fd_tlsrec_send_key_update( conn, tx, (uchar)request_peer_update );
     703        1938 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     704        1938 :   return rc;
     705        1938 : }
     706             : 
     707             : int
     708          27 : fd_tlsrec_conn_close( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz ) {
     709          27 :   fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
     710          27 :   *tcp_tx_sz = 0UL;
     711             : 
     712          27 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
     713             : 
     714          18 :   int rc = fd_tlsrec_send_alert( conn, tx, 1U, FD_TLS_ALERT_CLOSE_NOTIFY );
     715          18 :   *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
     716          18 :   return rc;
     717          27 : }
     718             : 
     719           0 : FD_FN_PURE int fd_tlsrec_conn_is_server( fd_tlsrec_conn_t const * c ) { return c->hs.base.server; }
     720       21600 : 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   128347710 : 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