ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
grpcweb.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file grpcweb.cpp
6 * @brief gRPC-Web message framing builder + parser (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_GRPC_WEB
12
13#include <string.h>
14
15size_t pc_grpcweb_frame(uint8_t *buf, size_t cap, uint8_t flags, const uint8_t *body, size_t body_len)
16{
17 if (!buf || (body_len && !body) || body_len > 0xFFFFFFFFu)
18 {
19 return 0;
20 }
21 size_t total = GRPCWEB_PREFIX_LEN + body_len;
22 if (total > cap)
23 {
24 return 0;
25 }
26 buf[0] = flags;
27 buf[1] = (uint8_t)(body_len >> 24);
28 buf[2] = (uint8_t)(body_len >> 16);
29 buf[3] = (uint8_t)(body_len >> 8);
30 buf[4] = (uint8_t)(body_len);
31 if (body_len)
32 {
33 memcpy(buf + GRPCWEB_PREFIX_LEN, body, body_len);
34 }
35 return total;
36}
37
38size_t pc_grpcweb_frame_message(uint8_t *buf, size_t cap, const uint8_t *msg, size_t msg_len, bool compressed)
39{
40 return pc_grpcweb_frame(buf, cap, compressed ? GRPCWEB_FLAG_COMPRESSED : 0, msg, msg_len);
41}
42
43// Append a NUL-terminated string at *pos with bounds check; advance *pos. False on overflow.
44static bool put_str(uint8_t *buf, size_t cap, size_t *pos, const char *s)
45{
46 size_t n = strnlen(s, cap + 1);
47 if (*pos + n > cap)
48 {
49 return false;
50 }
51 memcpy(buf + *pos, s, n);
52 *pos += n;
53 return true;
54}
55
56// Append a non-negative integer as decimal at *pos. False on overflow.
57static bool put_int(uint8_t *buf, size_t cap, size_t *pos, int v)
58{
59 if (v < 0)
60 {
61 return false;
62 }
63 char tmp[12];
64 size_t n = 0;
65 char rev[12];
66 size_t r = 0;
67 if (v == 0)
68 {
69 rev[r++] = '0';
70 }
71 while (v)
72 {
73 rev[r++] = (char)('0' + (v % 10));
74 v /= 10;
75 }
76 while (r)
77 {
78 tmp[n++] = rev[--r];
79 }
80 if (*pos + n > cap)
81 {
82 return false;
83 }
84 memcpy(buf + *pos, tmp, n);
85 *pos += n;
86 return true;
87}
88
89size_t pc_grpcweb_frame_trailer(uint8_t *buf, size_t cap, int status, const char *message)
90{
91 if (!buf || cap < GRPCWEB_PREFIX_LEN)
92 {
93 return 0;
94 }
95 size_t pos = GRPCWEB_PREFIX_LEN; // reserve the prefix; patch the length after the body
96 if (!put_str(buf, cap, &pos, "grpc-status:") || !put_int(buf, cap, &pos, status) ||
97 !put_str(buf, cap, &pos, "\r\n"))
98 {
99 return 0;
100 }
101 if (message && *message)
102 {
103 if (!put_str(buf, cap, &pos, "grpc-message:") || !put_str(buf, cap, &pos, message) ||
104 !put_str(buf, cap, &pos, "\r\n"))
105 {
106 return 0;
107 }
108 }
109 size_t body_len = pos - GRPCWEB_PREFIX_LEN;
110 buf[0] = GRPCWEB_FLAG_TRAILER;
111 buf[1] = (uint8_t)(body_len >> 24);
112 buf[2] = (uint8_t)(body_len >> 16);
113 buf[3] = (uint8_t)(body_len >> 8);
114 buf[4] = (uint8_t)(body_len);
115 return pos;
116}
117
118bool pc_grpcweb_parse(const uint8_t *buf, size_t len, GrpcWebFrame *out, size_t *consumed)
119{
120 if (!buf || !out || !consumed || len < GRPCWEB_PREFIX_LEN)
121 {
122 return false;
123 }
124 uint32_t body_len = ((uint32_t)buf[1] << 24) | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 8) | buf[4];
125 if ((size_t)GRPCWEB_PREFIX_LEN + body_len > len)
126 {
127 return false; // frame not fully buffered
128 }
129 out->flags = buf[0];
130 out->compressed = (buf[0] & GRPCWEB_FLAG_COMPRESSED) != 0;
131 out->trailer = (buf[0] & GRPCWEB_FLAG_TRAILER) != 0;
132 out->body = buf + GRPCWEB_PREFIX_LEN;
133 out->body_len = body_len;
134 *consumed = GRPCWEB_PREFIX_LEN + body_len;
135 return true;
136}
137
138bool pc_grpcweb_trailer_status(const uint8_t *body, size_t len, int *status)
139{
140 if (!body)
141 {
142 return false;
143 }
144 static const char key[] = "grpc-status:";
145 const size_t klen = sizeof(key) - 1;
146 for (size_t i = 0; i + klen <= len; i++)
147 {
148 // Match at the start of a line (i == 0 or preceded by '\n').
149 if ((i == 0 || body[i - 1] == '\n') && memcmp(body + i, key, klen) == 0)
150 {
151 size_t j = i + klen;
152 if (j >= len || body[j] < '0' || body[j] > '9')
153 {
154 return false;
155 }
156 int v = 0;
157 for (; j < len && body[j] >= '0' && body[j] <= '9'; j++)
158 {
159 v = v * 10 + (body[j] - '0');
160 }
161 if (status)
162 {
163 *status = v;
164 }
165 return true;
166 }
167 }
168 return false;
169}
170
171bool pc_grpcweb_trailer_message(const uint8_t *body, size_t len, const char **msg, size_t *msg_len)
172{
173 if (!body)
174 {
175 return false;
176 }
177 static const char key[] = "grpc-message:";
178 const size_t klen = sizeof(key) - 1;
179 for (size_t i = 0; i + klen <= len; i++)
180 {
181 // Match at the start of a line (i == 0 or preceded by '\n').
182 if ((i == 0 || body[i - 1] == '\n') && memcmp(body + i, key, klen) == 0)
183 {
184 size_t start = i + klen;
185 size_t j = start;
186 while (j < len && body[j] != '\r' && body[j] != '\n') // the value runs to end-of-line
187 {
188 j++;
189 }
190 if (msg)
191 {
192 *msg = (const char *)(body + start);
193 }
194 if (msg_len)
195 {
196 *msg_len = j - start;
197 }
198 return true;
199 }
200 }
201 return false;
202}
203
204#endif // PC_ENABLE_GRPC_WEB
gRPC-Web message framing (PC_ENABLE_GRPC_WEB) - zero-heap length-prefixed frame builder + parser,...