cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
grpc.c
Go to the documentation of this file.
1
6#define _POSIX_C_SOURCE 200809L
8#include <cwist/sys/app/app.h>
10#include <ctype.h>
11#include <pthread.h>
12#include <stdatomic.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <strings.h>
17#include <time.h>
18#include <zlib.h>
19
20typedef struct cwist_grpc_route {
21 char *path;
22 int streaming;
23 int builtin;
25 cwist_grpc_stream_handler_func stream_handler;
26 void *user_ctx;
27 struct cwist_grpc_route *next;
29
30typedef struct cwist_grpc_health_state {
31 char *service;
32 int serving;
33 struct cwist_grpc_health_state *next;
35
36/* Shared health registry: status list plus the notification machinery that
37 * long-lived Watch streams wait on. */
38typedef struct cwist_grpc_health_registry {
40 pthread_cond_t cond; /* signalled when generation advances */
42 uint64_t generation; /* bumped on every status change */
43 atomic_int watchers; /* active streaming Watch calls */
45
46static int grpc_register_route(cwist_app *app, const char *service, const char *method,
48 cwist_grpc_stream_handler_func stream_handler, void *user_ctx);
49
50/* --- Incremental HTTP/2 streaming sessions --- */
51
52typedef struct grpc_qnode {
53 uint8_t *data;
54 size_t len;
55 struct grpc_qnode *next;
57
59
60struct cwist_grpc_session {
61 pthread_mutex_t mu; /* inbound queue, eof, cancelled */
62 pthread_cond_t cond;
63 pthread_mutex_t wmu; /* outbound writes and h2s lifetime */
64 cwist_h2_stream *h2s; /* NULL once the transport detaches */
65 void *conn_ctx; /* cwist_grpc_h2_conn_ctx */
66 cwist_grpc_decoder decoder;
67 int encoding; /* 0 identity, 1 gzip */
68 int resp_encoding; /* 0 identity, 1 gzip */
69 grpc_qnode *qhead;
70 grpc_qnode *qtail;
71 int eof;
72 int cancelled;
73 int trailers_sent;
74 int headers_sent; /* response HEADERS are delayed until first DATA/trailers (gRFC A6) */
75 int decode_failed;
76 cwist_grpc_stream stream; /* public stream object (embeds req) */
77 uint8_t *recv_buf; /* backing store for the last recv message */
79 void *user_ctx;
80 atomic_int refs; /* transport side + handler thread */
81 struct cwist_grpc_session *next;
82};
83
84static void grpc_session_send_trailers(cwist_grpc_session *session, cwist_grpc_status_t status,
85 const char *message);
86
92static int grpc_content_type_is_grpc(const char *content_type) {
93 if (!content_type) return 0;
94 return strncmp(content_type, "application/grpc", 16) == 0;
95}
96
102static cwist_grpc_route *grpc_find_route(cwist_app *app, const char *path) {
103 if (!app || !path) return NULL;
105 while (route) {
106 if (route->path && strcmp(route->path, path) == 0) return route;
107 route = route->next;
108 }
109 return NULL;
110}
111
115static uint64_t grpc_now_ms(void) {
116 struct timespec ts;
118 return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
119}
120
126static const char *grpc_header_get(cwist_http_request *req, const char *key) {
127 if (!req || !key) return NULL;
128 for (cwist_http_header_node *h = req->headers; h; h = h->next) {
129 if (h->key && h->key->data && h->value && h->value->data &&
130 strcasecmp(h->key->data, key) == 0)
131 return h->value->data;
132 }
133 return NULL;
134}
135
146int cwist_grpc_parse_timeout(const char *value, uint64_t *out_ms) {
147 if (!value || !out_ms) return -1;
148 size_t len = strlen(value);
149 if (len < 2 || len > 9) return -1; /* up to 8 digits + unit */
150 char unit = value[len - 1];
152 switch (unit) {
153 case 'H': to_ms_num = 3600000; break;
154 case 'M': to_ms_num = 60000; break;
155 case 'S': to_ms_num = 1000; break;
156 case 'm': to_ms_num = 1; break;
157 case 'u':
158 to_ms_num = 1;
159 to_ms_den = 1000;
160 break;
161 case 'n':
162 to_ms_num = 1;
163 to_ms_den = 1000000;
164 break;
165 default: return -1;
166 }
167 uint64_t v = 0;
168 for (size_t i = 0; i + 1 < len; i++) {
169 if (!isdigit((unsigned char)value[i])) return -1;
170 v = v * 10 + (uint64_t)(value[i] - '0');
171 }
172 /* Round sub-millisecond units up so a positive timeout never becomes 0. */
174 if (v > 0 && ms == 0) ms = 1;
175 *out_ms = ms;
176 return 0;
177}
178
184static int grpc_request_encoding(cwist_http_request *req) {
185 const char *enc = grpc_header_get(req, "grpc-encoding");
186 if (!enc || strcmp(enc, "identity") == 0) return 0;
187 if (strcmp(enc, "gzip") == 0) return 1;
188 return -1;
189}
190
198static int grpc_client_accepts_gzip(cwist_http_request *req) {
199 const char *ae = grpc_header_get(req, "grpc-accept-encoding");
200 if (!ae) return 0;
201 while (*ae) {
202 while (*ae == ' ' || *ae == '\t' || *ae == ',') ae++;
203 if (!*ae) break;
204 const char *start = ae;
205 while (*ae && *ae != ',' && *ae != ' ' && *ae != '\t') ae++;
206 size_t len = (size_t)(ae - start);
207 if (len == 4 && strncasecmp(start, "gzip", 4) == 0) return 1;
208 }
209 return 0;
210}
211
220static int grpc_gzip_deflate(const uint8_t *in, size_t in_len, uint8_t **out, size_t *out_len) {
221 z_stream zs;
222 memset(&zs, 0, sizeof(zs));
225 return -1;
226 size_t cap = deflateBound(&zs, (uLong)in_len) + 32;
227 if (cap < 64) cap = 64;
228 uint8_t *buf = (uint8_t *)cwist_alloc(cap);
229 if (!buf) {
230 deflateEnd(&zs);
231 return -1;
232 }
233 zs.next_in = (Bytef *)in;
234 zs.avail_in = (uInt)in_len;
235 zs.next_out = buf;
236 zs.avail_out = (uInt)cap;
237 int zrc = deflate(&zs, Z_FINISH);
238 if (zrc != Z_STREAM_END) {
239 deflateEnd(&zs);
240 cwist_free(buf);
241 return -1;
242 }
243 *out_len = cap - zs.avail_out;
244 deflateEnd(&zs);
245 *out = buf;
246 return 0;
247}
248
259static int grpc_gzip_inflate(const uint8_t *in, size_t in_len, uint8_t **out, size_t *out_len) {
260 z_stream zs;
261 memset(&zs, 0, sizeof(zs));
262 if (inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK) return -1;
263 size_t cap = in_len * 3 + 1024;
264 if (cap < 8192) cap = 8192;
265 uint8_t *buf = (uint8_t *)cwist_alloc(cap);
266 if (!buf) {
267 inflateEnd(&zs);
268 return -1;
269 }
270 zs.next_in = (Bytef *)in;
271 zs.avail_in = (uInt)in_len;
272 size_t used = 0;
273 int zrc = Z_OK;
274 while (zrc == Z_OK) {
275 if (used == cap) {
276 cap *= 2;
277 uint8_t *nb = (uint8_t *)cwist_realloc(buf, cap);
278 if (!nb) {
279 cwist_free(buf);
280 inflateEnd(&zs);
281 return -1;
282 }
283 buf = nb;
284 }
285 zs.next_out = buf + used;
286 zs.avail_out = (uInt)(cap - used);
288 used = cap - zs.avail_out;
289 }
290 inflateEnd(&zs);
291 if (zrc != Z_STREAM_END) {
292 cwist_free(buf);
293 return -1;
294 }
295 *out = buf;
296 *out_len = used;
297 return 0;
298}
299
305static int grpc_message_inflate(cwist_http_request *req, cwist_grpc_message *message,
306 uint8_t **owned) {
307 *owned = NULL;
308 if (!message->compressed) return 0;
309 if (grpc_request_encoding(req) != 1) return -1;
310 uint8_t *plain = NULL;
311 size_t plain_len = 0;
312 if (grpc_gzip_inflate(message->data, message->len, &plain, &plain_len) != 0) return -1;
313 message->data = plain;
314 message->len = plain_len;
315 message->compressed = 0;
316 *owned = plain;
317 return 0;
318}
319
324static int grpc_b64_val(int c) {
325 if (c >= 'A' && c <= 'Z') return c - 'A';
326 if (c >= 'a' && c <= 'z') return c - 'a' + 26;
327 if (c >= '0' && c <= '9') return c - '0' + 52;
328 if (c == '+') return 62;
329 if (c == '/') return 63;
330 return -1;
331}
332
345static int grpc_base64_decode(const char *in, size_t in_len, uint8_t *out, size_t out_cap,
346 size_t *out_len) {
347 uint32_t acc = 0;
348 int bits = 0;
349 size_t used = 0;
350 for (size_t i = 0; i < in_len; i++) {
351 if (in[i] == '=') break;
352 int v = grpc_b64_val((unsigned char)in[i]);
353 if (v < 0) return -1;
354 acc = (acc << 6) | (uint32_t)v;
355 bits += 6;
356 if (bits >= 8) {
357 bits -= 8;
358 if (used >= out_cap) return -2;
359 out[used++] = (uint8_t)(acc >> bits);
360 }
361 }
362 *out_len = used;
363 return 0;
364}
365
371const char *cwist_grpc_metadata_get(cwist_http_request *req, const char *key) {
372 return grpc_header_get(req, key);
373}
374
386 size_t out_cap, size_t *out_len) {
387 if (!req || !key || !out_len) return -1;
388 size_t key_len = strlen(key);
389 if (key_len < 4 || strcmp(key + key_len - 4, "-bin") != 0) return -1;
390 const char *value = grpc_header_get(req, key);
391 if (!value) return -1;
392 return grpc_base64_decode(value, strlen(value), out, out_cap, out_len);
393}
394
405static void grpc_dispatch_unary(cwist_http_request *req, cwist_http_response *res) {
406 if (!req || !res || !req->app || !req->path || !req->path->data) return;
407
408 cwist_grpc_route *route = grpc_find_route(req->app, req->path->data);
409 if (!route || !route->handler) {
410 cwist_grpc_set_error(res, CWIST_GRPC_UNIMPLEMENTED, "gRPC method not registered");
411 return;
412 }
413
414 const char *ct = cwist_http_header_get(req->headers, "content-type");
415 if (!grpc_content_type_is_grpc(ct)) {
416 ct = cwist_http_header_get(req->headers, "Content-Type");
417 }
418 if (!grpc_content_type_is_grpc(ct)) {
420 "content-type must be application/grpc");
421 return;
422 }
423
424 if (!req->body || !req->body->data) {
425 cwist_grpc_set_error(res, CWIST_GRPC_INVALID_ARGUMENT, "missing gRPC request body");
426 return;
427 }
428
429 cwist_grpc_message message;
430 if (cwist_grpc_decode_message(req->body->data, req->body->size, &message) != 0) {
431 cwist_grpc_set_error(res, CWIST_GRPC_INVALID_ARGUMENT, "malformed gRPC message frame");
432 return;
433 }
434 if (grpc_request_encoding(req) < 0) {
435 cwist_grpc_set_error(res, CWIST_GRPC_UNIMPLEMENTED, "unsupported grpc-encoding");
436 return;
437 }
439 if (grpc_message_inflate(req, &message, &inflated) != 0) {
440 cwist_grpc_set_error(res, CWIST_GRPC_UNIMPLEMENTED, "unsupported compressed gRPC message");
441 return;
442 }
443
444 route->handler(req, res, &message, route->user_ctx);
446
447 if (grpc_client_accepts_gzip(req) && res->body && res->body->size >= 5) {
449 if (cwist_grpc_decode_message(res->body->data, res->body->size, &resp_msg) == 0 &&
450 !resp_msg.compressed && resp_msg.len > 0) {
451 uint8_t *cbuf = NULL;
452 size_t clen = 0;
453 if (grpc_gzip_deflate(resp_msg.data, resp_msg.len, &cbuf, &clen) == 0) {
455 size_t new_frame_len = 0;
459 cwist_http_header_remove(&res->headers, "grpc-encoding");
460 cwist_http_header_add(&res->headers, "grpc-encoding", "gzip");
461 }
463 }
464 }
465 }
466}
467
475static int grpc_validate_request(cwist_http_request *req, cwist_http_response *res) {
476 const char *ct = cwist_http_header_get(req->headers, "content-type");
477 if (!grpc_content_type_is_grpc(ct)) {
478 ct = cwist_http_header_get(req->headers, "Content-Type");
479 }
480 if (!grpc_content_type_is_grpc(ct)) {
482 "content-type must be application/grpc");
483 return -1;
484 }
485 if (!req->body || !req->body->data) {
486 cwist_grpc_set_error(res, CWIST_GRPC_INVALID_ARGUMENT, "missing gRPC request body");
487 return -1;
488 }
489 if (grpc_request_encoding(req) < 0) {
490 cwist_grpc_set_error(res, CWIST_GRPC_UNIMPLEMENTED, "unsupported grpc-encoding");
491 return -1;
492 }
493 return 0;
494}
495
507static void grpc_dispatch_stream(cwist_http_request *req, cwist_http_response *res) {
508 if (!req || !res || !req->app || !req->path || !req->path->data) return;
509
510 cwist_grpc_route *route = grpc_find_route(req->app, req->path->data);
511 if (!route || !route->stream_handler) {
512 cwist_grpc_set_error(res, CWIST_GRPC_UNIMPLEMENTED, "gRPC stream method not registered");
513 return;
514 }
515 if (grpc_validate_request(req, res) != 0) return;
516
517 size_t offset = 0;
518 size_t cap = 4;
519 size_t count = 0;
520 cwist_grpc_message *messages = (cwist_grpc_message *)cwist_alloc(cap * sizeof(*messages));
521 if (!messages) {
523 "failed to allocate gRPC stream messages");
524 return;
525 }
526
527 while (offset < req->body->size) {
528 if (count == cap) {
529 cap *= 2;
530 cwist_grpc_message *next =
531 (cwist_grpc_message *)cwist_realloc(messages, cap * sizeof(*messages));
532 if (!next) {
533 cwist_free(messages);
535 "failed to grow gRPC stream messages");
536 return;
537 }
538 messages = next;
539 }
540 if (cwist_grpc_decode_next_message(req->body->data, req->body->size, &offset,
541 &messages[count]) != 0) {
542 cwist_free(messages);
543 cwist_grpc_set_error(res, CWIST_GRPC_INVALID_ARGUMENT, "malformed gRPC stream frame");
544 return;
545 }
546 count++;
547 }
548
549 /* Decompress any compressed-flag messages when grpc-encoding: gzip. */
551 int has_compressed = 0;
552 for (size_t i = 0; i < count; i++)
553 if (messages[i].compressed) {
554 has_compressed = 1;
555 break;
556 }
557 if (has_compressed) {
558 owned = (uint8_t **)cwist_alloc(count * sizeof(*owned));
559 if (!owned) {
560 cwist_free(messages);
562 "failed to allocate inflate state");
563 return;
564 }
565 memset(owned, 0, count * sizeof(*owned));
566 for (size_t i = 0; i < count; i++) {
567 if (grpc_message_inflate(req, &messages[i], &owned[i]) != 0) {
568 for (size_t j = 0; j < count; j++) cwist_free(owned[j]);
569 cwist_free(messages);
571 "unsupported compressed gRPC message");
572 return;
573 }
574 }
575 }
576
577 uint64_t deadline_ms = 0;
578 const char *timeout = grpc_header_get(req, "grpc-timeout");
579 if (timeout) {
580 uint64_t timeout_ms = 0;
581 if (cwist_grpc_parse_timeout(timeout, &timeout_ms) == 0)
582 deadline_ms = grpc_now_ms() + timeout_ms;
583 }
584
586 cwist_http_header_add(&res->headers, "content-type", "application/grpc");
587 cwist_http_header_add(&res->headers, "grpc-accept-encoding", "gzip, identity");
588 if (grpc_client_accepts_gzip(req)) {
589 cwist_http_header_add(&res->headers, "grpc-encoding", "gzip");
590 }
591 if (res->body) cwist_sstring_assign(res->body, "");
592
593 cwist_grpc_stream stream = {
594 .req = req,
595 .res = res,
596 .messages = messages,
597 .message_count = count,
598 .status = CWIST_GRPC_OK,
599 .status_message = NULL,
600 .closed = 0,
601 .session = NULL,
602 .cancelled = 0,
603 .deadline_ms = deadline_ms,
604 };
605 route->stream_handler(&stream, route->user_ctx);
606 cwist_grpc_stream_close(&stream, stream.status, stream.status_message);
607 if (owned) {
608 for (size_t i = 0; i < count; i++) cwist_free(owned[i]);
609 }
610 cwist_free(messages);
611}
612
623 if (!frame || !out || frame_len < 5) return -1;
624 const uint8_t *p = (const uint8_t *)frame;
625 uint32_t len =
626 ((uint32_t)p[1] << 24) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 8) | (uint32_t)p[4];
627 if ((size_t)len > frame_len - 5) return -1;
628 if ((size_t)len != frame_len - 5) return -1;
629
630 out->compressed = p[0];
631 out->data = p + 5;
632 out->len = (size_t)len;
633 return 0;
634}
635
649int cwist_grpc_decode_next_message(const void *frames, size_t frames_len, size_t *offset,
650 cwist_grpc_message *out) {
651 if (!frames || !offset || !out || *offset > frames_len) return -1;
652 size_t pos = *offset;
653 if (frames_len - pos < 5) return -1;
654
655 const uint8_t *p = (const uint8_t *)frames + pos;
656 uint32_t len =
657 ((uint32_t)p[1] << 24) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 8) | (uint32_t)p[4];
658 if ((size_t)len > frames_len - pos - 5) return -1;
659
660 out->compressed = p[0];
661 out->data = p + 5;
662 out->len = (size_t)len;
663 *offset = pos + 5 + (size_t)len;
664 return 0;
665}
666
677int cwist_grpc_encode_message(const void *payload, size_t payload_len, uint8_t compressed,
678 uint8_t **out, size_t *out_len) {
679 if (!out || !out_len || (payload_len > 0 && !payload)) return -1;
680 if (payload_len > UINT32_MAX) return -1;
681
682 uint8_t *buf = (uint8_t *)cwist_alloc(payload_len + 5);
683 if (!buf) return -1;
684 buf[0] = compressed ? 1 : 0;
685 buf[1] = (uint8_t)((payload_len >> 24) & 0xff);
686 buf[2] = (uint8_t)((payload_len >> 16) & 0xff);
687 buf[3] = (uint8_t)((payload_len >> 8) & 0xff);
688 buf[4] = (uint8_t)(payload_len & 0xff);
689 if (payload_len > 0) memcpy(buf + 5, payload, payload_len);
690
691 *out = buf;
692 *out_len = payload_len + 5;
693 return 0;
694}
695
701void cwist_grpc_decoder_init(cwist_grpc_decoder *decoder, size_t max_message_size) {
702 if (!decoder) return;
703 memset(decoder, 0, sizeof(*decoder));
704 decoder->max_message_size = max_message_size ? max_message_size : (16u * 1024u * 1024u);
705}
706
714 if (!decoder) return;
715 cwist_free(decoder->payload);
716 memset(decoder, 0, sizeof(*decoder));
717}
718
734int cwist_grpc_decoder_feed(cwist_grpc_decoder *decoder, const void *data, size_t len,
736 if (!decoder || (!data && len) || !callback) return -1;
737 const uint8_t *input = data;
738 while (len) {
739 if (decoder->header_len < sizeof(decoder->header)) {
740 size_t take = sizeof(decoder->header) - decoder->header_len;
741 if (take > len) take = len;
742 memcpy(decoder->header + decoder->header_len, input, take);
743 decoder->header_len += take;
744 input += take;
745 len -= take;
746 if (decoder->header_len < sizeof(decoder->header)) continue;
747 decoder->compressed = decoder->header[0];
748 decoder->payload_len = ((size_t)decoder->header[1] << 24) |
749 ((size_t)decoder->header[2] << 16) |
750 ((size_t)decoder->header[3] << 8) | (size_t)decoder->header[4];
751 if (decoder->compressed > 1 || decoder->payload_len > decoder->max_message_size)
752 return -1;
753 if (decoder->payload_len) {
754 decoder->payload = cwist_alloc(decoder->payload_len);
755 if (!decoder->payload) return -1;
756 }
757 }
758 size_t take = decoder->payload_len - decoder->payload_used;
759 if (take > len) take = len;
760 if (take) memcpy(decoder->payload + decoder->payload_used, input, take);
761 decoder->payload_used += take;
762 input += take;
763 len -= take;
764 if (decoder->payload_used != decoder->payload_len) continue;
765 cwist_grpc_message message = {decoder->compressed, decoder->payload, decoder->payload_len};
766 if (callback(ctx, &message) != 0) return -1;
767 cwist_free(decoder->payload);
768 decoder->payload = NULL;
769 decoder->payload_len = decoder->payload_used = decoder->header_len = 0;
770 }
771 return 0;
772}
773
785 const char *message, const void *payload, size_t payload_len) {
786 if (!res) return;
787
789 cwist_http_header_add(&res->headers, "content-type", "application/grpc");
790 cwist_http_header_add(&res->headers, "grpc-accept-encoding", "gzip, identity");
791
792 char status_buf[16];
793 snprintf(status_buf, sizeof(status_buf), "%d", (int)status);
794 cwist_http_header_add(&res->headers, "grpc-status", status_buf);
795 if (message) {
796 cwist_http_header_add(&res->headers, "grpc-message", message);
797 }
798
799 uint8_t *frame = NULL;
800 size_t frame_len = 0;
801 if (cwist_grpc_encode_message(payload, payload_len, 0, &frame, &frame_len) == 0) {
804 } else {
806 cwist_sstring_assign(res->body, "");
807 cwist_http_header_add(&res->headers, "grpc-status", "13");
808 cwist_http_header_add(&res->headers, "grpc-message", "failed to encode gRPC response");
809 }
810}
811
820 const char *message) {
821 cwist_grpc_set_response(res, status, message, NULL, 0);
822}
823
836int cwist_grpc_stream_send(cwist_grpc_stream *stream, const void *payload, size_t payload_len) {
837 if (!stream) return -1;
838 int use_gzip = 0;
839 if (stream->session) {
840 use_gzip = (((cwist_grpc_session *)stream->session)->resp_encoding == 1);
841 } else if (stream->req) {
842 use_gzip = grpc_client_accepts_gzip(stream->req);
843 }
844
845 uint8_t *frame = NULL;
846 size_t frame_len = 0;
847 if (use_gzip && payload_len > 0) {
848 uint8_t *cbuf = NULL;
849 size_t clen = 0;
850 if (grpc_gzip_deflate(payload, payload_len, &cbuf, &clen) == 0) {
853 if (enc_rc != 0) {
854 stream->status = CWIST_GRPC_INTERNAL;
855 stream->status_message = "failed to encode gRPC stream message";
856 return -1;
857 }
858 } else {
859 if (cwist_grpc_encode_message(payload, payload_len, 0, &frame, &frame_len) != 0) {
860 stream->status = CWIST_GRPC_INTERNAL;
861 stream->status_message = "failed to encode gRPC stream message";
862 return -1;
863 }
864 }
865 } else {
866 if (cwist_grpc_encode_message(payload, payload_len, 0, &frame, &frame_len) != 0) {
867 stream->status = CWIST_GRPC_INTERNAL;
868 stream->status_message = "failed to encode gRPC stream message";
869 return -1;
870 }
871 }
872 if (stream->session) {
873 /* Transport-backed stream: emit a DATA frame immediately. */
874 if (!stream->write_frame ||
875 stream->write_frame(stream->write_frame_ctx, frame, frame_len, 0) != 0) {
878 stream->status_message = "failed to write gRPC DATA frame";
879 return -1;
880 }
882 return 0;
883 }
884 if (!stream->res || !stream->res->body) {
886 return -1;
887 }
888 if (stream->write_frame &&
889 stream->write_frame(stream->write_frame_ctx, frame, frame_len, 0) != 0) {
892 stream->status_message = "failed to write gRPC DATA frame";
893 return -1;
894 }
897 bool appended = cwist_error_is_ok(&err);
899 if (!appended) {
900 stream->status = CWIST_GRPC_INTERNAL;
901 stream->status_message = "failed to append gRPC stream message";
902 return -1;
903 }
904 return 0;
905}
906
917 int (*write_frame)(void *, const uint8_t *, size_t, int),
918 void *ctx) {
919 if (!stream) return;
920 stream->write_frame = write_frame;
921 stream->write_frame_ctx = ctx;
922}
923
934 const char *message) {
935 if (!stream) return;
936 if (stream->closed) return;
937 stream->status = status;
938 stream->status_message = message;
939 stream->closed = 1;
940
941 if (stream->session) {
942 grpc_session_send_trailers(stream->session, status, message);
943 return;
944 }
945 if (!stream->res) return;
946
947 char status_buf[16];
948 snprintf(status_buf, sizeof(status_buf), "%d", (int)status);
949 cwist_http_header_add(&stream->res->headers, "grpc-status", status_buf);
950 if (message) {
951 cwist_http_header_add(&stream->res->headers, "grpc-message", message);
952 }
953}
954
964 if (!stream) return;
965 stream->retry_pushback_ms = ms;
966 stream->retry_pushback_set = 1;
967 if (!stream->session && stream->res) {
968 /* Buffered path: the HTTP/2 layer moves this header into the
969 * trailers (Trailers-Only on error). */
970 char buf[20];
971 snprintf(buf, sizeof(buf), "%d", (int)ms);
972 cwist_http_header_add(&stream->res->headers, "grpc-retry-pushback-ms", buf);
973 }
974}
975
984static int grpc_health_status_locked(const cwist_grpc_health_registry *reg, const char *service) {
985 for (const cwist_grpc_health_state *it = reg->states; it; it = it->next)
986 if (it->service && strcmp(it->service, service ? service : "") == 0)
987 return it->serving ? 1 : 2;
988 return service && *service ? 3 : 1; /* SERVICE_UNKNOWN, or overall SERVING */
989}
990
996static char *grpc_health_service_copy(const cwist_grpc_message *message) {
998 cwist_pb_reader_init(&reader, message->data, message->len);
999 cwist_pb_field field;
1000 while (cwist_pb_read_field(&reader, &field) > 0)
1001 if (field.number == 1 && field.wire_type == CWIST_PB_LEN) {
1002 char *owned = cwist_alloc(field.len + 1);
1003 if (!owned) return NULL;
1004 memcpy(owned, field.bytes, field.len);
1005 owned[field.len] = '\0';
1006 return owned;
1007 }
1008 return NULL;
1009}
1010
1017static void grpc_health_reply(cwist_http_response *res, cwist_grpc_health_registry *reg,
1018 const cwist_grpc_message *message) {
1019 char *service = grpc_health_service_copy(message);
1020 pthread_mutex_lock(&reg->mu);
1021 int status = grpc_health_status_locked(reg, service);
1025 if (cwist_pb_write_uint64_field(&writer, 1, (uint64_t)status) != 0)
1026 cwist_grpc_set_error(res, CWIST_GRPC_INTERNAL, "health encoding failed");
1027 else
1030 cwist_free(service);
1031}
1032
1041static void grpc_health_check(cwist_http_request *req, cwist_http_response *res,
1042 const cwist_grpc_message *message, void *ctx) {
1043 (void)req;
1044 grpc_health_reply(res, ctx, message);
1045}
1046
1055static int grpc_health_send_status(cwist_grpc_stream *stream, cwist_grpc_health_registry *reg,
1056 const char *service) {
1057 pthread_mutex_lock(&reg->mu);
1058 int status = grpc_health_status_locked(reg, service);
1062 int rc = cwist_pb_write_uint64_field(&writer, 1, (uint64_t)status);
1063 if (rc == 0) rc = cwist_grpc_stream_send(stream, writer.data, writer.len);
1065 if (rc != 0 && stream->status == CWIST_GRPC_OK) {
1066 stream->status = CWIST_GRPC_INTERNAL;
1067 stream->status_message = "health encoding failed";
1068 }
1069 return rc;
1070}
1071
1080static void grpc_health_watch(cwist_grpc_stream *stream, void *ctx) {
1082 char *service = NULL;
1083 if (stream->session) {
1084 /* Transport path: the HealthCheckRequest arrives as the first
1085 * (and only) message; the client then half-closes. */
1087 int rc = cwist_grpc_stream_recv(stream, &msg);
1088 if (rc < 0) return; /* cancelled before the request arrived */
1089 if (rc > 0) service = grpc_health_service_copy(&msg);
1090 } else {
1091 cwist_grpc_message empty = {0, NULL, 0};
1092 const cwist_grpc_message *message = stream->message_count ? &stream->messages[0] : &empty;
1093 service = grpc_health_service_copy(message);
1094 }
1095
1096 /* Send the current status immediately, as the protocol requires. */
1097 if (grpc_health_send_status(stream, reg, service) != 0) goto out;
1098 if (!stream->session) goto out; /* buffered dispatch: single snapshot */
1099
1100 atomic_fetch_add(&reg->watchers, 1);
1101 for (;;) {
1102 int changed = 0;
1103 pthread_mutex_lock(&reg->mu);
1104 uint64_t gen = reg->generation;
1105 while (reg->generation == gen && !cwist_grpc_stream_cancelled(stream) &&
1107 /* Poll cancellation/deadline on a short tick; status changes
1108 * wake us immediately through the registry condvar. */
1109 struct timespec ts;
1111 ts.tv_nsec += 100000000L;
1112 if (ts.tv_nsec >= 1000000000L) {
1113 ts.tv_sec++;
1114 ts.tv_nsec -= 1000000000L;
1115 }
1116 pthread_cond_timedwait(&reg->cond, &reg->mu, &ts);
1117 }
1118 changed = reg->generation != gen;
1120 if (!changed) break; /* cancelled or deadline expired */
1121 if (grpc_health_send_status(stream, reg, service) != 0) break;
1122 }
1123 atomic_fetch_sub(&reg->watchers, 1);
1124out:
1125 cwist_free(service);
1126}
1127
1138 if (!app) return -1;
1140 if (!reg) return -1;
1141 memset(reg, 0, sizeof(*reg));
1143 pthread_cond_init(&reg->cond, NULL);
1144 atomic_store(&reg->watchers, 0);
1145 if (grpc_register_route(app, "grpc.health.v1.Health", "Check", 0, grpc_health_check, NULL,
1146 reg) != 0 ||
1147 grpc_register_route(app, "grpc.health.v1.Health", "Watch", 1, NULL, grpc_health_watch,
1148 reg) != 0) {
1150 pthread_cond_destroy(&reg->cond);
1151 cwist_free(reg);
1152 return -1;
1153 }
1154 cwist_grpc_route *route = grpc_find_route(app, "/grpc.health.v1.Health/Check");
1155 if (route) route->builtin = 1;
1156 route = grpc_find_route(app, "/grpc.health.v1.Health/Watch");
1157 if (route) route->builtin = 2;
1158 return 0;
1159}
1160
1173int cwist_app_grpc_health_set_status(cwist_app *app, const char *service, int serving) {
1174 cwist_grpc_route *route = grpc_find_route(app, "/grpc.health.v1.Health/Check");
1175 if (!route || !route->user_ctx || !service) return -1;
1177 pthread_mutex_lock(&reg->mu);
1178 for (cwist_grpc_health_state *it = reg->states; it; it = it->next)
1179 if (it->service && strcmp(it->service, service) == 0) {
1180 it->serving = !!serving;
1181 reg->generation++;
1184 return 0;
1185 }
1187 if (!item) {
1189 return -1;
1190 }
1191 item->service = cwist_alloc(strlen(service) + 1);
1192 if (!item->service) {
1195 return -1;
1196 }
1197 strcpy(item->service, service);
1198 item->serving = !!serving;
1199 item->next = reg->states;
1200 reg->states = item;
1201 reg->generation++;
1204 return 0;
1205}
1206
1213 cwist_grpc_route *route = grpc_find_route(app, "/grpc.health.v1.Health/Watch");
1214 if (!route || !route->user_ctx) return -1;
1216 return atomic_load(&reg->watchers);
1217}
1218
1226static int grpc_reflection_append_service(cwist_pb_writer *response, const char *service) {
1230 int rc = cwist_pb_write_string_field(&item, 1, service) ||
1231 cwist_pb_write_bytes_field(&list, 1, item.data, item.len) ||
1235 return rc ? -1 : 0;
1236}
1237
1247static void grpc_reflection_info(cwist_grpc_stream *stream, void *ctx) {
1248 (void)ctx;
1252 for (; route; route = route->next) {
1253 if (!route->path || route->path[0] != '/') continue;
1254 const char *slash = strrchr(route->path + 1, '/');
1255 if (!slash) continue;
1256 size_t len = (size_t)(slash - route->path - 1);
1258 char *service = cwist_scratch_alloc(&service_s, len + 1);
1259 if (!service) {
1261 break;
1262 }
1263 memcpy(service, route->path + 1, len);
1264 service[len] = '\0';
1265 if (grpc_reflection_append_service(&response, service) != 0)
1266 stream->status = CWIST_GRPC_INTERNAL;
1267 if (stream->status != CWIST_GRPC_OK) break;
1268 }
1269 if (stream->status == CWIST_GRPC_OK)
1270 cwist_grpc_stream_send(stream, response.data, response.len);
1272}
1273
1282 if (grpc_register_route(app, "grpc.reflection.v1alpha.ServerReflection", "ServerReflectionInfo",
1283 1, NULL, grpc_reflection_info, NULL) != 0)
1284 return -1;
1286 grpc_find_route(app, "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo");
1287 if (route) route->builtin = 3;
1288 return 0;
1289}
1290
1307static int grpc_register_route(cwist_app *app, const char *service, const char *method,
1309 cwist_grpc_stream_handler_func stream_handler, void *user_ctx) {
1310 if (!app || !service || !method) return -1;
1311 if ((!streaming && !unary_handler) || (streaming && !stream_handler)) return -1;
1312 size_t service_len = strlen(service);
1313 size_t method_len = strlen(method);
1314 if (service_len == 0 || method_len == 0) return -1;
1315
1316 size_t path_len = service_len + method_len + 3;
1317 char *path = (char *)cwist_alloc(path_len);
1318 if (!path) return -1;
1319 snprintf(path, path_len, "/%s/%s", service, method);
1320
1321 cwist_grpc_route *existing = grpc_find_route(app, path);
1322 if (existing) {
1323 existing->streaming = streaming;
1324 existing->handler = unary_handler;
1325 existing->stream_handler = stream_handler;
1326 existing->user_ctx = user_ctx;
1327 cwist_free(path);
1328 return 0;
1329 }
1330
1332 if (!route) {
1333 cwist_free(path);
1334 return -1;
1335 }
1336 route->path = path;
1337 route->streaming = streaming;
1338 route->handler = unary_handler;
1339 route->stream_handler = stream_handler;
1340 route->user_ctx = user_ctx;
1341 route->next = (cwist_grpc_route *)app->grpc_routes;
1342 app->grpc_routes = route;
1343
1344 cwist_app_post(app, path, streaming ? grpc_dispatch_stream : grpc_dispatch_unary);
1345 return 0;
1346}
1347
1357int cwist_app_grpc_unary(cwist_app *app, const char *service, const char *method,
1358 cwist_grpc_unary_handler_func handler, void *user_ctx) {
1359 return grpc_register_route(app, service, method, 0, handler, NULL, user_ctx);
1360}
1361
1371int cwist_app_grpc_stream(cwist_app *app, const char *service, const char *method,
1372 cwist_grpc_stream_handler_func handler, void *user_ctx) {
1373 return grpc_register_route(app, service, method, 1, NULL, handler, user_ctx);
1374}
1375
1383 if (!app) return;
1385 while (route) {
1386 cwist_grpc_route *next = route->next;
1387 if (route->builtin == 1 && route->user_ctx) {
1389 cwist_grpc_health_state *state = reg->states;
1390 while (state) {
1391 cwist_grpc_health_state *state_next = state->next;
1392 cwist_free(state->service);
1393 cwist_free(state);
1394 state = state_next;
1395 }
1397 pthread_cond_destroy(&reg->cond);
1398 cwist_free(reg);
1399 }
1400 cwist_free(route->path);
1402 route = next;
1403 }
1404 app->grpc_routes = NULL;
1405}
1406
1418 if (!dst || !src) return -1;
1419 const cwist_grpc_route *route = (const cwist_grpc_route *)src->grpc_routes;
1420 while (route) {
1421 if (route->builtin == 1) {
1422 if (cwist_app_grpc_health(dst) != 0) return -1;
1423 const cwist_grpc_health_registry *reg = route->user_ctx;
1424 for (const cwist_grpc_health_state *it = reg->states; it; it = it->next)
1425 if (it->service &&
1426 cwist_app_grpc_health_set_status(dst, it->service, it->serving) != 0)
1427 return -1;
1428 route = route->next;
1429 continue;
1430 }
1431 if (route->builtin == 2) {
1432 route = route->next;
1433 continue;
1434 }
1435 if (route->builtin == 3) {
1436 if (cwist_app_grpc_reflection(dst) != 0) return -1;
1437 route = route->next;
1438 continue;
1439 }
1440 const char *path = route->path;
1441 if (!path || path[0] != '/') return -1;
1442 const char *method_sep = strrchr(path + 1, '/');
1443 if (!method_sep || method_sep == path + 1 || method_sep[1] == '\0') return -1;
1444
1445 size_t service_len = (size_t)(method_sep - (path + 1));
1446 char *service CWIST_DEFER_FREE = (char *)cwist_alloc(service_len + 1);
1447 if (!service) return -1;
1448 memcpy(service, path + 1, service_len);
1449 service[service_len] = '\0';
1450
1451 int rc = route->streaming ? cwist_app_grpc_stream(dst, service, method_sep + 1,
1452 route->stream_handler, route->user_ctx)
1453 : cwist_app_grpc_unary(dst, service, method_sep + 1,
1454 route->handler, route->user_ctx);
1455 if (rc != 0) return -1;
1456 route = route->next;
1457 }
1458 return 0;
1459}
1460
1461/* ------------------------------------------------------------------ */
1462/* Incremental HTTP/2 streaming session engine */
1463/* ------------------------------------------------------------------ */
1464
1465typedef struct cwist_grpc_h2_conn_ctx {
1466 cwist_app *app;
1467 pthread_mutex_t mu; /* sessions list + refs */
1468 int refs; /* connection + one per session */
1469 cwist_grpc_session *sessions;
1471
1477static void grpc_conn_ctx_release(cwist_grpc_h2_conn_ctx *ctx) {
1478 int last;
1479 pthread_mutex_lock(&ctx->mu);
1480 last = (--ctx->refs == 0);
1481 pthread_mutex_unlock(&ctx->mu);
1482 if (last) {
1483 pthread_mutex_destroy(&ctx->mu);
1484 cwist_free(ctx);
1485 }
1486}
1487
1495static void grpc_session_release(cwist_grpc_session *session) {
1496 if (atomic_fetch_sub(&session->refs, 1) != 1) return;
1497 cwist_grpc_h2_conn_ctx *ctx = session->conn_ctx;
1498 pthread_mutex_lock(&ctx->mu);
1499 cwist_grpc_session **pp = &ctx->sessions;
1500 while (*pp && *pp != session) pp = &(*pp)->next;
1501 if (*pp) *pp = session->next;
1502 pthread_mutex_unlock(&ctx->mu);
1503 grpc_conn_ctx_release(ctx);
1504
1505 cwist_grpc_decoder_destroy(&session->decoder);
1506 while (session->qhead) {
1507 grpc_qnode *next = session->qhead->next;
1508 cwist_free(session->qhead->data);
1509 cwist_free(session->qhead);
1510 session->qhead = next;
1511 }
1512 cwist_free(session->recv_buf);
1513 if (session->stream.req) cwist_http_request_destroy(session->stream.req);
1514 pthread_mutex_destroy(&session->mu);
1515 pthread_cond_destroy(&session->cond);
1516 pthread_mutex_destroy(&session->wmu);
1517 cwist_free(session);
1518}
1519
1530static int grpc_session_push(cwist_grpc_session *session, uint8_t *data, size_t len) {
1531 grpc_qnode *node = (grpc_qnode *)cwist_alloc(sizeof(*node));
1532 if (!node) return -1;
1533 node->data = data;
1534 node->len = len;
1535 node->next = NULL;
1536 pthread_mutex_lock(&session->mu);
1537 if (session->cancelled || session->eof) {
1538 pthread_mutex_unlock(&session->mu);
1539 cwist_free(data);
1540 cwist_free(node);
1541 return -1;
1542 }
1543 if (session->qtail)
1544 session->qtail->next = node;
1545 else
1546 session->qhead = node;
1547 session->qtail = node;
1548 pthread_cond_signal(&session->cond);
1549 pthread_mutex_unlock(&session->mu);
1550 return 0;
1551}
1552
1563static int grpc_session_decoded(void *ctx, const cwist_grpc_message *message) {
1564 cwist_grpc_session *session = ctx;
1565 uint8_t *copy;
1566 size_t len;
1567 if (message->compressed) {
1568 if (session->encoding != 1) return -1; /* rejected by on_data */
1569 if (grpc_gzip_inflate(message->data, message->len, &copy, &len) != 0) return -1;
1570 } else {
1571 copy = (uint8_t *)cwist_alloc(message->len ? message->len : 1);
1572 if (!copy) return -1;
1573 if (message->len) memcpy(copy, message->data, message->len);
1574 len = message->len;
1575 }
1576 if (grpc_session_push(session, copy, len) != 0) return -1;
1577 return 0;
1578}
1579
1589static void grpc_session_fail(cwist_grpc_session *session, cwist_grpc_status_t status,
1590 const char *message) {
1591 pthread_mutex_lock(&session->mu);
1592 session->cancelled = 1;
1593 __atomic_store_n(&session->stream.cancelled, 1, __ATOMIC_RELEASE);
1594 session->stream.status = status;
1595 session->stream.status_message = message;
1596 pthread_cond_broadcast(&session->cond);
1597 pthread_mutex_unlock(&session->mu);
1598 grpc_session_send_trailers(session, status, message);
1599}
1600
1610static int grpc_session_send_headers_locked(cwist_grpc_session *session) {
1611 if (session->headers_sent) return 0;
1612 if (!session->h2s) return -1;
1614 size_t count = 0;
1615 hdrs[count++] = (cwist_http2_header){"content-type", "application/grpc"};
1616 hdrs[count++] = (cwist_http2_header){"grpc-accept-encoding", "gzip, identity"};
1617 if (session->resp_encoding == 1) hdrs[count++] = (cwist_http2_header){"grpc-encoding", "gzip"};
1618 if (cwist_http2_stream_send_headers(session->h2s, 200, hdrs, count, 0) != 0) return -1;
1619 session->headers_sent = 1;
1620 return 0;
1621}
1622
1634static void grpc_session_send_trailers(cwist_grpc_session *session, cwist_grpc_status_t status,
1635 const char *message) {
1636 char status_buf[16];
1637 snprintf(status_buf, sizeof(status_buf), "%d", (int)status);
1638 char pushback_buf[20];
1639 if (session->stream.retry_pushback_set)
1640 snprintf(pushback_buf, sizeof(pushback_buf), "%d", (int)session->stream.retry_pushback_ms);
1641 pthread_mutex_lock(&session->wmu);
1642 if (session->h2s && !__atomic_load_n(&session->trailers_sent, __ATOMIC_ACQUIRE)) {
1643 if (!session->headers_sent && status != CWIST_GRPC_OK) {
1644 /* Trailers-Only (PROTOCOL-HTTP2, gRFC A6): the whole response is
1645 * one HEADERS frame with END_STREAM. Response-Headers never went
1646 * out, so the RPC stays retryable for conforming clients. */
1648 size_t count = 0;
1649 hdrs[count++] = (cwist_http2_header){"content-type", "application/grpc"};
1650 hdrs[count++] = (cwist_http2_header){"grpc-accept-encoding", "gzip, identity"};
1651 hdrs[count++] = (cwist_http2_header){"grpc-status", status_buf};
1652 if (message) hdrs[count++] = (cwist_http2_header){"grpc-message", message};
1653 if (session->stream.retry_pushback_set)
1654 hdrs[count++] = (cwist_http2_header){"grpc-retry-pushback-ms", pushback_buf};
1655 if (cwist_http2_stream_send_headers(session->h2s, 200, hdrs, count, 1) == 0)
1656 session->headers_sent = 1;
1657 __atomic_store_n(&session->trailers_sent, 1, __ATOMIC_RELEASE);
1658 } else if (grpc_session_send_headers_locked(session) == 0) {
1660 size_t count = 0;
1661 pairs[count].name = "grpc-status";
1662 pairs[count].value = status_buf;
1663 count++;
1664 if (message) {
1665 pairs[count].name = "grpc-message";
1666 pairs[count].value = message;
1667 count++;
1668 }
1669 if (session->stream.retry_pushback_set) {
1670 pairs[count].name = "grpc-retry-pushback-ms";
1671 pairs[count].value = pushback_buf;
1672 count++;
1673 }
1675 __atomic_store_n(&session->trailers_sent, 1, __ATOMIC_RELEASE);
1676 } else {
1677 /* transport broken; do not retry */
1678 __atomic_store_n(&session->trailers_sent, 1, __ATOMIC_RELEASE);
1679 }
1680 }
1681 pthread_mutex_unlock(&session->wmu);
1682}
1683
1695static int grpc_session_write_frame(void *ctx, const uint8_t *frame, size_t frame_len,
1696 int end_stream) {
1698 cwist_grpc_session *session = ctx;
1699 pthread_mutex_lock(&session->wmu);
1700 int rc = -1;
1701 if (session->h2s && !__atomic_load_n(&session->trailers_sent, __ATOMIC_ACQUIRE) &&
1702 grpc_session_send_headers_locked(session) == 0)
1704 pthread_mutex_unlock(&session->wmu);
1705 return rc;
1706}
1707
1716static void *grpc_session_thread(void *arg) {
1717 cwist_grpc_session *session = arg;
1718 session->handler(&session->stream, session->user_ctx);
1719 if (!session->stream.closed) {
1720 cwist_grpc_stream_close(&session->stream, session->stream.status,
1721 session->stream.status_message);
1722 }
1723 grpc_session_release(session);
1724 return NULL;
1725}
1726
1741 if (!stream || !out) return -1;
1742
1743 if (!stream->session) {
1744 /* Buffered path: pop the pre-decoded message array. */
1745 if (stream->deadline_ms && grpc_now_ms() >= stream->deadline_ms) {
1747 return -1;
1748 }
1749 if (stream->recv_pos >= stream->message_count) return 0;
1750 *out = stream->messages[stream->recv_pos++];
1751 return 1;
1752 }
1753
1754 cwist_grpc_session *session = stream->session;
1755 pthread_mutex_lock(&session->mu);
1756 for (;;) {
1757 if (session->qhead) {
1758 grpc_qnode *node = session->qhead;
1759 session->qhead = node->next;
1760 if (!session->qhead) session->qtail = NULL;
1761 pthread_mutex_unlock(&session->mu);
1762 cwist_free(session->recv_buf);
1763 session->recv_buf = node->data;
1764 out->compressed = 0;
1765 out->data = node->data;
1766 out->len = node->len;
1767 cwist_free(node);
1768 return 1;
1769 }
1770 if (session->cancelled) {
1771 pthread_mutex_unlock(&session->mu);
1772 return -1;
1773 }
1774 if (session->eof) {
1775 pthread_mutex_unlock(&session->mu);
1776 return 0;
1777 }
1778 if (stream->deadline_ms) {
1779 uint64_t now = grpc_now_ms();
1780 if (now >= stream->deadline_ms) {
1781 session->cancelled = 1;
1783 /* Report the real outcome so a handler that simply closes
1784 * with stream->status still emits DEADLINE_EXCEEDED. */
1786 stream->status_message = "deadline exceeded";
1787 pthread_mutex_unlock(&session->mu);
1788 return -1;
1789 }
1790 uint64_t left = stream->deadline_ms - now;
1791 struct timespec ts;
1792 struct timespec rt;
1794 ts.tv_sec = rt.tv_sec + (time_t)(left / 1000);
1795 ts.tv_nsec = rt.tv_nsec + (long)((left % 1000) * 1000000);
1796 if (ts.tv_nsec >= 1000000000L) {
1797 ts.tv_sec++;
1798 ts.tv_nsec -= 1000000000L;
1799 }
1800 pthread_cond_timedwait(&session->cond, &session->mu, &ts);
1801 } else {
1802 pthread_cond_wait(&session->cond, &session->mu);
1803 }
1804 }
1805}
1806
1815 if (!stream) return 0;
1816 /* Public field, also set by the poll/deadline path without the
1817 * handler's lock: always accessed atomically (#351). */
1818 if (__atomic_load_n(&stream->cancelled, __ATOMIC_ACQUIRE)) return 1;
1819 if (stream->session) {
1820 cwist_grpc_session *session = stream->session;
1821 pthread_mutex_lock(&session->mu);
1822 int cancelled = session->cancelled;
1823 pthread_mutex_unlock(&session->mu);
1824 return cancelled;
1825 }
1826 return 0;
1827}
1828
1835 if (!stream || !stream->deadline_ms) return UINT64_MAX;
1836 uint64_t now = grpc_now_ms();
1837 return now >= stream->deadline_ms ? 0 : stream->deadline_ms - now;
1838}
1839
1840/* --- HTTP/2 stream hooks --- */
1841
1847static void *grpc_h2_on_conn_open(void *user_ctx) {
1849 if (!ctx) return NULL;
1850 ctx->app = (cwist_app *)user_ctx;
1851 ctx->refs = 1;
1852 ctx->sessions = NULL;
1853 pthread_mutex_init(&ctx->mu, NULL);
1854 return ctx;
1855}
1856
1860static void grpc_h2_on_conn_close(void *conn_ctx) {
1861 if (conn_ctx) grpc_conn_ctx_release(conn_ctx);
1862}
1863
1878static void *grpc_h2_on_headers(void *conn_ctx, cwist_http_request *req, cwist_h2_stream *stream) {
1879 cwist_grpc_h2_conn_ctx *ctx = conn_ctx;
1880 if (!ctx || !ctx->app || !req || !req->path || !req->path->data) return NULL;
1881
1882 cwist_grpc_route *route = grpc_find_route(ctx->app, req->path->data);
1883 /* Unary-only builtins (health Check) and reflection stay on the buffered
1884 * dispatch path; the health Watch builtin streams live. */
1885 if (!route || !route->stream_handler || (route->builtin && route->builtin != 2)) return NULL;
1886
1887 const char *ct = grpc_header_get(req, "content-type");
1888 if (!grpc_content_type_is_grpc(ct)) return NULL;
1889
1890 int encoding = grpc_request_encoding(req);
1891 if (encoding < 0) return NULL; /* buffered dispatch replies UNIMPLEMENTED */
1892
1893 cwist_grpc_session *session = (cwist_grpc_session *)cwist_alloc(sizeof(*session));
1894 if (!session) return NULL;
1895 memset(session, 0, sizeof(*session));
1896 pthread_mutex_init(&session->mu, NULL);
1897 pthread_cond_init(&session->cond, NULL);
1898 pthread_mutex_init(&session->wmu, NULL);
1899 session->h2s = stream;
1900 session->conn_ctx = ctx;
1901 session->encoding = encoding;
1902 cwist_grpc_decoder_init(&session->decoder, 0);
1903 atomic_store(&session->refs, 2); /* transport + handler thread */
1904
1905 session->stream.req = req;
1906 session->stream.res = NULL;
1907 session->stream.messages = NULL;
1908 session->stream.message_count = 0;
1909 session->stream.status = CWIST_GRPC_OK;
1910 session->stream.status_message = NULL;
1911 session->stream.closed = 0;
1912 session->stream.write_frame = grpc_session_write_frame;
1913 session->stream.write_frame_ctx = session;
1914 session->stream.session = session;
1915 __atomic_store_n(&session->stream.cancelled, 0, __ATOMIC_RELEASE);
1916 session->stream.recv_pos = 0;
1917 const char *timeout = grpc_header_get(req, "grpc-timeout");
1918 if (timeout) {
1919 uint64_t timeout_ms = 0;
1920 if (cwist_grpc_parse_timeout(timeout, &timeout_ms) == 0)
1921 session->stream.deadline_ms = grpc_now_ms() + timeout_ms;
1922 }
1923 session->handler = route->stream_handler;
1924 session->user_ctx = route->user_ctx;
1925 session->resp_encoding = grpc_client_accepts_gzip(req) ? 1 : 0;
1926
1927 /* Response-Headers are deliberately not sent yet (gRFC A6): they go out
1928 * with the first DATA frame, or the call ends in Trailers-Only form so
1929 * conforming clients may retry the RPC. */
1930
1931 pthread_mutex_lock(&ctx->mu);
1932 ctx->refs++;
1933 session->next = ctx->sessions;
1934 ctx->sessions = session;
1935 pthread_mutex_unlock(&ctx->mu);
1936
1937 pthread_t tid;
1938 if (pthread_create(&tid, NULL, grpc_session_thread, session) != 0) {
1939 pthread_mutex_lock(&ctx->mu);
1940 ctx->refs--;
1941 cwist_grpc_session **pp = &ctx->sessions;
1942 while (*pp && *pp != session) pp = &(*pp)->next;
1943 if (*pp) *pp = session->next;
1944 pthread_mutex_unlock(&ctx->mu);
1945 pthread_mutex_destroy(&session->mu);
1946 pthread_cond_destroy(&session->cond);
1947 pthread_mutex_destroy(&session->wmu);
1948 cwist_grpc_decoder_destroy(&session->decoder);
1949 cwist_free(session);
1950 return NULL;
1951 }
1953 return session;
1954}
1955
1971static int grpc_h2_on_data(void *conn_ctx, void *stream_ctx, const unsigned char *data, size_t len,
1972 int end_stream) {
1973 (void)conn_ctx;
1974 cwist_grpc_session *session = stream_ctx;
1975 if (!session) return 1;
1976
1977 if (data && len > 0) {
1978 if (cwist_grpc_decoder_feed(&session->decoder, data, len, grpc_session_decoded, session) !=
1979 0) {
1980 if (session->encoding != 1) {
1981 /* Compressed flag set without grpc-encoding: gzip. */
1982 grpc_session_fail(session, CWIST_GRPC_UNIMPLEMENTED,
1983 "compressed gRPC message without grpc-encoding: gzip");
1984 } else {
1985 grpc_session_fail(session, CWIST_GRPC_INVALID_ARGUMENT,
1986 "malformed gRPC message frame");
1987 }
1988 return 0; /* trailers carry the error; the poll sweep reaps us */
1989 }
1990 }
1991 if (end_stream) {
1992 pthread_mutex_lock(&session->mu);
1993 session->eof = 1;
1994 pthread_cond_broadcast(&session->cond);
1995 pthread_mutex_unlock(&session->mu);
1996 }
1997 return 0;
1998}
1999
2007static void grpc_h2_on_cancel(void *conn_ctx, void *stream_ctx) {
2008 (void)conn_ctx;
2009 cwist_grpc_session *session = stream_ctx;
2010 if (!session) return;
2011 pthread_mutex_lock(&session->mu);
2012 session->cancelled = 1;
2013 __atomic_store_n(&session->stream.cancelled, 1, __ATOMIC_RELEASE);
2014 pthread_cond_broadcast(&session->cond);
2015 pthread_mutex_unlock(&session->mu);
2016}
2017
2028static int grpc_h2_on_poll(void *conn_ctx, void *stream_ctx) {
2029 (void)conn_ctx;
2030 cwist_grpc_session *session = stream_ctx;
2031 if (!session) return 1;
2032
2033 /* Deadline enforcement: cancel the handler and emit DEADLINE_EXCEEDED
2034 * trailers so the client learns the outcome. */
2035 if (session->stream.deadline_ms &&
2036 !__atomic_load_n(&session->trailers_sent, __ATOMIC_ACQUIRE) &&
2037 grpc_now_ms() >= session->stream.deadline_ms) {
2038 pthread_mutex_lock(&session->mu);
2039 session->cancelled = 1;
2040 __atomic_store_n(&session->stream.cancelled, 1, __ATOMIC_RELEASE);
2041 pthread_cond_broadcast(&session->cond);
2042 pthread_mutex_unlock(&session->mu);
2043 grpc_session_send_trailers(session, CWIST_GRPC_DEADLINE_EXCEEDED, "deadline exceeded");
2044 }
2045 return __atomic_load_n(&session->trailers_sent, __ATOMIC_ACQUIRE);
2046}
2047
2056static uint64_t grpc_h2_next_deadline_ms(void *conn_ctx) {
2057 cwist_grpc_h2_conn_ctx *ctx = conn_ctx;
2058 if (!ctx) return 0;
2059 uint64_t nearest = 0;
2060 pthread_mutex_lock(&ctx->mu);
2061 for (cwist_grpc_session *s = ctx->sessions; s; s = s->next) {
2062 if (__atomic_load_n(&s->trailers_sent, __ATOMIC_ACQUIRE) || !s->stream.deadline_ms)
2063 continue;
2064 if (!nearest || s->stream.deadline_ms < nearest) nearest = s->stream.deadline_ms;
2065 }
2066 pthread_mutex_unlock(&ctx->mu);
2067 return nearest;
2068}
2069
2078static void grpc_h2_on_close(void *conn_ctx, void *stream_ctx) {
2079 (void)conn_ctx;
2080 cwist_grpc_session *session = stream_ctx;
2081 if (!session) return;
2082 /* Transport is gone: detach so handler-thread sends fail fast. */
2083 pthread_mutex_lock(&session->wmu);
2084 session->h2s = NULL;
2085 pthread_mutex_unlock(&session->wmu);
2086 pthread_mutex_lock(&session->mu);
2087 session->cancelled = 1;
2088 __atomic_store_n(&session->stream.cancelled, 1, __ATOMIC_RELEASE);
2089 pthread_cond_broadcast(&session->cond);
2090 pthread_mutex_unlock(&session->mu);
2091 grpc_session_release(session);
2092}
2093
2094static const cwist_http2_stream_hooks grpc_h2_hooks = {
2095 .on_conn_open = grpc_h2_on_conn_open,
2096 .on_conn_close = grpc_h2_on_conn_close,
2097 .on_headers = grpc_h2_on_headers,
2098 .on_data = grpc_h2_on_data,
2099 .on_cancel = grpc_h2_on_cancel,
2100 .on_poll = grpc_h2_on_poll,
2101 .next_deadline_ms = grpc_h2_next_deadline_ms,
2102 .on_close = grpc_h2_on_close,
2103};
2104
2110 return &grpc_h2_hooks;
2111}
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
#define CWIST_DEFER_FREE
Attach to a pointer variable's declaration to cwist_free() it automatically when that variable's encl...
Definition alloc.h:101
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
#define CWIST_SCRATCH_DEFER
Attach to a cwist_scratch_t variable's declaration so its allocation (arena- or heap-backed,...
Definition alloc.h:190
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
Definition sstring.c:212
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
Definition sstring.c:88
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
struct cwist_grpc_session cwist_grpc_session
Definition grpc.c:58
void cwist_grpc_set_response(cwist_http_response *res, cwist_grpc_status_t status, const char *message, const void *payload, size_t payload_len)
Build a complete buffered unary gRPC response.
Definition grpc.c:784
int cwist_app_grpc_health_watchers(cwist_app *app)
Count currently active Health/Watch streams.
Definition grpc.c:1212
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
int cwist_grpc_metadata_get_binary(cwist_http_request *req, const char *key, uint8_t *out, size_t out_cap, size_t *out_len)
Decode a binary ("-bin" suffixed) metadata header from base64.
Definition grpc.c:385
int cwist_grpc_stream_recv(cwist_grpc_stream *stream, cwist_grpc_message *out)
Receive the next inbound message on a stream.
Definition grpc.c:1740
int cwist_grpc_parse_timeout(const char *value, uint64_t *out_ms)
Parse a gRPC timeout value into milliseconds.
Definition grpc.c:146
struct grpc_qnode grpc_qnode
int cwist_app_grpc_health_set_status(cwist_app *app, const char *service, int serving)
Set or update the serving status of a named service.
Definition grpc.c:1173
struct cwist_grpc_route cwist_grpc_route
struct cwist_grpc_health_state cwist_grpc_health_state
struct cwist_grpc_h2_conn_ctx cwist_grpc_h2_conn_ctx
int cwist_app_grpc_stream(cwist_app *app, const char *service, const char *method, cwist_grpc_stream_handler_func handler, void *user_ctx)
Register a client-streaming gRPC method on an app.
Definition grpc.c:1371
void cwist_grpc_set_error(cwist_http_response *res, cwist_grpc_status_t status, const char *message)
Build a buffered gRPC error response with an empty message.
Definition grpc.c:819
int cwist_grpc_stream_cancelled(cwist_grpc_stream *stream)
Check whether a stream has been cancelled.
Definition grpc.c:1814
const char * cwist_grpc_metadata_get(cwist_http_request *req, const char *key)
Fetch a metadata header value from a gRPC request.
Definition grpc.c:371
void cwist_grpc_routes_destroy(cwist_app *app)
Free all gRPC routes and per-route resources on an app.
Definition grpc.c:1382
void cwist_grpc_decoder_init(cwist_grpc_decoder *decoder, size_t max_message_size)
Initialize an incremental gRPC message decoder.
Definition grpc.c:701
int cwist_grpc_stream_send(cwist_grpc_stream *stream, const void *payload, size_t payload_len)
Send one message on an open gRPC stream.
Definition grpc.c:836
void cwist_grpc_stream_set_retry_pushback(cwist_grpc_stream *stream, int32_t ms)
Advertise a server-side retry pushback hint to the client.
Definition grpc.c:963
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
void cwist_grpc_stream_set_writer(cwist_grpc_stream *stream, int(*write_frame)(void *, const uint8_t *, size_t, int), void *ctx)
Install a custom frame writer on a stream.
Definition grpc.c:916
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
int cwist_grpc_routes_clone(cwist_app *dst, const cwist_app *src)
Copy all gRPC routes (and built-in service state) to another app.
Definition grpc.c:1417
uint64_t cwist_grpc_stream_deadline_remaining_ms(cwist_grpc_stream *stream)
Milliseconds remaining until the stream deadline.
Definition grpc.c:1834
int cwist_app_grpc_unary(cwist_app *app, const char *service, const char *method, cwist_grpc_unary_handler_func handler, void *user_ctx)
Register a unary gRPC method on an app.
Definition grpc.c:1357
void cwist_grpc_stream_close(cwist_grpc_stream *stream, cwist_grpc_status_t status, const char *message)
Close a gRPC stream with a final status.
Definition grpc.c:933
struct cwist_grpc_health_registry cwist_grpc_health_registry
int cwist_app_grpc_reflection(cwist_app *app)
Register the built-in gRPC server reflection service on an app.
Definition grpc.c:1281
int cwist_grpc_decode_message(const void *frame, size_t frame_len, cwist_grpc_message *out)
Decode a single gRPC length-prefixed message frame.
Definition grpc.c:622
int cwist_grpc_decode_next_message(const void *frames, size_t frames_len, size_t *offset, cwist_grpc_message *out)
Decode the next message in a concatenated gRPC frame buffer.
Definition grpc.c:649
int cwist_app_grpc_health(cwist_app *app)
Register the built-in grpc.health.v1.Health service on an app.
Definition grpc.c:1137
const cwist_http2_stream_hooks * cwist_grpc_http2_hooks(void)
Access the gRPC HTTP/2 stream hooks table.
Definition grpc.c:2109
gRPC over HTTP/2 helpers for CWIST.
@ CWIST_GRPC_OK
Definition grpc.h:22
@ CWIST_GRPC_RESOURCE_EXHAUSTED
Definition grpc.h:30
@ CWIST_GRPC_INVALID_ARGUMENT
Definition grpc.h:25
@ CWIST_GRPC_UNIMPLEMENTED
Definition grpc.h:34
@ CWIST_GRPC_UNAVAILABLE
Definition grpc.h:36
@ CWIST_GRPC_DEADLINE_EXCEEDED
Definition grpc.h:26
@ CWIST_GRPC_INTERNAL
Definition grpc.h:35
int(* cwist_grpc_message_callback)(void *ctx, const cwist_grpc_message *message)
Definition grpc.h:80
enum cwist_grpc_status cwist_grpc_status_t
void(* cwist_grpc_unary_handler_func)(cwist_http_request *req, cwist_http_response *res, const cwist_grpc_message *message, void *user_ctx)
Definition grpc.h:82
void(* cwist_grpc_stream_handler_func)(cwist_grpc_stream *stream, void *user_ctx)
Definition grpc.h:85
int cwist_http2_stream_send_trailers(cwist_h2_stream *stream, const cwist_http2_header *trailers, size_t trailer_count)
Definition http2.c:2688
int cwist_http2_stream_send_headers(cwist_h2_stream *stream, int status, const cwist_http2_header *headers, size_t header_count, int end_stream)
Definition http2.c:2608
struct cwist_h2_stream cwist_h2_stream
Definition http2.h:23
int cwist_http2_stream_send_data(cwist_h2_stream *stream, const unsigned char *data, size_t len)
Send DATA on a hook-taken stream, honoring flow-control credit. Splits data into peer_max_frame_size ...
Definition http2.c:2642
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
Definition http.c:1880
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
@ CWIST_HTTP_OK
Definition http.h:28
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
Definition http.c:2180
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Definition http.c:1861
size_t cwist_http_header_remove(cwist_http_header_node **head, const char *key)
Remove all headers matching a name, honoring per-node ownership (heap vs arena vs borrowed buffers).
Definition http.c:1907
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
void cwist_pb_reader_init(cwist_pb_reader *r, const void *data, size_t len)
Initialize a reader over a caller-owned buffer.
Definition protobuf.c:244
int cwist_pb_write_uint64_field(cwist_pb_writer *w, uint32_t field_number, uint64_t value)
Write a uint64 field as a varint.
Definition protobuf.c:104
@ CWIST_PB_LEN
Definition protobuf.h:20
void cwist_pb_writer_free(cwist_pb_writer *w)
Release the buffer owned by a Protobuf writer.
Definition protobuf.c:28
void cwist_pb_writer_init(cwist_pb_writer *w)
Initialize a Protobuf writer to an empty buffer.
Definition protobuf.c:17
int cwist_pb_read_field(cwist_pb_reader *r, cwist_pb_field *field)
Read the next field key and payload from the reader.
Definition protobuf.c:284
int cwist_pb_write_bytes_field(cwist_pb_writer *w, uint32_t field_number, const void *data, size_t len)
Write a length-delimited bytes field (key, length varint, raw bytes).
Definition protobuf.c:215
int cwist_pb_write_string_field(cwist_pb_writer *w, uint32_t field_number, const char *value)
Write a string field (length-delimited, excluding the NUL terminator).
Definition protobuf.c:233
Main Application Context.
Definition app.h:155
void * grpc_routes
Unary gRPC route registry.
Definition app.h:219
Integrated error structure used throughout the system.
Definition cwist_err.h:72
size_t header_len
Definition grpc.h:72
size_t max_message_size
Definition grpc.h:77
uint8_t * payload
Definition grpc.h:73
size_t payload_len
Definition grpc.h:74
uint8_t header[5]
Definition grpc.h:71
uint8_t compressed
Definition grpc.h:76
size_t payload_used
Definition grpc.h:75
const uint8_t * data
Definition grpc.h:43
uint8_t compressed
Definition grpc.h:42
cwist_http_response * res
Definition grpc.h:49
const char * status_message
Definition grpc.h:53
size_t message_count
Definition grpc.h:51
void * session
Definition grpc.h:59
cwist_http_request * req
Definition grpc.h:48
cwist_grpc_status_t status
Definition grpc.h:52
uint64_t deadline_ms
Definition grpc.h:61
int retry_pushback_set
Definition grpc.h:65
int32_t retry_pushback_ms
Definition grpc.h:66
void * write_frame_ctx
Definition grpc.h:56
int(* write_frame)(void *ctx, const uint8_t *frame, size_t frame_len, int end_stream)
Definition grpc.h:55
const cwist_grpc_message * messages
Definition grpc.h:50
size_t recv_pos
Definition grpc.h:62
const char * name
Definition http2.h:27
void *(* on_conn_open)(void *user_ctx)
Definition http2.h:39
Linked list for headers to handle multiple headers easily.
Definition http.h:59
cwist_sstring * path
e.g., "/users/1"
Definition http.h:67
struct cwist_app * app
Owning app context (if any).
Definition http.h:176
cwist_sstring * body
Definition http.h:73
cwist_http_header_node * headers
Definition http.h:72
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * body
Definition http.h:87
cwist_http_status_t status_code
Definition http.h:84
cwist_http_header_node * headers
Definition http.h:86
uint32_t number
Definition protobuf.h:40
const uint8_t * bytes
Definition protobuf.h:43
cwist_pb_wire_type_t wire_type
Definition protobuf.h:41
Backing state for cwist_scratch_alloc()/CWIST_SCRATCH_DEFER: a single block-scoped allocation that pr...
Definition alloc.h:131
size_t size
Definition sstring.h:20
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
Core Application Structure and Lifecycle Management.
void cwist_app_post(cwist_app *app, const char *path, cwist_handler_func handler)
Register a POST handler with default endpoint options.
Definition app.c:2267
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...
Definition error.c:43