LCOV - code coverage report
Current view: top level - waltz/h2 - fd_hpack.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 105 124 84.7 %
Date: 2025-07-01 05:00:49 Functions: 5 5 100.0 %

          Line data    Source code
       1             : #include "fd_hpack.h"
       2             : #include "fd_h2_base.h"
       3             : #include "fd_hpack_private.h"
       4             : #include "nghttp2_hd_huffman.h"
       5             : #include "../../util/log/fd_log.h"
       6             : 
       7             : fd_hpack_static_entry_t const
       8             : fd_hpack_static_table[ 62 ] = {
       9             :   [  1 ] = { ":authority",                       10,  0 },
      10             :   [  2 ] = { ":method"         "GET",             7,  3 },
      11             :   [  3 ] = { ":method"         "POST",            7,  4 },
      12             :   [  4 ] = { ":path"           "/",               5,  1 },
      13             :   [  5 ] = { ":path"           "/index.html",     5, 11 },
      14             :   [  6 ] = { ":scheme"         "http",            7,  4 },
      15             :   [  7 ] = { ":scheme"         "https",           7,  5 },
      16             :   [  8 ] = { ":status"         "200",             7,  3 },
      17             :   [  9 ] = { ":status"         "204",             7,  3 },
      18             :   [ 10 ] = { ":status"         "206",             7,  3 },
      19             :   [ 11 ] = { ":status"         "304",             7,  3 },
      20             :   [ 12 ] = { ":status"         "400",             7,  3 },
      21             :   [ 13 ] = { ":status"         "404",             7,  3 },
      22             :   [ 14 ] = { ":status"         "500",             7,  3 },
      23             :   [ 15 ] = { "accept-charset",                   14,  0 },
      24             :   [ 16 ] = { "accept-encoding" "gzip, deflate",  15, 13 },
      25             :   [ 17 ] = { "accept-language",                  15,  0 },
      26             :   [ 18 ] = { "accept-ranges",                    13,  0 },
      27             :   [ 19 ] = { "accept",                            6,  0 },
      28             :   [ 20 ] = { "access-control-allow-origin",      27,  0 },
      29             :   [ 21 ] = { "age",                               3,  0 },
      30             :   [ 22 ] = { "allow",                             5,  0 },
      31             :   [ 23 ] = { "authorization",                    13,  0 },
      32             :   [ 24 ] = { "cache-control",                    13,  0 },
      33             :   [ 25 ] = { "content-disposition",              19,  0 },
      34             :   [ 26 ] = { "content-encoding",                 16,  0 },
      35             :   [ 27 ] = { "content-language",                 16,  0 },
      36             :   [ 28 ] = { "content-length",                   14,  0 },
      37             :   [ 29 ] = { "content-location",                 16,  0 },
      38             :   [ 30 ] = { "content-range",                    13,  0 },
      39             :   [ 31 ] = { "content-type",                     12,  0 },
      40             :   [ 32 ] = { "cookie",                            6,  0 },
      41             :   [ 33 ] = { "date",                              4,  0 },
      42             :   [ 34 ] = { "etag",                              4,  0 },
      43             :   [ 35 ] = { "expect",                            6,  0 },
      44             :   [ 36 ] = { "expires",                           7,  0 },
      45             :   [ 37 ] = { "from",                              4,  0 },
      46             :   [ 38 ] = { "host",                              4,  0 },
      47             :   [ 39 ] = { "if-match",                          8,  0 },
      48             :   [ 40 ] = { "if-modified-since",                17,  0 },
      49             :   [ 41 ] = { "if-none-match",                    13,  0 },
      50             :   [ 42 ] = { "if-range",                          8,  0 },
      51             :   [ 43 ] = { "if-unmodified-since",              19,  0 },
      52             :   [ 44 ] = { "last-modified",                    13,  0 },
      53             :   [ 45 ] = { "link",                              4,  0 },
      54             :   [ 46 ] = { "location",                          8,  0 },
      55             :   [ 47 ] = { "max-forwards",                     12,  0 },
      56             :   [ 48 ] = { "proxy-authenticate",               18,  0 },
      57             :   [ 49 ] = { "proxy-authorization",              19,  0 },
      58             :   [ 50 ] = { "range",                             5,  0 },
      59             :   [ 51 ] = { "referer",                           7,  0 },
      60             :   [ 52 ] = { "refresh",                           7,  0 },
      61             :   [ 53 ] = { "retry-after",                      11,  0 },
      62             :   [ 54 ] = { "server",                            6,  0 },
      63             :   [ 55 ] = { "set-cookie",                       10,  0 },
      64             :   [ 56 ] = { "strict-transport-security",        25,  0 },
      65             :   [ 57 ] = { "transfer-encoding",                17,  0 },
      66             :   [ 58 ] = { "user-agent",                       10,  0 },
      67             :   [ 59 ] = { "vary",                              4,  0 },
      68             :   [ 60 ] = { "via",                               3,  0 },
      69             :   [ 61 ] = { "www-authenticate",                 16,  0 }
      70             : };
      71             : 
      72             : fd_hpack_rd_t *
      73             : fd_hpack_rd_init( fd_hpack_rd_t * rd,
      74             :                   uchar const *   src,
      75          48 :                   ulong           srcsz ) {
      76          48 :   *rd = (fd_hpack_rd_t) {
      77          48 :     .src     = src,
      78          48 :     .src_end = src+srcsz
      79          48 :   };
      80             :   /* FIXME slow */
      81             :   /* Skip over Dynamic Table Size Updates */
      82          48 :   while( FD_LIKELY( rd->src < rd->src_end ) ) {
      83          33 :     uint b0 = rd->src[0];
      84          33 :     if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
      85           0 :       ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
      86           0 :       if( FD_UNLIKELY( max_sz!=0UL ) ) break; /* FIXME hacky */
      87           0 :       rd->src++;
      88          33 :     } else {
      89          33 :       break;
      90          33 :     }
      91          33 :   }
      92          48 :   return rd;
      93          48 : }
      94             : 
      95             : /* fd_hpack_rd_indexed selects a header from HPACK dictionaries.
      96             :    Currently, only supports the static table.  (Pretends that the
      97             :    dynamic table size is 0). */
      98             : 
      99             : static uint
     100             : fd_hpack_rd_indexed( fd_h2_hdr_t * hdr,
     101         129 :                      ulong         idx ) {
     102         129 :   if( FD_UNLIKELY( idx==0 || idx>61 ) ) return FD_H2_ERR_COMPRESSION;
     103         129 :   fd_hpack_static_entry_t const * entry = &fd_hpack_static_table[ idx ];
     104         129 :   *hdr = (fd_h2_hdr_t) {
     105         129 :     .name      = entry->entry,
     106         129 :     .name_len  = entry->name_len,
     107         129 :     .value     = entry->entry + entry->name_len,
     108         129 :     .value_len = entry->value_len,
     109         129 :     .hint      = (ushort)idx | FD_H2_HDR_HINT_NAME_INDEXED,
     110         129 :   };
     111         129 :   return FD_H2_SUCCESS;
     112         129 : }
     113             : 
     114             : static uint
     115             : fd_hpack_rd_next_raw( fd_hpack_rd_t * rd,
     116         144 :                       fd_h2_hdr_t *   hdr ) {
     117         144 :   uchar const * end = rd->src_end;
     118         144 :   if( FD_UNLIKELY( rd->src >= end ) ) FD_LOG_CRIT(( "fd_hpack_rd_next called out of bounds" ));
     119             : 
     120         144 :   uint b0 = *(rd->src++);
     121             : 
     122         144 :   if( (b0&0xc0)==0x80 ) {
     123             :     /* name indexed, value indexed, index in [0,63], varint sz 0 */
     124          66 :     uint err = fd_hpack_rd_indexed( hdr, b0&0x7f );
     125          66 :     hdr->hint |= FD_H2_HDR_HINT_VALUE_INDEXED;
     126          66 :     return err;
     127          66 :   }
     128             : 
     129          78 :   if( b0==0x40 || b0==0x00 || b0==0x10 ) {
     130             :     /* name literal, value literal */
     131          12 :     if( FD_UNLIKELY( rd->src+2 > end ) ) return FD_H2_ERR_COMPRESSION;
     132             : 
     133          12 :     uint  name_word = *(rd->src++);
     134          12 :     ulong name_len  = fd_hpack_rd_varint( rd, name_word, 0x7f );
     135          12 :     if( FD_UNLIKELY( name_len==ULONG_MAX     ) ) return FD_H2_ERR_COMPRESSION;
     136          12 :     if( FD_UNLIKELY( rd->src+name_len >= end ) ) return FD_H2_ERR_COMPRESSION;
     137          12 :     uchar const * name_p = rd->src;
     138          12 :     rd->src += name_len;
     139             : 
     140          12 :     uint  value_word = *(rd->src++);
     141          12 :     ulong value_len  = fd_hpack_rd_varint( rd, value_word, 0x7f );
     142          12 :     if( FD_UNLIKELY( value_len==ULONG_MAX    ) ) return FD_H2_ERR_COMPRESSION;
     143          12 :     if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
     144          12 :     uchar const * value_p = rd->src;
     145          12 :     rd->src += value_len;
     146             : 
     147          12 :     hdr->name      = (char const *)name_p;
     148          12 :     hdr->name_len  = (ushort)name_len;
     149          12 :     hdr->value     = (char const *)value_p;
     150          12 :     hdr->value_len = (uint)value_len;
     151          12 :     hdr->hint      = fd_ushort_if( name_word&0x80,  FD_H2_HDR_HINT_NAME_HUFFMAN,  0 ) |
     152          12 :                      fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
     153          12 :     return FD_H2_SUCCESS;
     154          12 :   }
     155             : 
     156          66 :   if( (b0&0xc0)==0x40 || (b0&0xf0)==0x00 || (b0&0xf0)==0x10 ) {
     157             :     /* name indexed, value literal */
     158          66 :     uint  name_mask = (b0&0xc0)==0x40 ? 0x3f : 0x0f;
     159          66 :     ulong name_idx  = fd_hpack_rd_varint( rd, b0, name_mask );
     160             : 
     161          66 :     if( FD_UNLIKELY( rd->src >= end ) ) return FD_H2_ERR_COMPRESSION;
     162          66 :     uint  value_word = *(rd->src++);
     163          66 :     ulong value_len  = fd_hpack_rd_varint( rd, value_word, 0x7f );
     164          66 :     if( FD_UNLIKELY( value_len==ULONG_MAX    ) ) return FD_H2_ERR_COMPRESSION;
     165          66 :     if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
     166          63 :     uchar const * value_p = rd->src;
     167          63 :     rd->src += value_len;
     168             : 
     169          63 :     uint err = fd_hpack_rd_indexed( hdr, name_idx );
     170          63 :     if( FD_UNLIKELY( err ) ) return FD_H2_ERR_COMPRESSION;
     171          63 :     hdr->value     = (char const *)value_p;
     172          63 :     hdr->value_len = (uint)value_len;
     173          63 :     hdr->hint     |= fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
     174          63 :     return FD_H2_SUCCESS;
     175          63 :   }
     176             : 
     177           0 :   if( FD_UNLIKELY( (b0&0xc0)==0xc0 ) ) {
     178             :     /* name indexed, value indexed, index >=128 */
     179           0 :     ulong idx = fd_hpack_rd_varint( rd, b0, 0x7f ); /* may fail */
     180           0 :     return fd_hpack_rd_indexed( hdr, idx );
     181           0 :   }
     182             : 
     183             :   /* FIXME slow */
     184             :   /* Skip over Dynamic Table Size Updates */
     185           0 :   while( FD_LIKELY( rd->src < end ) ) {
     186           0 :     b0 = rd->src[0];
     187           0 :     if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
     188           0 :       ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
     189           0 :       if( FD_UNLIKELY( max_sz!=0UL ) ) return FD_H2_ERR_COMPRESSION;
     190           0 :       rd->src++;
     191           0 :     } else {
     192           0 :       break;
     193           0 :     }
     194           0 :   }
     195             : 
     196             :   /* Unknown HPACK instruction */
     197           0 :   return FD_H2_ERR_COMPRESSION;
     198           0 : }
     199             : 
     200             : /* fd_hpack_decoded_sz_max returns an upper bound for the number of
     201             :    decoded bytes given an arbitrary HPACK Huffman coding of enc_sz
     202             :    bytes.  The smallest HPACK symbol is 5 bits large.  Therefore, the
     203             :    true bound is closer to (enc_sz*8)/5.  To defend against possible
     204             :    bugs in huff_decode_table, we use a more conservative estimate,
     205             :    namely the greatest amount of bytes that nghttp2_hd_huff_decode can
     206             :    produce regardless of the content of huff_decode_table. */
     207             : 
     208             : static inline ulong
     209          24 : fd_hpack_decoded_sz_max( ulong enc_sz ) {
     210          24 :   return enc_sz*2UL;
     211          24 : }
     212             : 
     213             : uint
     214             : fd_hpack_rd_next( fd_hpack_rd_t * hpack_rd,
     215             :                   fd_h2_hdr_t *   hdr,
     216             :                   uchar **        scratch,
     217         144 :                   uchar *         scratch_end ) {
     218         144 :   uint err = fd_hpack_rd_next_raw( hpack_rd, hdr );
     219         144 :   if( FD_UNLIKELY( err ) ) return err;
     220             : 
     221         141 :   uchar * scratch_ = *scratch;
     222             : 
     223         141 :   if( hdr->hint & FD_H2_HDR_HINT_NAME_HUFFMAN ) {
     224           3 :     if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->name_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
     225           3 :     nghttp2_hd_huff_decode_context ctx[1];
     226           3 :     nghttp2_hd_huff_decode_context_init( ctx );
     227           3 :     nghttp2_buf buf = { .last = scratch_ };
     228           3 :     if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->name, hdr->name_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
     229           3 :     hdr->name     = (char const *)scratch_;
     230           3 :     hdr->name_len = (ushort)( buf.last-scratch_ );
     231           3 :     scratch_      = buf.last;
     232           3 :   }
     233             : 
     234         141 :   if( hdr->hint & FD_H2_HDR_HINT_VALUE_HUFFMAN ) {
     235          21 :     if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->value_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
     236          21 :     nghttp2_hd_huff_decode_context ctx[1];
     237          21 :     nghttp2_hd_huff_decode_context_init( ctx );
     238          21 :     nghttp2_buf buf = { .last = scratch_ };
     239          21 :     if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->value, hdr->value_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
     240          21 :     hdr->value     = (char const *)scratch_;
     241          21 :     hdr->value_len = (ushort)( buf.last-scratch_ );
     242          21 :     scratch_       = buf.last;
     243          21 :   }
     244             : 
     245         141 :   *scratch = scratch_;
     246         141 :   hdr->hint &= (ushort)~FD_H2_HDR_HINT_HUFFMAN;
     247         141 :   return FD_H2_SUCCESS;
     248         141 : }

Generated by: LCOV version 1.14