Line data Source code
1 : #include "fd_grpc_codec.h" 2 : #include "../h2/fd_hpack.h" 3 : #include "../h2/fd_hpack_wr.h" 4 : 5 : #include <limits.h> 6 : 7 : static int 8 15 : fd_hpack_wr_content_type_grpc( fd_h2_rbuf_t * rbuf_tx ) { 9 15 : static char const code[] = 10 15 : "\x5f" "\x16" "application/grpc+proto"; 11 15 : if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx)<sizeof(code)-1 ) ) return 0; 12 15 : fd_h2_rbuf_push( rbuf_tx, code, sizeof(code)-1 ); 13 15 : return 1; 14 15 : } 15 : 16 : int 17 : fd_grpc_h2_gen_request_hdrs( fd_grpc_req_hdrs_t const * req, 18 : fd_h2_rbuf_t * rbuf_tx, 19 : char const * version, 20 15 : ulong version_len ) { 21 15 : if( FD_UNLIKELY( !fd_hpack_wr_method_post( rbuf_tx ) ) ) return 0; 22 15 : if( FD_UNLIKELY( !fd_hpack_wr_scheme( rbuf_tx, 1 ) ) ) return 0; 23 15 : if( FD_UNLIKELY( !fd_hpack_wr_path( rbuf_tx, req->path, req->path_len ) ) ) return 0; 24 15 : if( req->host_len ) { 25 6 : if( FD_UNLIKELY( !fd_hpack_wr_authority( rbuf_tx, req->host, req->host_len, req->port ) ) ) return 0; 26 6 : } 27 15 : if( FD_UNLIKELY( !fd_hpack_wr_trailers( rbuf_tx ) ) ) return 0; 28 15 : if( FD_UNLIKELY( !fd_hpack_wr_content_type_grpc( rbuf_tx ) ) ) return 0; 29 : 30 15 : static char const user_agent[] = "grpc-firedancer/"; 31 15 : ulong const user_agent_len = sizeof(user_agent)-1 + version_len; 32 15 : if( FD_UNLIKELY( !fd_hpack_wr_user_agent( rbuf_tx, user_agent_len ) ) ) return 0; 33 15 : fd_h2_rbuf_push( rbuf_tx, user_agent, sizeof(user_agent)-1 ); 34 15 : fd_h2_rbuf_push( rbuf_tx, version, version_len ); 35 : 36 15 : if( req->bearer_auth_len ) { 37 3 : if( FD_UNLIKELY( !fd_hpack_wr_auth_bearer( rbuf_tx, req->bearer_auth, req->bearer_auth_len ) ) ) return 0; 38 3 : } 39 15 : return 1; 40 15 : } 41 : 42 : /* fd_grpc_h2_parse_num parses a strictly-decimal unsigned integer from 43 : a header value. Every byte must be in ['0','9']. Returns the parsed 44 : value, or UINT_MAX on failure (empty, any non-digit character 45 : including whitespace/sign/hex prefix, or overflow). */ 46 : 47 : static uint 48 : fd_grpc_h2_parse_num( char const * num, 49 105 : ulong num_len ) { 50 105 : if( FD_UNLIKELY( !num_len || num_len>10UL ) ) return UINT_MAX; 51 102 : ulong val = 0; 52 354 : for( ulong i=0; i<num_len; i++ ) { 53 267 : uint d = (uint)( (uchar)num[i] - '0' ); 54 267 : if( FD_UNLIKELY( d>9U ) ) return UINT_MAX; 55 252 : val = val*10UL + d; 56 252 : } 57 87 : if( FD_UNLIKELY( val>(ulong)UINT_MAX ) ) return UINT_MAX; 58 87 : return (uint)val; 59 87 : } 60 : 61 : int 62 : fd_grpc_h2_read_response_hdrs( fd_grpc_resp_hdrs_t * resp, 63 : fd_h2_hdr_matcher_t const * matcher, 64 : uchar const * payload, 65 129 : ulong payload_sz ) { 66 129 : fd_hpack_rd_t hpack_rd[1]; 67 129 : fd_hpack_rd_init( hpack_rd, payload, payload_sz ); 68 252 : while( !fd_hpack_rd_done( hpack_rd ) ) { 69 165 : static FD_TL uchar scratch_buf[ 4096 ]; 70 165 : uchar * scratch = scratch_buf; 71 165 : fd_h2_hdr_t hdr[1]; 72 165 : uint err = fd_hpack_rd_next( hpack_rd, hdr, &scratch, scratch_buf+sizeof(scratch_buf) ); 73 165 : if( FD_UNLIKELY( err ) ) { 74 6 : FD_LOG_WARNING(( "Failed to parse response headers (%u-%s)", err, fd_h2_strerror( err ) )); 75 6 : return FD_H2_ERR_PROTOCOL; 76 6 : } 77 : 78 159 : int hdr_idx = fd_h2_hdr_match( matcher, hdr->name, hdr->name_len, hdr->hint ); 79 159 : switch( hdr_idx ) { 80 105 : case FD_H2_HDR_STATUS: { 81 105 : if( FD_UNLIKELY( hdr->value_len!=3 ) ) { 82 36 : FD_LOG_WARNING(( "Invalid HTTP status length %u", hdr->value_len )); 83 36 : return FD_H2_ERR_PROTOCOL; 84 36 : } 85 69 : uint h2_status = fd_grpc_h2_parse_num( hdr->value, hdr->value_len ); 86 : /* [100,999] matches the http crate's StatusCode::from_bytes used 87 : by tonic/h2 (first digit 1-9). RFC 9110 only defines 100-599 88 : but the h2 crate accepts the full 3-digit range. */ 89 69 : if( FD_UNLIKELY( h2_status<100U || h2_status>999U ) ) { 90 0 : FD_LOG_WARNING(( "Invalid HTTP status %u", h2_status )); 91 0 : return FD_H2_ERR_PROTOCOL; 92 0 : } 93 69 : resp->h2_status = h2_status; 94 69 : break; 95 69 : } 96 15 : case FD_H2_HDR_CONTENT_TYPE: 97 15 : resp->is_grpc_proto = 98 15 : ( ( hdr->value_len==(sizeof("application/grpc")-1UL) && 99 15 : fd_memeq( hdr->value, "application/grpc", sizeof("application/grpc")-1UL ) ) || 100 15 : ( hdr->value_len==(sizeof("application/grpc+proto")-1UL) && 101 12 : fd_memeq( hdr->value, "application/grpc+proto", sizeof("application/grpc+proto")-1UL ) ) ); 102 15 : break; 103 36 : case FD_GRPC_HDR_STATUS: { 104 36 : uint grpc_status = fd_grpc_h2_parse_num( hdr->value, hdr->value_len ); 105 : /* Tonic's Code::from_bytes maps any unrecognized grpc-status 106 : (including >16, non-numeric, empty) to Code::Unknown rather 107 : than rejecting the stream. We match that behavior here so that 108 : in all cases, we don't cause spurious RST_STREAMs. */ 109 36 : if( FD_UNLIKELY( grpc_status>FD_GRPC_STATUS_UNAUTHENTICATED ) ) { 110 27 : int trunc_len = (int)fd_ulong_min( hdr->value_len, 32UL ); 111 27 : FD_LOG_WARNING(( "Unknown grpc-status \"%.*s\", treating as UNKNOWN", trunc_len, hdr->value )); 112 27 : grpc_status = FD_GRPC_STATUS_UNKNOWN; 113 27 : } 114 36 : resp->grpc_status = grpc_status; 115 36 : break; 116 69 : } 117 3 : case FD_GRPC_HDR_MESSAGE: 118 3 : resp->grpc_msg_len = (uint)fd_ulong_min( hdr->value_len, sizeof(resp->grpc_msg) ); 119 3 : if( resp->grpc_msg_len ) { 120 3 : fd_memcpy( resp->grpc_msg, hdr->value, resp->grpc_msg_len ); 121 3 : } 122 3 : break; 123 159 : } 124 159 : } 125 87 : return FD_H2_SUCCESS; 126 129 : } 127 : 128 : char const * 129 9 : fd_grpc_status_cstr( uint status ) { 130 9 : switch( status ) { 131 3 : case FD_GRPC_STATUS_OK: return "ok"; 132 0 : case FD_GRPC_STATUS_CANCELLED: return "cancelled"; 133 6 : case FD_GRPC_STATUS_UNKNOWN: return "unknown"; 134 0 : case FD_GRPC_STATUS_INVALID_ARGUMENT: return "invalid argument"; 135 0 : case FD_GRPC_STATUS_DEADLINE_EXCEEDED: return "deadline exceeded"; 136 0 : case FD_GRPC_STATUS_NOT_FOUND: return "not found"; 137 0 : case FD_GRPC_STATUS_ALREADY_EXISTS: return "already exists"; 138 0 : case FD_GRPC_STATUS_PERMISSION_DENIED: return "permission denied"; 139 0 : case FD_GRPC_STATUS_RESOURCE_EXHAUSTED: return "resource exhausted"; 140 0 : case FD_GRPC_STATUS_FAILED_PRECONDITION: return "failed precondition"; 141 0 : case FD_GRPC_STATUS_ABORTED: return "aborted"; 142 0 : case FD_GRPC_STATUS_OUT_OF_RANGE: return "out of range"; 143 0 : case FD_GRPC_STATUS_UNIMPLEMENTED: return "unimplemented"; 144 0 : case FD_GRPC_STATUS_INTERNAL: return "internal"; 145 0 : case FD_GRPC_STATUS_UNAVAILABLE: return "unavailable"; 146 0 : case FD_GRPC_STATUS_DATA_LOSS: return "data loss"; 147 0 : case FD_GRPC_STATUS_UNAUTHENTICATED: return "unauthenticated"; 148 0 : default: return "unknown"; 149 9 : } 150 9 : }