cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
grpc_client.c
Go to the documentation of this file.
1
15#define _POSIX_C_SOURCE 200809L
18#include <arpa/inet.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <netdb.h>
22#include <poll.h>
23#include <pthread.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <strings.h>
28#include <sys/socket.h>
29#include <time.h>
30#include <unistd.h>
31#include <openssl/ssl.h>
32
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
37
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
50
51typedef struct grpc_client_hpack_entry {
52 char *name;
53 char *value;
54 size_t size;
55 struct grpc_client_hpack_entry *next;
57
58typedef struct grpc_client_msg {
59 uint8_t *data;
60 size_t len;
61 struct grpc_client_msg *next;
63
64struct cwist_grpc_call {
65 cwist_grpc_client *client;
66 uint32_t stream_id;
67 uint64_t deadline_ms; /* monotonic; 0 = none */
68 int64_t send_window;
69 cwist_grpc_decoder decoder;
70 grpc_client_msg *msg_head;
71 grpc_client_msg *msg_tail;
72 uint8_t *recv_buf; /* backing store for the last returned message */
74 int status_seen; /* grpc-status trailer/header arrived */
75 char *status_message;
76 int trailers; /* trailer HEADERS seen */
77 int stream_ended; /* END_STREAM or RST_STREAM seen */
78 int failed;
79 /* gRFC A6 retry state: committed once Response-Headers arrive. */
80 int headers_received; /* non-trailers response HEADERS seen (committed) */
81 int trailers_only; /* ended with a Trailers-Only response */
82 int refused_stream; /* RST_STREAM(REFUSED_STREAM) before headers */
83 int goaway_refused; /* GOAWAY last_stream_id < our stream id */
84 int pushback_seen; /* grpc-retry-pushback-ms trailer arrived */
85 int32_t pushback_ms; /* negative or unparseable = do not retry */
86};
87
88struct cwist_grpc_client {
89 int fd;
90 SSL *ssl;
91 SSL_CTX *ssl_ctx;
92 pthread_mutex_t mu; /* one active call per connection */
93 cwist_grpc_call *active;
94 uint32_t next_stream_id;
95 uint32_t peer_max_frame;
96 uint32_t peer_initial_window;
97 int64_t conn_send_window;
98 grpc_client_hpack_entry *hpack_head;
99 size_t hpack_size;
100 size_t hpack_cap;
101 int goaway;
102 int dead;
103 char *authority;
104};
105
110static uint64_t grpc_client_now_ms(void) {
111 struct timespec ts;
113 return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
114}
115
124static int grpc_client_wait(cwist_grpc_client *c, short events, uint64_t deadline_ms) {
125 for (;;) {
126 int timeout = -1;
127 if (deadline_ms) {
128 uint64_t now = grpc_client_now_ms();
129 if (now >= deadline_ms) return -1;
130 uint64_t left = deadline_ms - now;
131 timeout = left > 600000 ? 600000 : (int)left;
132 }
133 struct pollfd pfd = {.fd = c->fd, .events = events};
134 int rc = poll(&pfd, 1, timeout);
135 if (rc > 0) {
136 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) return -1;
137 return 0;
138 }
139 if (rc == 0) return -1; /* timeout */
140 if (errno == EINTR) continue;
141 return -1;
142 }
143}
144
153static ssize_t grpc_client_read_some(cwist_grpc_client *c, void *buf, size_t len,
154 uint64_t deadline_ms) {
155 for (;;) {
156 ssize_t n;
157 if (c->ssl) {
158 n = SSL_read(c->ssl, buf, (int)len);
159 if (n <= 0) {
160 int err = SSL_get_error(c->ssl, (int)n);
162 if (grpc_client_wait(c, err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN,
163 deadline_ms) != 0)
164 return -1;
165 continue;
166 }
167 if (err == SSL_ERROR_ZERO_RETURN) return 0;
168 return -1;
169 }
170 return n;
171 }
172 n = read(c->fd, buf, len);
173 if (n > 0) return n;
174 if (n == 0) return 0;
175 if (errno == EINTR) continue;
176 if (errno == EAGAIN || errno == EWOULDBLOCK) {
177 if (grpc_client_wait(c, POLLIN, deadline_ms) != 0) return -1;
178 continue;
179 }
180 return -1;
181 }
182}
183
191static int grpc_client_read_all(cwist_grpc_client *c, void *buf, size_t len, uint64_t deadline_ms) {
192 uint8_t *p = buf;
193 size_t got = 0;
194 while (got < len) {
195 ssize_t n = grpc_client_read_some(c, p + got, len - got, deadline_ms);
196 if (n <= 0) return -1;
197 got += (size_t)n;
198 }
199 return 0;
200}
201
210static int grpc_client_write_all(cwist_grpc_client *c, const void *buf, size_t len,
211 uint64_t deadline_ms) {
212 const uint8_t *p = buf;
213 while (len) {
214 ssize_t n;
215 if (c->ssl) {
216 n = SSL_write(c->ssl, p, (int)len);
217 if (n <= 0) {
218 int err = SSL_get_error(c->ssl, (int)n);
220 if (grpc_client_wait(c, err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN,
221 deadline_ms) != 0)
222 return -1;
223 continue;
224 }
225 return -1;
226 }
227 } else {
228 n = write(c->fd, p, len);
229 if (n < 0) {
230 if (errno == EINTR) continue;
231 if (errno == EAGAIN || errno == EWOULDBLOCK) {
232 if (grpc_client_wait(c, POLLOUT, deadline_ms) != 0) return -1;
233 continue;
234 }
235 return -1;
236 }
237 }
238 p += n;
239 len -= (size_t)n;
240 }
241 return 0;
242}
243
256static int grpc_client_write_frame(cwist_grpc_client *c, uint8_t type, uint8_t flags,
257 uint32_t stream_id, const void *payload, uint32_t len,
258 uint64_t deadline_ms) {
260 hdr[0] = (uint8_t)((len >> 16) & 0xff);
261 hdr[1] = (uint8_t)((len >> 8) & 0xff);
262 hdr[2] = (uint8_t)(len & 0xff);
263 hdr[3] = type;
264 hdr[4] = flags;
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;
271 return 0;
272}
273
274typedef struct grpc_client_frame {
275 uint8_t type;
276 uint8_t flags;
277 uint32_t stream_id;
278 uint32_t len;
279 uint8_t *payload; /* owned; caller frees */
281
290static int grpc_client_read_frame(cwist_grpc_client *c, grpc_client_frame *f,
291 uint64_t deadline_ms) {
293 if (grpc_client_read_all(c, hdr, sizeof(hdr), deadline_ms) != 0) return -1;
294 f->len = ((uint32_t)hdr[0] << 16) | ((uint32_t)hdr[1] << 8) | hdr[2];
295 f->type = hdr[3];
296 f->flags = hdr[4];
297 f->stream_id = (((uint32_t)hdr[5] & 0x7f) << 24) | ((uint32_t)hdr[6] << 16) |
298 ((uint32_t)hdr[7] << 8) | hdr[8];
299 if (f->len > GRPC_CLIENT_MAX_FRAME) return -1;
300 f->payload = NULL;
301 if (f->len) {
302 f->payload = cwist_alloc(f->len);
303 if (!f->payload) return -1;
304 if (grpc_client_read_all(c, f->payload, f->len, deadline_ms) != 0) {
305 cwist_free(f->payload);
306 f->payload = NULL;
307 return -1;
308 }
309 }
310 return 0;
311}
312
313/* --- HPACK decoding (per-connection dynamic table) --- */
314
320static void grpc_client_hpack_evict_to(cwist_grpc_client *c, size_t limit) {
321 while (c->hpack_size > limit && c->hpack_head) {
322 grpc_client_hpack_entry **pp = &c->hpack_head;
323 while ((*pp)->next) pp = &(*pp)->next;
325 *pp = NULL;
326 c->hpack_size -= old->size;
327 cwist_free(old->name);
328 cwist_free(old->value);
330 }
331}
332
341static int grpc_client_hpack_insert(cwist_grpc_client *c, const char *name, const char *value) {
342 size_t size = strlen(name) + strlen(value) + 32;
343 if (size > c->hpack_cap) {
344 grpc_client_hpack_evict_to(c, 0);
345 return 0;
346 }
348 if (!e) return -1;
349 e->name = cwist_alloc(strlen(name) + 1);
350 e->value = cwist_alloc(strlen(value) + 1);
351 if (!e->name || !e->value) {
352 cwist_free(e->name);
353 cwist_free(e->value);
354 cwist_free(e);
355 return -1;
356 }
357 strcpy(e->name, name);
358 strcpy(e->value, value);
359 e->size = size;
360 grpc_client_hpack_evict_to(c, c->hpack_cap - size);
361 e->next = c->hpack_head;
362 c->hpack_head = e;
363 c->hpack_size += size;
364 return 0;
365}
366
377static int grpc_client_hpack_get(cwist_grpc_client *c, uint32_t index, const char **name,
378 const char **value) {
380 if (st) {
381 *name = st->name;
382 *value = st->value;
383 return 0;
384 }
385 uint32_t pos = index - 61; /* 1-based into the dynamic table */
386 const grpc_client_hpack_entry *e = c->hpack_head;
387 while (e && --pos) e = e->next;
388 if (!e) return -1;
389 *name = e->name;
390 *value = e->value;
391 return 0;
392}
393
394typedef void (*grpc_client_header_cb)(void *ctx, const char *name, const char *value);
395
407static int grpc_client_hpack_decode(cwist_grpc_client *c, const uint8_t *buf, size_t len,
408 grpc_client_header_cb cb, void *ctx) {
409 size_t pos = 0;
410 while (pos < len) {
411 uint8_t first = buf[pos];
412 if (first & 0x80) { /* indexed */
413 uint32_t index;
414 if (h2_decode_integer(buf, len, &pos, 7, &index) != 0) return -1;
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) { /* 0x40: literal, incremental indexing */
420 if (h2_decode_integer(buf, len, &pos, 6, &name_index) != 0) return -1;
421 char *owned_name = NULL;
422 const char *name;
423 if (name_index) {
424 const char *unused;
425 if (grpc_client_hpack_get(c, name_index, &name, &unused) != 0) return -1;
426 } else {
427 owned_name = h2_decode_string(buf, len, &pos);
428 if (!owned_name) return -1;
429 name = owned_name;
430 }
431 char *value = h2_decode_string(buf, len, &pos);
432 if (!value) {
434 return -1;
435 }
436 if (grpc_client_hpack_insert(c, name, value) != 0) {
438 cwist_free(value);
439 return -1;
440 }
441 cb(ctx, name, value);
443 cwist_free(value);
444 } else if (first & 0x20) { /* dynamic table size update */
446 if (h2_decode_integer(buf, len, &pos, 5, &new_size) != 0) return -1;
447 if (new_size > GRPC_CLIENT_HEADER_TABLE) return -1;
448 c->hpack_cap = new_size;
449 grpc_client_hpack_evict_to(c, new_size);
450 } else { /* literal without indexing / never indexed */
452 if (h2_decode_integer(buf, len, &pos, 4, &name_index) != 0) return -1;
453 char *owned_name = NULL;
454 const char *name;
455 if (name_index) {
456 const char *unused;
457 if (grpc_client_hpack_get(c, name_index, &name, &unused) != 0) return -1;
458 } else {
459 owned_name = h2_decode_string(buf, len, &pos);
460 if (!owned_name) return -1;
461 name = owned_name;
462 }
463 char *value = h2_decode_string(buf, len, &pos);
464 if (!value) {
466 return -1;
467 }
468 cb(ctx, name, value);
470 cwist_free(value);
471 }
472 }
473 return 0;
474}
475
476/* --- request header block encoding (literal, never-indexed) --- */
477
487static size_t grpc_client_enc_literal(uint8_t *dst, size_t cap, uint32_t name_index,
488 const char *name, const char *value) {
489 size_t pos = 0;
490 if (pos + 1 > cap) return 0;
491 dst[pos] = 0x00;
492 size_t n = h2_encode_integer(dst + pos, cap - pos, name_index, 4);
493 if (n == 0) return 0;
494 pos += n;
495 if (!name_index) {
496 n = h2_encode_string(dst + pos, cap - pos, name);
497 if (n == 0) return 0;
498 pos += n;
499 }
500 n = h2_encode_string(dst + pos, cap - pos, value);
501 if (n == 0) return 0;
502 return pos + n;
503}
504
505/* --- connection setup --- */
506
513static int grpc_client_tcp_connect(const char *host, uint16_t port) {
514 char port_str[8];
515 snprintf(port_str, sizeof(port_str), "%u", (unsigned)port);
516 struct addrinfo hints;
517 memset(&hints, 0, sizeof(hints));
518 hints.ai_family = AF_UNSPEC;
519 hints.ai_socktype = SOCK_STREAM;
520 struct addrinfo *res = NULL;
521 if (getaddrinfo(host, port_str, &hints, &res) != 0) return -1;
522 int fd = -1;
523 for (struct addrinfo *it = res; it; it = it->ai_next) {
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;
527 close(fd);
528 fd = -1;
529 }
530 freeaddrinfo(res);
531 return fd;
532}
533
543static int grpc_client_tls_setup(cwist_grpc_client *c, const char *host, int verify_peer,
544 uint64_t deadline_ms) {
545 c->ssl_ctx = SSL_CTX_new(TLS_client_method());
546 if (!c->ssl_ctx) return -1;
547 if (verify_peer) {
550 } else {
552 }
553 static const uint8_t alpn[] = {2, 'h', '2'};
554 if (SSL_CTX_set_alpn_protos(c->ssl_ctx, alpn, sizeof(alpn)) != 0) return -1;
555 c->ssl = SSL_new(c->ssl_ctx);
556 if (!c->ssl) return -1;
557 SSL_set_fd(c->ssl, c->fd);
558 SSL_set_tlsext_host_name(c->ssl, host);
559 for (;;) {
560 int rc = SSL_connect(c->ssl);
561 if (rc == 1) break;
562 int err = SSL_get_error(c->ssl, rc);
564 if (grpc_client_wait(c, err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN, deadline_ms) !=
565 0)
566 return -1;
567 continue;
568 }
569 return -1;
570 }
571 const uint8_t *sel = NULL;
572 unsigned sel_len = 0;
574 if (sel_len != 2 || memcmp(sel, "h2", 2) != 0) return -1;
575 return 0;
576}
577
586static int grpc_client_apply_settings(cwist_grpc_client *c, const uint8_t *payload, uint32_t len,
587 uint64_t deadline_ms) {
588 if (len % 6 != 0) return -1;
589 for (uint32_t off = 0; off + 6 <= len; off += 6) {
590 uint16_t id = (uint16_t)((payload[off] << 8) | payload[off + 1]);
591 uint32_t value = ((uint32_t)payload[off + 2] << 24) | ((uint32_t)payload[off + 3] << 16) |
592 ((uint32_t)payload[off + 4] << 8) | payload[off + 5];
593 switch (id) {
594 case 0x2: /* ENABLE_PUSH: must be 0 for clients */
595 if (value != 0) return -1;
596 break;
597 case 0x4: /* INITIAL_WINDOW_SIZE */
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;
601 break;
602 case 0x5: /* MAX_FRAME_SIZE */
603 if (value < 16384 || value > 16777215) return -1;
604 c->peer_max_frame = value;
605 break;
606 default: break;
607 }
608 }
609 return grpc_client_write_frame(c, H2_FRAME_SETTINGS, H2_FLAG_ACK, 0, NULL, 0, deadline_ms);
610}
611
625 const cwist_grpc_client_options *options) {
626 if (!host || !port) return NULL;
627 cwist_grpc_client *c = cwist_alloc(sizeof(*c));
628 if (!c) return NULL;
629 memset(c, 0, sizeof(*c));
630 c->fd = -1;
632 c->next_stream_id = 1;
633 c->peer_max_frame = 16384;
634 c->peer_initial_window = GRPC_CLIENT_DEFAULT_WINDOW;
635 c->conn_send_window = GRPC_CLIENT_DEFAULT_WINDOW;
636 c->hpack_cap = GRPC_CLIENT_HEADER_TABLE;
637
639 options && options->connect_timeout_ms ? options->connect_timeout_ms : 10000;
640 uint64_t deadline = grpc_client_now_ms() + connect_ms;
641
642 if (options && options->authority) {
643 c->authority = cwist_alloc(strlen(options->authority) + 1);
644 if (c->authority) strcpy(c->authority, options->authority);
645 } else {
646 char buf[300];
647 snprintf(buf, sizeof(buf), "%s:%u", host, (unsigned)port);
648 c->authority = cwist_alloc(strlen(buf) + 1);
649 if (c->authority) strcpy(c->authority, buf);
650 }
651 if (!c->authority) goto fail;
652
653 c->fd = grpc_client_tcp_connect(host, port);
654 if (c->fd < 0) goto fail;
655 /* Non-blocking so poll() can enforce call deadlines. */
656 int fl = fcntl(c->fd, F_GETFL, 0);
657 if (fl < 0 || fcntl(c->fd, F_SETFL, fl | O_NONBLOCK) != 0) goto fail;
658
659 if (options && options->use_tls) {
660 const char *sni = options->tls_server_name ? options->tls_server_name : host;
661 if (grpc_client_tls_setup(c, sni, options->verify_peer, deadline) != 0) goto fail;
662 }
663
664 static const char preface[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
665 if (grpc_client_write_all(c, preface, sizeof(preface) - 1, deadline) != 0) goto fail;
666 if (grpc_client_write_frame(c, H2_FRAME_SETTINGS, 0, 0, NULL, 0, deadline) != 0) goto fail;
667
668 /* The server's first frame must be SETTINGS; answer it, then keep
669 * reading until our own SETTINGS ACK lands. */
670 int server_settings = 0, our_ack = 0;
671 while (!server_settings || !our_ack) {
673 if (grpc_client_read_frame(c, &f, deadline) != 0) goto fail;
674 int rc = 0;
675 if (f.type == H2_FRAME_SETTINGS && !(f.flags & H2_FLAG_ACK)) {
676 server_settings = 1;
677 rc = grpc_client_apply_settings(c, f.payload, f.len, deadline);
678 } else if (f.type == H2_FRAME_SETTINGS) {
679 our_ack = 1;
680 } else if (f.type == H2_FRAME_PING && !(f.flags & H2_FLAG_ACK) && f.len == 8) {
681 rc = grpc_client_write_frame(c, H2_FRAME_PING, H2_FLAG_ACK, 0, f.payload, 8, deadline);
682 } else if (f.type == H2_FRAME_GOAWAY) {
683 cwist_free(f.payload);
684 goto fail;
685 }
686 /* WINDOW_UPDATE and friends before the first call need no action. */
687 cwist_free(f.payload);
688 if (rc != 0) goto fail;
689 }
690 return c;
691
692fail:
694 return NULL;
695}
696
701void cwist_grpc_client_close(cwist_grpc_client *c) {
702 if (!c) return;
703 if (c->active) cwist_grpc_call_destroy(c->active);
704 if (c->fd >= 0 && !c->dead)
705 grpc_client_write_frame(c, H2_FRAME_GOAWAY, 0, 0, NULL, 0, grpc_client_now_ms() + 500);
706 if (c->ssl) {
707 SSL_shutdown(c->ssl);
708 SSL_free(c->ssl);
709 }
710 if (c->ssl_ctx) SSL_CTX_free(c->ssl_ctx);
711 if (c->fd >= 0) close(c->fd);
712 grpc_client_hpack_evict_to(c, 0);
714 cwist_free(c->authority);
715 cwist_free(c);
716}
717
718/* --- call plumbing --- */
719
727static int grpc_client_queue_msg(void *ctx, const cwist_grpc_message *message) {
728 cwist_grpc_call *call = ctx;
729 grpc_client_msg *node = cwist_alloc(sizeof(*node));
730 if (!node) return -1;
731 node->data = cwist_alloc(message->len ? message->len : 1);
732 if (!node->data) {
733 cwist_free(node);
734 return -1;
735 }
736 if (message->len) memcpy(node->data, message->data, message->len);
737 node->len = message->len;
738 node->next = NULL;
739 if (call->msg_tail)
740 call->msg_tail->next = node;
741 else
742 call->msg_head = node;
743 call->msg_tail = node;
744 return 0;
745}
746
747typedef struct grpc_client_headers_ctx {
748 cwist_grpc_call *call;
749 int http_status;
751
763static void grpc_client_on_header(void *ctx, const char *name, const char *value) {
764 grpc_client_headers_ctx *hc = ctx;
765 if (strcmp(name, ":status") == 0) {
766 /* HTTP status is a 3-digit decimal; reject anything that doesn't
767 * parse cleanly rather than silently treating garbage as 0. */
768 char *end = NULL;
769 long v = strtol(value, &end, 10);
770 hc->http_status = (end != value && *end == '\0' && v >= 100 && v <= 599) ? (int)v : 0;
771 } else if (strcmp(name, "grpc-status") == 0) {
772 /* gRPC status codes are small non-negative integers (0–16 today).
773 * Use strtol so a malformed trailer produces status 2 (UNKNOWN)
774 * instead of silently returning 0 (OK). */
775 char *end = NULL;
776 long v = strtol(value, &end, 10);
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) {
782 cwist_free(hc->call->status_message);
783 hc->call->status_message = cwist_alloc(strlen(value) + 1);
784 if (hc->call->status_message) strcpy(hc->call->status_message, value);
785 } else if (strcmp(name, "grpc-retry-pushback-ms") == 0) {
786 /* gRFC A6 pushback: ASCII signed 32-bit; negative or unparseable
787 * means the server asks the client not to retry at all. */
788 char *end = NULL;
789 long v = strtol(value, &end, 10);
790 if (end == value || *end != '\0' || v < INT32_MIN || v > INT32_MAX) v = -1;
791 hc->call->pushback_ms = (int32_t)v;
792 hc->call->pushback_seen = 1;
793 }
794}
795
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,
812 int *end_stream, uint64_t deadline_ms) {
813 size_t cap = first->len ? first->len : 1;
815 if (!block) return -1;
816 size_t len = first->len;
817 if (len) memcpy(block, first->payload, len);
818 *end_stream = !!(first->flags & H2_FLAG_END_STREAM);
819 while (!(first->flags & H2_FLAG_END_HEADERS)) {
821 if (grpc_client_read_frame(c, &cont, deadline_ms) != 0 ||
822 cont.type != H2_FRAME_CONTINUATION || cont.stream_id != call->stream_id) {
824 return -1;
825 }
826 uint8_t *nb = cwist_realloc(block, len + cont.len);
827 if (!nb) {
828 cwist_free(cont.payload);
830 return -1;
831 }
832 block = nb;
833 if (cont.len) memcpy(block + len, cont.payload, cont.len);
834 len += cont.len;
835 first->flags = cont.flags; /* CONTINUATION carries END_HEADERS */
836 cwist_free(cont.payload);
837 }
838 *out = block;
839 *out_len = len;
840 return 0;
841}
842
856static int grpc_client_pump(cwist_grpc_call *call) {
857 cwist_grpc_client *c = call->client;
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;
862 c->dead = 1;
863 goto transport_fail;
864 }
865
866 int rc = 0;
867 switch (f.type) {
869 if (!(f.flags & H2_FLAG_ACK))
870 rc = grpc_client_apply_settings(c, f.payload, f.len, call->deadline_ms);
871 break;
872 case H2_FRAME_PING:
873 if (!(f.flags & H2_FLAG_ACK) && f.len == 8)
874 rc = grpc_client_write_frame(c, H2_FRAME_PING, H2_FLAG_ACK, 0, f.payload, 8,
875 call->deadline_ms);
876 break;
878 if (f.len != 4) {
879 rc = -1;
880 break;
881 }
882 int32_t inc =
883 (int32_t)((((uint32_t)f.payload[0] & 0x7f) << 24) | ((uint32_t)f.payload[1] << 16) |
884 ((uint32_t)f.payload[2] << 8) | f.payload[3]);
885 if (inc <= 0) {
886 rc = -1;
887 break;
888 }
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;
893 break;
894 }
895 case H2_FRAME_GOAWAY:
896 c->goaway = 1;
897 if (!call->trailers) {
898 /* gRFC A6 case 3: streams above the GOAWAY last-stream-id
899 * were never seen by server application logic. */
900 if (f.len >= 4 && !call->headers_received) {
901 uint32_t last = (((uint32_t)f.payload[0] & 0x7f) << 24) |
902 ((uint32_t)f.payload[1] << 16) | ((uint32_t)f.payload[2] << 8) |
903 f.payload[3];
904 if (last < call->stream_id) call->goaway_refused = 1;
905 }
906 call->failed = 1;
907 call->stream_ended = 1;
908 call->status = CWIST_GRPC_UNAVAILABLE;
909 cwist_free(call->status_message);
910 call->status_message = NULL;
911 rc = 1;
912 }
913 break;
915 if (f.stream_id == call->stream_id) {
916 call->stream_ended = 1;
917 if (!call->trailers) {
918 /* REFUSED_STREAM (0x7) before Response-Headers: the RPC
919 * never reached server application logic (gRFC A6 case 3). */
920 if (f.len == 4 && !call->headers_received) {
921 uint32_t code = ((uint32_t)f.payload[0] << 24) |
922 ((uint32_t)f.payload[1] << 16) |
923 ((uint32_t)f.payload[2] << 8) | f.payload[3];
924 if (code == 0x7) call->refused_stream = 1;
925 }
926 call->failed = 1;
927 call->status = CWIST_GRPC_UNAVAILABLE;
928 }
929 rc = 1;
930 }
931 break;
932 case H2_FRAME_HEADERS: {
933 if (f.stream_id != call->stream_id) break;
934 uint8_t *block = NULL;
935 size_t block_len = 0;
936 int end_stream = 0;
937 if (grpc_client_read_header_block(c, call, &f, &block, &block_len, &end_stream,
938 call->deadline_ms) != 0) {
939 rc = -1;
940 break;
941 }
942 grpc_client_headers_ctx hc = {call, 0};
943 if (grpc_client_hpack_decode(c, block, block_len, grpc_client_on_header, &hc) != 0) {
945 rc = -1;
946 break;
947 }
949 if (hc.http_status && hc.http_status != 200) {
950 call->failed = 1;
951 call->stream_ended = 1;
952 call->status = CWIST_GRPC_UNAVAILABLE;
953 cwist_free(call->status_message);
954 call->status_message = cwist_alloc(32);
955 if (call->status_message)
956 snprintf(call->status_message, 32, "HTTP status %d", hc.http_status);
957 rc = 1;
958 break;
959 }
960 if (end_stream) {
961 /* Trailer HEADERS close the stream; a missing grpc-status
962 * is a protocol error surfaced as UNKNOWN (gRPC spec).
963 * When no Response-Headers preceded them this is a
964 * Trailers-Only response, which stays retryable (gRFC A6). */
965 if (!call->headers_received) call->trailers_only = 1;
966 call->trailers = 1;
967 call->stream_ended = 1;
968 if (!call->status_seen) call->status = CWIST_GRPC_UNKNOWN;
969 rc = 1;
970 } else {
971 /* Response-Headers commit the RPC (gRFC A6). */
972 call->headers_received = 1;
973 }
974 break;
975 }
976 case H2_FRAME_DATA: {
977 if (f.stream_id != call->stream_id) break;
978 if (f.len) {
979 if (cwist_grpc_decoder_feed(&call->decoder, f.payload, f.len, grpc_client_queue_msg,
980 call) != 0) {
981 call->failed = 1;
982 call->stream_ended = 1;
983 call->status = CWIST_GRPC_INTERNAL;
984 cwist_free(call->status_message);
985 call->status_message = cwist_alloc(32);
986 if (call->status_message)
987 strcpy(call->status_message, "malformed gRPC message frame");
988 rc = 1;
989 break;
990 }
991 /* Return flow-control credit for what we consumed. */
992 uint8_t upd[4];
993 upd[0] = (uint8_t)((f.len >> 24) & 0x7f);
994 upd[1] = (uint8_t)(f.len >> 16);
995 upd[2] = (uint8_t)(f.len >> 8);
996 upd[3] = (uint8_t)f.len;
997 if (grpc_client_write_frame(c, H2_FRAME_WINDOW_UPDATE, 0, 0, upd, 4,
998 call->deadline_ms) != 0 ||
999 grpc_client_write_frame(c, H2_FRAME_WINDOW_UPDATE, 0, call->stream_id, upd, 4,
1000 call->deadline_ms) != 0) {
1001 rc = -1;
1002 break;
1003 }
1004 }
1005 if (f.flags & H2_FLAG_END_STREAM) {
1006 /* END_STREAM on DATA without trailers: tolerate, status stays
1007 * UNKNOWN unless trailers arrive... they cannot; mark done. */
1008 call->stream_ended = 1;
1009 if (!call->trailers) {
1010 call->trailers = 1;
1011 call->status = CWIST_GRPC_UNKNOWN;
1012 }
1013 rc = 1;
1014 }
1015 break;
1016 }
1017 default: break; /* PUSH_PROMISE, PRIORITY, unknown: ignore */
1018 }
1019 cwist_free(f.payload);
1020 return rc;
1021
1022deadline:
1023 call->failed = 1;
1024 call->stream_ended = 1;
1025 call->status = CWIST_GRPC_DEADLINE_EXCEEDED;
1026 cwist_free(call->status_message);
1027 call->status_message = cwist_alloc(32);
1028 if (call->status_message) strcpy(call->status_message, "deadline exceeded");
1029 grpc_client_write_frame(c, H2_FRAME_RST_STREAM, 0, call->stream_id, (uint8_t[]){0, 0, 0, 0x08},
1030 4, grpc_client_now_ms() + 500);
1031 return -1;
1032
1034 call->failed = 1;
1035 call->stream_ended = 1;
1036 call->status = CWIST_GRPC_UNAVAILABLE;
1037 cwist_free(call->status_message);
1038 call->status_message = cwist_alloc(32);
1039 if (call->status_message) strcpy(call->status_message, "transport error");
1040 return -1;
1041}
1042
1056static int grpc_client_send_request(cwist_grpc_call *call, const uint8_t *frame, size_t frame_len) {
1057 cwist_grpc_client *c = call->client;
1058 size_t sent = 0;
1059 for (;;) {
1060 int64_t window =
1061 c->conn_send_window < call->send_window ? c->conn_send_window : call->send_window;
1062 size_t chunk = frame_len - sent;
1063 if ((int64_t)chunk > window) chunk = window > 0 ? (size_t)window : 0;
1064 if (chunk > c->peer_max_frame) chunk = c->peer_max_frame;
1065 if (chunk == 0) {
1066 if (frame_len == sent) break;
1067 int rc = grpc_client_pump(call);
1068 if (rc != 0) return -1;
1069 continue;
1070 }
1071 uint8_t flags = (sent + chunk == frame_len) ? H2_FLAG_END_STREAM : 0;
1072 if (grpc_client_write_frame(c, H2_FRAME_DATA, flags, call->stream_id, frame + sent,
1073 (uint32_t)chunk, call->deadline_ms) != 0)
1074 return -1;
1075 c->conn_send_window -= (int64_t)chunk;
1076 call->send_window -= (int64_t)chunk;
1077 sent += chunk;
1078 if (sent == frame_len) break;
1079 }
1080 return 0;
1081}
1082
1094cwist_grpc_call *cwist_grpc_call_start(cwist_grpc_client *c, const char *method,
1095 const void *request, size_t request_len,
1096 uint64_t timeout_ms) {
1097 return cwist_grpc_call_start_ex(c, method, request, request_len, timeout_ms, 0);
1098}
1099
1112cwist_grpc_call *cwist_grpc_call_start_ex(cwist_grpc_client *c, const char *method,
1113 const void *request, size_t request_len,
1114 uint64_t timeout_ms, uint32_t previous_attempts) {
1115 if (!c || !method || (request_len && !request) || c->dead || c->goaway) return NULL;
1116
1117 pthread_mutex_lock(&c->mu);
1118 if (c->active) {
1119 pthread_mutex_unlock(&c->mu);
1120 return NULL;
1121 }
1122
1123 cwist_grpc_call *call = cwist_alloc(sizeof(*call));
1124 if (!call) {
1125 pthread_mutex_unlock(&c->mu);
1126 return NULL;
1127 }
1128 memset(call, 0, sizeof(*call));
1129 call->client = c;
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;
1134 call->status = CWIST_GRPC_OK;
1135 cwist_grpc_decoder_init(&call->decoder, 0);
1136
1137 /* HEADERS: :method POST, :scheme, :path, :authority, content-type, te. */
1138 uint8_t block[4096];
1139 size_t pos = 0;
1140 block[pos++] = 0x83; /* :method: POST */
1141 block[pos++] = c->ssl ? 0x87 : 0x86; /* :scheme: https / http */
1142 size_t n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 4, NULL, method);
1143 if (n == 0) goto fail;
1144 pos += n;
1145 n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 1, NULL, c->authority);
1146 if (n == 0) goto fail;
1147 pos += n;
1148 n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 31, NULL, "application/grpc");
1149 if (n == 0) goto fail;
1150 pos += n;
1151 n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 0, "te", "trailers");
1152 if (n == 0) goto fail;
1153 pos += n;
1154 if (timeout_ms) {
1155 char timeout_hdr[16];
1156 uint64_t v = timeout_ms > 99999999u ? 99999999u : timeout_ms;
1157 snprintf(timeout_hdr, sizeof(timeout_hdr), "%llum", (unsigned long long)v);
1158 n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 0, "grpc-timeout",
1159 timeout_hdr);
1160 if (n == 0) goto fail;
1161 pos += n;
1162 }
1163 if (previous_attempts) {
1164 /* gRFC A6: absent on the first RPC, 1 on the second, ... */
1165 char attempts_hdr[16];
1166 snprintf(attempts_hdr, sizeof(attempts_hdr), "%u", (unsigned)previous_attempts);
1167 n = grpc_client_enc_literal(block + pos, sizeof(block) - pos, 0,
1168 "grpc-previous-rpc-attempts", attempts_hdr);
1169 if (n == 0) goto fail;
1170 pos += n;
1171 }
1172 if (grpc_client_write_frame(c, H2_FRAME_HEADERS, H2_FLAG_END_HEADERS, call->stream_id, block,
1173 (uint32_t)pos, call->deadline_ms) != 0)
1174 goto fail;
1175
1176 uint8_t *frame = NULL;
1177 size_t frame_len = 0;
1179 int rc = grpc_client_send_request(call, frame, frame_len);
1181 if (rc != 0) goto fail;
1182
1183 c->active = call;
1184 pthread_mutex_unlock(&c->mu);
1185 return call;
1186
1187fail:
1188 cwist_grpc_decoder_destroy(&call->decoder);
1189 cwist_free(call);
1190 pthread_mutex_unlock(&c->mu);
1191 return NULL;
1192}
1193
1203int cwist_grpc_call_recv(cwist_grpc_call *call, cwist_grpc_message *out) {
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;
1208 }
1209 grpc_client_msg *node = call->msg_head;
1210 call->msg_head = node->next;
1211 if (!call->msg_head) call->msg_tail = NULL;
1212 cwist_free(call->recv_buf);
1213 call->recv_buf = node->data;
1214 out->compressed = 0;
1215 out->data = node->data;
1216 out->len = node->len;
1217 cwist_free(node);
1218 return 1;
1219}
1220
1228int cwist_grpc_call_await_headers(cwist_grpc_call *call) {
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)
1232 return -1;
1233 }
1234 return call->headers_received ? 0 : 1;
1235}
1236
1241int cwist_grpc_call_committed(const cwist_grpc_call *call) {
1242 return call && call->headers_received;
1243}
1244
1250int cwist_grpc_call_refused(const cwist_grpc_call *call) {
1251 if (!call || call->headers_received || call->trailers) return 0;
1252 return call->refused_stream || call->goaway_refused;
1253}
1254
1261int cwist_grpc_call_retry_pushback_ms(const cwist_grpc_call *call, int32_t *out_ms) {
1262 if (!call || !call->pushback_seen) return 0;
1263 if (out_ms) *out_ms = call->pushback_ms;
1264 return 1;
1265}
1266
1271int cwist_grpc_client_dead(cwist_grpc_client *c) {
1272 return !c || c->dead || c->goaway;
1273}
1274
1280cwist_grpc_status_t cwist_grpc_call_status(const cwist_grpc_call *call) {
1281 return call ? call->status : CWIST_GRPC_INTERNAL;
1282}
1283
1291cwist_grpc_status_t cwist_grpc_call_finish(cwist_grpc_call *call, const char **message) {
1292 if (!call) {
1293 if (message) *message = NULL;
1294 return CWIST_GRPC_INTERNAL;
1295 }
1296 while (!call->stream_ended) {
1297 if (grpc_client_pump(call) != 0 && !call->stream_ended) break;
1298 }
1299 if (message) *message = call->status_message;
1300 return call->status;
1301}
1302
1308void cwist_grpc_call_cancel(cwist_grpc_call *call) {
1309 if (!call || call->stream_ended) return;
1310 cwist_grpc_client *c = call->client;
1311 grpc_client_write_frame(c, H2_FRAME_RST_STREAM, 0, call->stream_id, (uint8_t[]){0, 0, 0, 0x08},
1312 4, grpc_client_now_ms() + 500);
1313 call->stream_ended = 1;
1314 call->failed = 1;
1315 call->status = CWIST_GRPC_CANCELLED;
1316}
1317
1323void cwist_grpc_call_destroy(cwist_grpc_call *call) {
1324 if (!call) return;
1325 cwist_grpc_client *c = call->client;
1326 if (!call->stream_ended) cwist_grpc_call_cancel(call);
1327 pthread_mutex_lock(&c->mu);
1328 if (c->active == call) c->active = NULL;
1329 pthread_mutex_unlock(&c->mu);
1330 cwist_grpc_decoder_destroy(&call->decoder);
1331 while (call->msg_head) {
1332 grpc_client_msg *next = call->msg_head->next;
1333 cwist_free(call->msg_head->data);
1334 cwist_free(call->msg_head);
1335 call->msg_head = next;
1336 }
1337 cwist_free(call->recv_buf);
1338 cwist_free(call->status_message);
1339 cwist_free(call);
1340}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
Definition alloc.c:27
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
@ CWIST_GRPC_OK
Definition grpc.h:22
@ CWIST_GRPC_CANCELLED
Definition grpc.h:23
@ CWIST_GRPC_UNAVAILABLE
Definition grpc.h:36
@ CWIST_GRPC_DEADLINE_EXCEEDED
Definition grpc.h:26
@ CWIST_GRPC_UNKNOWN
Definition grpc.h:24
@ CWIST_GRPC_INTERNAL
Definition grpc.h:35
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.
Definition grpc.c:734
void cwist_grpc_decoder_init(cwist_grpc_decoder *decoder, size_t max_message_size)
Initialize an incremental gRPC message decoder.
Definition grpc.c:701
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.
Definition grpc.c:713
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.
Definition grpc.c:677
struct grpc_client_hpack_entry grpc_client_hpack_entry
#define H2_FRAME_RST_STREAM
Definition grpc_client.c:40
#define H2_FLAG_ACK
Definition grpc_client.c:49
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
Definition grpc_client.c:34
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
Definition grpc_client.c:36
#define H2_FLAG_END_STREAM
Definition grpc_client.c:47
int cwist_grpc_call_await_headers(cwist_grpc_call *call)
Block until response headers arrive or the stream ends.
#define H2_FRAME_GOAWAY
Definition grpc_client.c:44
#define H2_FRAME_HEADERS
Definition grpc_client.c:39
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
Definition grpc_client.c:33
struct grpc_client_frame grpc_client_frame
#define H2_FLAG_END_HEADERS
Definition grpc_client.c:48
#define H2_FRAME_WINDOW_UPDATE
Definition grpc_client.c:45
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
Definition grpc_client.c:41
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
Definition grpc_client.c:46
#define H2_FRAME_PING
Definition grpc_client.c:43
#define H2_FRAME_DATA
Definition grpc_client.c:38
#define GRPC_CLIENT_DEFAULT_WINDOW
Definition grpc_client.c:35
HTTP/2-based gRPC client (h2c cleartext, or TLS with ALPN "h2").
struct cwist_grpc_client cwist_grpc_client
Definition grpc_client.h:19
struct cwist_grpc_call cwist_grpc_call
Definition grpc_client.h:20
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.
Definition http2.c:1267
const cwist_http2_static_header * h2_static_header(uint32_t index)
Resolve an HPACK static table index (1-based); NULL when out of range.
Definition http2.c:1570
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.
Definition http2.c:1903
char * h2_decode_string(const unsigned char *buf, size_t len, size_t *pos)
Decode an HPACK string (literal or huffman).
Definition http2.c:1543
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.
Definition http2.c:1875
size_t cap
Definition otel.c:56
mbedTLS TLS backend: OpenSSL-shaped seam over the CWIST TLS subset.
#define SSL_new
Definition ssl.h:166
#define SSL_VERIFY_NONE
Definition ssl.h:80
#define SSL_shutdown
Definition ssl.h:174
struct cwist_mtls_ssl SSL
Definition ssl.h:44
#define SSL_get0_alpn_selected
Definition ssl.h:180
#define SSL_set_tlsext_host_name
Definition ssl.h:181
#define SSL_ERROR_WANT_READ
Definition ssl.h:54
#define SSL_CTX_new
Definition ssl.h:98
#define SSL_free
Definition ssl.h:167
#define SSL_VERIFY_PEER
Definition ssl.h:81
#define SSL_write
Definition ssl.h:172
#define SSL_CTX_free
Definition ssl.h:99
#define SSL_set_fd
Definition ssl.h:168
#define SSL_read
Definition ssl.h:171
#define SSL_CTX_set_default_verify_paths
Definition ssl.h:125
#define SSL_CTX_set_alpn_protos
Definition ssl.h:119
#define SSL_CTX_set_verify
Definition ssl.h:124
#define TLS_client_method
Definition ssl.h:97
#define SSL_ERROR_ZERO_RETURN
Definition ssl.h:57
struct cwist_mtls_ctx SSL_CTX
Definition ssl.h:43
#define SSL_get_error
Definition ssl.h:175
#define SSL_connect
Definition ssl.h:170
#define SSL_ERROR_WANT_WRITE
Definition ssl.h:55
const char * tls_server_name
Definition grpc_client.h:27
const uint8_t * data
Definition grpc.h:43
uint8_t compressed
Definition grpc.h:42
HPACK static table entry (RFC 7541 Appendix A).
Definition http2.h:154