15#define _POSIX_C_SOURCE 200809L
28#include <sys/socket.h>
33#define GRPC_CLIENT_FRAME_HEADER 9
34#define GRPC_CLIENT_MAX_FRAME (16u * 1024u * 1024u)
35#define GRPC_CLIENT_DEFAULT_WINDOW 65535
36#define GRPC_CLIENT_HEADER_TABLE 4096
38#define H2_FRAME_DATA 0x00
39#define H2_FRAME_HEADERS 0x01
40#define H2_FRAME_RST_STREAM 0x03
41#define H2_FRAME_SETTINGS 0x04
42#define H2_FRAME_PUSH_PROMISE 0x05
43#define H2_FRAME_PING 0x06
44#define H2_FRAME_GOAWAY 0x07
45#define H2_FRAME_WINDOW_UPDATE 0x08
46#define H2_FRAME_CONTINUATION 0x09
47#define H2_FLAG_END_STREAM 0x01
48#define H2_FLAG_END_HEADERS 0x04
49#define H2_FLAG_ACK 0x01
110static uint64_t grpc_client_now_ms(
void) {
124static int grpc_client_wait(cwist_grpc_client *
c,
short events,
uint64_t deadline_ms) {
129 if (
now >= deadline_ms)
return -1;
139 if (
rc == 0)
return -1;
153static ssize_t grpc_client_read_some(cwist_grpc_client *
c,
void *buf,
size_t len,
172 n =
read(
c->fd, buf, len);
174 if (
n == 0)
return 0;
177 if (grpc_client_wait(
c,
POLLIN, deadline_ms) != 0)
return -1;
191static int grpc_client_read_all(cwist_grpc_client *
c,
void *buf,
size_t len,
uint64_t deadline_ms) {
196 if (
n <= 0)
return -1;
210static int grpc_client_write_all(cwist_grpc_client *
c,
const void *buf,
size_t len,
232 if (grpc_client_wait(
c,
POLLOUT, deadline_ms) != 0)
return -1;
256static int grpc_client_write_frame(cwist_grpc_client *
c,
uint8_t type,
uint8_t flags,
260 hdr[0] = (
uint8_t)((len >> 16) & 0xff);
261 hdr[1] = (
uint8_t)((len >> 8) & 0xff);
262 hdr[2] = (
uint8_t)(len & 0xff);
265 hdr[5] = (
uint8_t)((stream_id >> 24) & 0x7f);
266 hdr[6] = (
uint8_t)((stream_id >> 16) & 0xff);
267 hdr[7] = (
uint8_t)((stream_id >> 8) & 0xff);
268 hdr[8] = (
uint8_t)(stream_id & 0xff);
269 if (grpc_client_write_all(
c, hdr,
sizeof(hdr), deadline_ms) != 0)
return -1;
270 if (len && grpc_client_write_all(
c, payload, len, deadline_ms) != 0)
return -1;
290static int grpc_client_read_frame(cwist_grpc_client *
c, grpc_client_frame *
f,
293 if (grpc_client_read_all(
c, hdr,
sizeof(hdr), deadline_ms) != 0)
return -1;
303 if (!
f->payload)
return -1;
304 if (grpc_client_read_all(
c,
f->payload,
f->len, deadline_ms) != 0) {
320static void grpc_client_hpack_evict_to(cwist_grpc_client *
c,
size_t limit) {
321 while (
c->hpack_size >
limit &&
c->hpack_head) {
323 while ((*pp)->next)
pp = &(*pp)->next;
326 c->hpack_size -=
old->size;
341static int grpc_client_hpack_insert(cwist_grpc_client *
c,
const char *name,
const char *value) {
343 if (size >
c->hpack_cap) {
344 grpc_client_hpack_evict_to(
c, 0);
351 if (!
e->name || !
e->value) {
360 grpc_client_hpack_evict_to(
c,
c->hpack_cap - size);
361 e->next =
c->hpack_head;
363 c->hpack_size += size;
377static int grpc_client_hpack_get(cwist_grpc_client *
c,
uint32_t index,
const char **name,
378 const char **value) {
387 while (
e && --pos)
e =
e->next;
407static int grpc_client_hpack_decode(cwist_grpc_client *
c,
const uint8_t *buf,
size_t len,
415 const char *name, *value;
416 if (grpc_client_hpack_get(
c, index, &name, &value) != 0)
return -1;
417 cb(ctx, name, value);
418 }
else if (
first & 0xc0) {
425 if (grpc_client_hpack_get(
c,
name_index, &name, &unused) != 0)
return -1;
436 if (grpc_client_hpack_insert(
c, name, value) != 0) {
441 cb(ctx, name, value);
444 }
else if (
first & 0x20) {
457 if (grpc_client_hpack_get(
c,
name_index, &name, &unused) != 0)
return -1;
468 cb(ctx, name, value);
488 const char *name,
const char *value) {
490 if (pos + 1 >
cap)
return 0;
493 if (
n == 0)
return 0;
497 if (
n == 0)
return 0;
501 if (
n == 0)
return 0;
513static int grpc_client_tcp_connect(
const char *host,
uint16_t port) {
524 fd =
socket(
it->ai_family,
it->ai_socktype,
it->ai_protocol);
525 if (fd < 0)
continue;
526 if (
connect(fd,
it->ai_addr,
it->ai_addrlen) == 0)
break;
543static int grpc_client_tls_setup(cwist_grpc_client *
c,
const char *host,
int verify_peer,
546 if (!
c->ssl_ctx)
return -1;
556 if (!
c->ssl)
return -1;
586static int grpc_client_apply_settings(cwist_grpc_client *
c,
const uint8_t *payload,
uint32_t len,
588 if (len % 6 != 0)
return -1;
589 for (
uint32_t off = 0; off + 6 <= len; off += 6) {
592 ((
uint32_t)payload[off + 4] << 8) | payload[off + 5];
595 if (value != 0)
return -1;
598 if (value > 0x7fffffffu)
return -1;
599 if (
c->active)
c->active->send_window += (
int64_t)value -
c->peer_initial_window;
600 c->peer_initial_window = value;
604 c->peer_max_frame = value;
626 if (!host || !port)
return NULL;
632 c->next_stream_id = 1;
633 c->peer_max_frame = 16384;
647 snprintf(buf,
sizeof(buf),
"%s:%u", host, (
unsigned)port);
649 if (
c->authority)
strcpy(
c->authority, buf);
651 if (!
c->authority)
goto fail;
653 c->fd = grpc_client_tcp_connect(host, port);
654 if (
c->fd < 0)
goto fail;
659 if (options && options->
use_tls) {
664 static const char preface[] =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
673 if (grpc_client_read_frame(
c, &
f, deadline) != 0)
goto fail;
677 rc = grpc_client_apply_settings(
c,
f.payload,
f.len, deadline);
704 if (
c->fd >= 0 && !
c->dead)
712 grpc_client_hpack_evict_to(
c, 0);
730 if (!node)
return -1;
737 node->len = message->
len;
740 call->msg_tail->next = node;
742 call->msg_head = node;
743 call->msg_tail = node;
763static void grpc_client_on_header(
void *ctx,
const char *name,
const char *value) {
765 if (
strcmp(name,
":status") == 0) {
770 hc->http_status = (
end != value && *
end ==
'\0' &&
v >= 100 &&
v <= 599) ? (
int)
v : 0;
771 }
else if (
strcmp(name,
"grpc-status") == 0) {
777 hc->call->status = (
end != value && *
end ==
'\0' &&
v >= 0 &&
v <= 0x7fffffff)
780 hc->call->status_seen = 1;
781 }
else if (
strcmp(name,
"grpc-message") == 0) {
784 if (hc->call->status_message)
strcpy(hc->call->status_message, value);
785 }
else if (
strcmp(name,
"grpc-retry-pushback-ms") == 0) {
792 hc->call->pushback_seen = 1;
810static int grpc_client_read_header_block(cwist_grpc_client *
c, cwist_grpc_call *call,
811 grpc_client_frame *
first,
uint8_t **out,
size_t *out_len,
815 if (!
block)
return -1;
816 size_t len =
first->len;
821 if (grpc_client_read_frame(
c, &
cont, deadline_ms) != 0 ||
856static int grpc_client_pump(cwist_grpc_call *call) {
858 if (call->deadline_ms && grpc_client_now_ms() >= call->deadline_ms)
goto deadline;
860 if (grpc_client_read_frame(
c, &
f, call->deadline_ms) != 0) {
861 if (call->deadline_ms && grpc_client_now_ms() >= call->deadline_ms)
goto deadline;
870 rc = grpc_client_apply_settings(
c,
f.payload,
f.len, call->deadline_ms);
884 ((
uint32_t)
f.payload[2] << 8) |
f.payload[3]);
889 if (
f.stream_id == 0)
890 c->conn_send_window +=
inc;
891 else if (
f.stream_id == call->stream_id)
892 call->send_window +=
inc;
897 if (!call->trailers) {
900 if (
f.len >= 4 && !call->headers_received) {
907 call->stream_ended = 1;
910 call->status_message =
NULL;
915 if (
f.stream_id == call->stream_id) {
916 call->stream_ended = 1;
917 if (!call->trailers) {
920 if (
f.len == 4 && !call->headers_received) {
924 if (
code == 0x7) call->refused_stream = 1;
933 if (
f.stream_id != call->stream_id)
break;
938 call->deadline_ms) != 0) {
943 if (grpc_client_hpack_decode(
c,
block,
block_len, grpc_client_on_header, &hc) != 0) {
949 if (hc.http_status && hc.http_status != 200) {
951 call->stream_ended = 1;
955 if (call->status_message)
956 snprintf(call->status_message, 32,
"HTTP status %d", hc.http_status);
965 if (!call->headers_received) call->trailers_only = 1;
967 call->stream_ended = 1;
972 call->headers_received = 1;
977 if (
f.stream_id != call->stream_id)
break;
982 call->stream_ended = 1;
986 if (call->status_message)
987 strcpy(call->status_message,
"malformed gRPC message frame");
998 call->deadline_ms) != 0 ||
1000 call->deadline_ms) != 0) {
1008 call->stream_ended = 1;
1009 if (!call->trailers) {
1024 call->stream_ended = 1;
1028 if (call->status_message)
strcpy(call->status_message,
"deadline exceeded");
1030 4, grpc_client_now_ms() + 500);
1035 call->stream_ended = 1;
1039 if (call->status_message)
strcpy(call->status_message,
"transport error");
1061 c->conn_send_window < call->send_window ?
c->conn_send_window : call->send_window;
1064 if (
chunk >
c->peer_max_frame)
chunk =
c->peer_max_frame;
1067 int rc = grpc_client_pump(call);
1068 if (
rc != 0)
return -1;
1128 memset(call, 0,
sizeof(*call));
1130 call->stream_id =
c->next_stream_id;
1131 c->next_stream_id += 2;
1132 call->deadline_ms = timeout_ms ? grpc_client_now_ms() + timeout_ms : 0;
1133 call->send_window =
c->peer_initial_window;
1140 block[pos++] = 0x83;
1141 block[pos++] =
c->ssl ? 0x87 : 0x86;
1142 size_t n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 4,
NULL, method);
1143 if (
n == 0)
goto fail;
1145 n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 1,
NULL,
c->authority);
1146 if (
n == 0)
goto fail;
1148 n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 31,
NULL,
"application/grpc");
1149 if (
n == 0)
goto fail;
1151 n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 0,
"te",
"trailers");
1152 if (
n == 0)
goto fail;
1156 uint64_t v = timeout_ms > 99999999u ? 99999999u : timeout_ms;
1158 n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 0,
"grpc-timeout",
1160 if (
n == 0)
goto fail;
1167 n = grpc_client_enc_literal(
block + pos,
sizeof(
block) - pos, 0,
1169 if (
n == 0)
goto fail;
1173 (
uint32_t)pos, call->deadline_ms) != 0)
1204 if (!call || !out)
return -1;
1205 while (!call->msg_head) {
1206 if (call->stream_ended)
return call->failed ? -1 : 0;
1207 if (grpc_client_pump(call) != 0 && !call->msg_head)
return call->failed ? -1 : 0;
1210 call->msg_head = node->next;
1211 if (!call->msg_head) call->msg_tail =
NULL;
1213 call->recv_buf = node->data;
1215 out->
data = node->data;
1216 out->
len = node->len;
1229 if (!call)
return -1;
1230 while (!call->headers_received && !call->stream_ended) {
1231 if (grpc_client_pump(call) != 0 && !call->headers_received && !call->stream_ended)
1234 return call->headers_received ? 0 : 1;
1242 return call && call->headers_received;
1251 if (!call || call->headers_received || call->trailers)
return 0;
1252 return call->refused_stream || call->goaway_refused;
1262 if (!call || !call->pushback_seen)
return 0;
1272 return !
c ||
c->dead ||
c->goaway;
1293 if (message) *message =
NULL;
1296 while (!call->stream_ended) {
1297 if (grpc_client_pump(call) != 0 && !call->stream_ended)
break;
1299 if (message) *message = call->status_message;
1300 return call->status;
1309 if (!call || call->stream_ended)
return;
1312 4, grpc_client_now_ms() + 500);
1313 call->stream_ended = 1;
1328 if (
c->active == call)
c->active =
NULL;
1331 while (call->msg_head) {
1335 call->msg_head = next;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
@ CWIST_GRPC_DEADLINE_EXCEEDED
int cwist_grpc_decoder_feed(cwist_grpc_decoder *decoder, const void *data, size_t len, cwist_grpc_message_callback callback, void *ctx)
Feed bytes into an incremental gRPC message decoder.
void cwist_grpc_decoder_init(cwist_grpc_decoder *decoder, size_t max_message_size)
Initialize an incremental gRPC message decoder.
enum cwist_grpc_status cwist_grpc_status_t
void cwist_grpc_decoder_destroy(cwist_grpc_decoder *decoder)
Release all resources held by a decoder and reset it to zero.
int cwist_grpc_encode_message(const void *payload, size_t payload_len, uint8_t compressed, uint8_t **out, size_t *out_len)
Wrap a payload in a gRPC length-prefixed message frame.
struct grpc_client_hpack_entry grpc_client_hpack_entry
#define H2_FRAME_RST_STREAM
cwist_grpc_client * cwist_grpc_client_connect(const char *host, uint16_t port, const cwist_grpc_client_options *options)
Connect to a gRPC server and complete the HTTP/2 handshake.
cwist_grpc_call * cwist_grpc_call_start_ex(cwist_grpc_client *c, const char *method, const void *request, size_t request_len, uint64_t timeout_ms, uint32_t previous_attempts)
Start a unary call, with a gRFC A6 retry-attempt marker.
int cwist_grpc_client_dead(cwist_grpc_client *c)
Whether the client connection is unusable.
int cwist_grpc_call_recv(cwist_grpc_call *call, cwist_grpc_message *out)
Receive the next response message of a call.
#define GRPC_CLIENT_MAX_FRAME
int cwist_grpc_call_committed(const cwist_grpc_call *call)
Whether response headers have committed the RPC (gRFC A6).
struct grpc_client_msg grpc_client_msg
cwist_grpc_call * cwist_grpc_call_start(cwist_grpc_client *c, const char *method, const void *request, size_t request_len, uint64_t timeout_ms)
Start a unary call (no previous-attempt marker).
void cwist_grpc_client_close(cwist_grpc_client *c)
Close the client: send GOAWAY, tear down TLS and the socket, free all state.
#define GRPC_CLIENT_HEADER_TABLE
#define H2_FLAG_END_STREAM
int cwist_grpc_call_await_headers(cwist_grpc_call *call)
Block until response headers arrive or the stream ends.
int cwist_grpc_call_refused(const cwist_grpc_call *call)
Whether the call failed before commit in a retryable way (gRFC A6 case 3: REFUSED_STREAM before heade...
#define GRPC_CLIENT_FRAME_HEADER
struct grpc_client_frame grpc_client_frame
#define H2_FLAG_END_HEADERS
#define H2_FRAME_WINDOW_UPDATE
void cwist_grpc_call_cancel(cwist_grpc_call *call)
Cancel a call: send RST_STREAM(CANCEL), mark it failed with CWIST_GRPC_CANCELLED, and end its stream.
int cwist_grpc_call_retry_pushback_ms(const cwist_grpc_call *call, int32_t *out_ms)
Report the server-provided retry pushback delay, if any.
void cwist_grpc_call_destroy(cwist_grpc_call *call)
Destroy a call: cancel it if still open, release it as the client's active call, and free all queued ...
#define H2_FRAME_SETTINGS
cwist_grpc_status_t cwist_grpc_call_finish(cwist_grpc_call *call, const char **message)
Drain the call to completion and return its final status.
cwist_grpc_status_t cwist_grpc_call_status(const cwist_grpc_call *call)
Current gRPC status code of a call.
struct grpc_client_headers_ctx grpc_client_headers_ctx
void(* grpc_client_header_cb)(void *ctx, const char *name, const char *value)
#define H2_FRAME_CONTINUATION
#define GRPC_CLIENT_DEFAULT_WINDOW
HTTP/2-based gRPC client (h2c cleartext, or TLS with ALPN "h2").
struct cwist_grpc_client cwist_grpc_client
struct cwist_grpc_call cwist_grpc_call
int h2_decode_integer(const unsigned char *buf, size_t len, size_t *pos, uint8_t prefix_bits, uint32_t *value)
Decode an HPACK integer.
const cwist_http2_static_header * h2_static_header(uint32_t index)
Resolve an HPACK static table index (1-based); NULL when out of range.
size_t h2_encode_string(unsigned char *dst, size_t dst_cap, const char *str)
Encode an HPACK string (literal, non-huffman); 0 when dst is too small.
char * h2_decode_string(const unsigned char *buf, size_t len, size_t *pos)
Decode an HPACK string (literal or huffman).
size_t h2_encode_integer(unsigned char *dst, size_t dst_cap, uint32_t value, uint8_t prefix_bits)
Encode an HPACK integer; returns bytes written, 0 when dst is too small.
mbedTLS TLS backend: OpenSSL-shaped seam over the CWIST TLS subset.
struct cwist_mtls_ssl SSL
#define SSL_get0_alpn_selected
#define SSL_set_tlsext_host_name
#define SSL_ERROR_WANT_READ
#define SSL_CTX_set_default_verify_paths
#define SSL_CTX_set_alpn_protos
#define SSL_CTX_set_verify
#define TLS_client_method
#define SSL_ERROR_ZERO_RETURN
struct cwist_mtls_ctx SSL_CTX
#define SSL_ERROR_WANT_WRITE
const char * tls_server_name
uint64_t connect_timeout_ms