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 0 : fd_hpack_wr_content_type_grpc( fd_h2_rbuf_t * rbuf_tx ) {
9 0 : static char const code[] =
10 0 : "\x5f" "\x16" "application/grpc+proto";
11 0 : if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx)<sizeof(code)-1 ) ) return 0;
12 0 : fd_h2_rbuf_push( rbuf_tx, code, sizeof(code)-1 );
13 0 : return 1;
14 0 : }
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 0 : ulong version_len ) {
21 0 : if( FD_UNLIKELY( !fd_hpack_wr_method_post( rbuf_tx ) ) ) return 0;
22 0 : if( FD_UNLIKELY( !fd_hpack_wr_scheme( rbuf_tx, 1 ) ) ) return 0;
23 0 : if( FD_UNLIKELY( !fd_hpack_wr_path( rbuf_tx, req->path, req->path_len ) ) ) return 0;
24 0 : if( req->host_len ) {
25 0 : if( FD_UNLIKELY( !fd_hpack_wr_authority( rbuf_tx, req->host, req->host_len, req->port ) ) ) return 0;
26 0 : }
27 0 : if( FD_UNLIKELY( !fd_hpack_wr_trailers( rbuf_tx ) ) ) return 0;
28 0 : if( FD_UNLIKELY( !fd_hpack_wr_content_type_grpc( rbuf_tx ) ) ) return 0;
29 :
30 0 : static char const user_agent[] = "grpc-firedancer/";
31 0 : ulong const user_agent_len = sizeof(user_agent)-1 + version_len;
32 0 : if( FD_UNLIKELY( !fd_hpack_wr_user_agent( rbuf_tx, user_agent_len ) ) ) return 0;
33 0 : fd_h2_rbuf_push( rbuf_tx, user_agent, sizeof(user_agent)-1 );
34 0 : fd_h2_rbuf_push( rbuf_tx, version, version_len );
35 :
36 0 : if( req->bearer_auth_len ) {
37 0 : if( FD_UNLIKELY( !fd_hpack_wr_auth_bearer( rbuf_tx, req->bearer_auth, req->bearer_auth_len ) ) ) return 0;
38 0 : }
39 0 : return 1;
40 0 : }
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 0 : ulong num_len ) {
50 0 : if( FD_UNLIKELY( !num_len || num_len>10UL ) ) return UINT_MAX;
51 0 : ulong val = 0;
52 0 : for( ulong i=0; i<num_len; i++ ) {
53 0 : uint d = (uint)( (uchar)num[i] - '0' );
54 0 : if( FD_UNLIKELY( d>9U ) ) return UINT_MAX;
55 0 : val = val*10UL + d;
56 0 : }
57 0 : if( FD_UNLIKELY( val>(ulong)UINT_MAX ) ) return UINT_MAX;
58 0 : return (uint)val;
59 0 : }
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 0 : ulong payload_sz ) {
66 0 : fd_hpack_rd_t hpack_rd[1];
67 0 : fd_hpack_rd_init( hpack_rd, payload, payload_sz );
68 0 : while( !fd_hpack_rd_done( hpack_rd ) ) {
69 0 : static FD_TL uchar scratch_buf[ 4096 ];
70 0 : uchar * scratch = scratch_buf;
71 0 : fd_h2_hdr_t hdr[1];
72 0 : uint err = fd_hpack_rd_next( hpack_rd, hdr, &scratch, scratch_buf+sizeof(scratch_buf) );
73 0 : if( FD_UNLIKELY( err ) ) {
74 0 : FD_LOG_WARNING(( "Failed to parse response headers (%u-%s)", err, fd_h2_strerror( err ) ));
75 0 : return FD_H2_ERR_PROTOCOL;
76 0 : }
77 :
78 0 : int hdr_idx = fd_h2_hdr_match( matcher, hdr->name, hdr->name_len, hdr->hint );
79 0 : switch( hdr_idx ) {
80 0 : case FD_H2_HDR_STATUS: {
81 0 : if( FD_UNLIKELY( hdr->value_len!=3 ) ) {
82 0 : FD_LOG_WARNING(( "Invalid HTTP status length %u", hdr->value_len ));
83 0 : return FD_H2_ERR_PROTOCOL;
84 0 : }
85 0 : 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 0 : 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 0 : resp->h2_status = h2_status;
94 0 : break;
95 0 : }
96 0 : case FD_H2_HDR_CONTENT_TYPE:
97 0 : resp->is_grpc_proto =
98 0 : ( ( hdr->value_len==(sizeof("application/grpc")-1UL) &&
99 0 : fd_memeq( hdr->value, "application/grpc", sizeof("application/grpc")-1UL ) ) ||
100 0 : ( hdr->value_len==(sizeof("application/grpc+proto")-1UL) &&
101 0 : fd_memeq( hdr->value, "application/grpc+proto", sizeof("application/grpc+proto")-1UL ) ) );
102 0 : break;
103 0 : case FD_GRPC_HDR_STATUS: {
104 0 : 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 0 : if( FD_UNLIKELY( grpc_status>FD_GRPC_STATUS_UNAUTHENTICATED ) ) {
110 0 : int trunc_len = (int)fd_ulong_min( hdr->value_len, 32UL );
111 0 : FD_LOG_WARNING(( "Unknown grpc-status \"%.*s\", treating as UNKNOWN", trunc_len, hdr->value ));
112 0 : grpc_status = FD_GRPC_STATUS_UNKNOWN;
113 0 : }
114 0 : resp->grpc_status = grpc_status;
115 0 : break;
116 0 : }
117 0 : case FD_GRPC_HDR_MESSAGE:
118 0 : resp->grpc_msg_len = (uint)fd_ulong_min( hdr->value_len, sizeof(resp->grpc_msg) );
119 0 : if( resp->grpc_msg_len ) {
120 0 : fd_memcpy( resp->grpc_msg, hdr->value, resp->grpc_msg_len );
121 0 : }
122 0 : break;
123 0 : }
124 0 : }
125 0 : return FD_H2_SUCCESS;
126 0 : }
127 :
128 : char const *
129 0 : fd_grpc_status_cstr( uint status ) {
130 0 : switch( status ) {
131 0 : case FD_GRPC_STATUS_OK: return "ok";
132 0 : case FD_GRPC_STATUS_CANCELLED: return "cancelled";
133 0 : 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 0 : }
150 0 : }
|