ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
grpcweb.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file grpcweb.h
6 * @brief gRPC-Web message framing (PROTOCORE_ENABLE_GRPC_WEB): the zero-heap length-prefixed
7 * message builder and parser.
8 *
9 * THE GOVERNING STANDARD IS NOT AN IETF RFC. gRPC-Web is specified by the gRPC project (CNCF) in
10 * `grpc/grpc doc/PROTOCOL-WEB.md`, layered on the gRPC over HTTP/2 protocol in
11 * `grpc/grpc doc/PROTOCOL-HTTP2.md`. Both are cited below by document and section name. The
12 * transport underneath is HTTP, which IS IETF: RFC 9110 and RFC 9112.
13 *
14 * PROTOCOL-HTTP2.md "Requests" gives the frame every call here builds or reads:
15 * @code
16 * Length-Prefixed-Message -> Compressed-Flag Message-Length Message
17 * Compressed-Flag -> 0 / 1 ; encoded as 1 byte unsigned integer
18 * Message-Length -> {length of Message} ; 4 byte unsigned integer (big endian)
19 * Message -> *{binary octet}
20 * @endcode
21 *
22 * PROTOCOL-WEB.md "Protocol differences vs gRPC over HTTP2", under "Message framing", reads the
23 * 8th (MSB) bit of the 1st gRPC frame byte as the frame type, 0 for data and 1 for trailers, so
24 * `10000000b` is an uncompressed trailer and `10000001b` a compressed one. That trailers frame
25 * carries the response status in the body as "Key-value pairs encoded as a HTTP/1 headers block
26 * (without the terminating newline)", which is RFC 9112 sec 7.1.2
27 * `trailer-section = *( field-line CRLF )` over the RFC 9112 sec 5 `field-line = field-name ":"
28 * OWS field-value OWS`. PROTOCOL-WEB.md "HTTP wire protocols" item 2 requires those names to be
29 * lower-case in the last length-prefixed message, and PROTOCOL-HTTP2.md "Responses" names them:
30 * @code
31 * Trailers -> Status [Status-Message] [Status-Details] *Custom-Metadata
32 * Status -> "grpc-status" 1*DIGIT
33 * Status-Message -> "grpc-message" Percent-Encoded
34 * @endcode
35 * Trailers are the last message of a response, after zero or more data frames.
36 *
37 * A Message is an encoded Protobuf message: services/iot/protobuf builds it, this frames it. The
38 * media type a response carries it under is `application/grpc-web+proto` (RFC 9110 sec 8.3),
39 * over the shipped HTTP/1.1 server and client.
40 *
41 * The module exports one symbol, @ref GrpcWeb. Everything in grpcweb.c has internal linkage.
42 *
43 * @author Douglas Quigg (dstroy0)
44 * @date 2026
45 */
46
47#ifndef PROTOCORE_GRPCWEB_H
48#define PROTOCORE_GRPCWEB_H
49
50#include "protocore_config.h" // the entry point: protocore_types.h for the widths
51
52#if PROTOCORE_ENABLE_GRPC_WEB
53
55
56/** @brief Compressed-Flag set in the frame byte: the Message uses the Message-Encoding. */
57#define PROTOCORE_GRPCWEB_COMPRESSED 0x01
58/** @brief The MSB of the frame byte: the body is a trailer-section, not a Message. */
59#define PROTOCORE_GRPCWEB_TRAILERS 0x80
60/** @brief Compressed-Flag plus Message-Length: the octets ahead of every Message. */
61#define PROTOCORE_GRPCWEB_PREFIX_LEN 5
62
63/** @brief One decoded Length-Prefixed-Message. @c body points INTO the parsed buffer. */
64typedef struct
65{
66 uint8_t flags; ///< the 1st gRPC frame byte as it arrived
67 proto_bool compressed; ///< its Compressed-Flag, bit 0
68 proto_bool trailers; ///< its 8th (MSB) bit: the body is a trailer-section
69 const uint8_t *body; ///< the Message, or the trailer-section when @c trailers is set
70 size_t body_len; ///< Message-Length, the octets @c body spans
71} GrpcWebFrame;
72
73/** @brief Where a builder writes the Length-Prefixed-Message it assembles. */
74typedef struct
75{
76 uint8_t *buf; ///< the octets a builder writes into
77 size_t cap; ///< how many octets it may write
78} GrpcWebOutArgs;
79
80/** @brief The frame byte and the Message a data frame carries (PROTOCOL-HTTP2.md "Requests"). */
81typedef struct
82{
83 const uint8_t *body; ///< Message, the octets Message-Length measures
84 size_t body_len; ///< Message-Length, capped at what a 4-byte big-endian field expresses
85 uint8_t flags; ///< the whole 1st gRPC frame byte, when a caller sets it outright
86 proto_bool compressed; ///< Compressed-Flag, the bit a frame_message sets in @c flags
87} GrpcWebMessageArgs;
88
89/** @brief The trailer-section a trailers frame carries (PROTOCOL-HTTP2.md "Responses"). */
90typedef struct
91{
92 int32_t status; ///< Status, the "grpc-status" value as 1*DIGIT
93 const char *message; ///< Status-Message, the "grpc-message" value; NULL or "" omits the line
94} GrpcWebTrailersArgs;
95
96/** @brief The octets a parse decodes, or a Trailers read scans. */
97typedef struct
98{
99 const uint8_t *data; ///< a frame stream, or one decoded trailer-section
100 size_t len; ///< how many octets are buffered there
101} GrpcWebInArgs;
102
103/**
104 * @brief The gRPC-Web framing codec.
105 *
106 * A caller sets the members a call takes, invokes it through ::GrpcWeb, and reads the outcome off
107 * the same handle.
108 *
109 * No slot member: the codec owns no rows, so no call names one.
110 *
111 * @var GrpcWebNs::out where a builder writes the frame
112 * @var GrpcWebNs::msg the frame byte and the Message a data frame carries
113 * @var GrpcWebNs::trailers the Status and Status-Message a trailers frame carries
114 * @var GrpcWebNs::in the octets a parse decodes or a Trailers read scans
115 * @var GrpcWebNs::ok a call's true/false outcome
116 * @var GrpcWebNs::n octets a builder wrote, or a parse consumed; 0 when a call failed
117 * @var GrpcWebNs::parsed the Length-Prefixed-Message a parse decoded
118 * @var GrpcWebNs::i32 the Status a trailers_status read
119 * @var GrpcWebNs::text the Status-Message slice, pointing into @c in.data
120 * @var GrpcWebNs::text_len its length in octets
121 * @var GrpcWebNs::frame frame @c msg.body under the frame byte in @c msg.flags
122 * @var GrpcWebNs::frame_message frame @c msg.body with @c msg.compressed as the Compressed-Flag
123 * @var GrpcWebNs::frame_trailers frame @c trailers as a trailer-section under the MSB
124 * @var GrpcWebNs::parse decode the frame at the head of @c in
125 * @var GrpcWebNs::trailers_status read Status ("grpc-status") out of the trailer-section in @c in
126 * @var GrpcWebNs::trailers_message read Status-Message ("grpc-message") out of that same section
127 */
128typedef struct
129{
130 GrpcWebOutArgs out; ///< where a builder writes
131 GrpcWebMessageArgs msg; ///< what a data frame carries
132 GrpcWebTrailersArgs trailers; ///< what a trailers frame carries
133 GrpcWebInArgs in; ///< what a read consumes
134 proto_bool ok;
135 size_t n;
136 GrpcWebFrame parsed;
137 int32_t i32;
138 const char *text;
139 size_t text_len;
140} GrpcWebVars;
141
142/** @brief The operands and the outcome. */
143extern GrpcWebVars GrpcWebV;
144
145/** @brief The entries. */
146typedef struct
147{
148 void (*const frame)(uint8_t *work);
149 void (*const frame_message)(uint8_t *work);
150 void (*const frame_trailers)(uint8_t *work);
151 void (*const parse)(uint8_t *work);
152 void (*const trailers_status)(uint8_t *work);
153 void (*const trailers_message)(uint8_t *work);
154} GrpcWebNs;
155
156// What the table binds, defined once in the .c and taking one parameter each: everything
157// else an entry needs is an operand in GrpcWebV or a region of the borrow at a fixed offset.
158void protocore_grpc_web_frame(uint8_t *work);
159void protocore_grpc_web_frame_message(uint8_t *work);
160void protocore_grpc_web_frame_trailers(uint8_t *work);
161void protocore_grpc_web_parse(uint8_t *work);
162void protocore_grpc_web_trailers_status(uint8_t *work);
163void protocore_grpc_web_trailers_message(uint8_t *work);
164
165// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
166// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
167// `GrpcWeb.frame(work)` resolves to a named function and becomes a DIRECT call. An extern table
168// leaves the call indirect and the symbol live at every level, -O2 -flto included.
169static const GrpcWebNs GrpcWeb __attribute__((unused)) = {
170 .frame = protocore_grpc_web_frame,
171 .frame_message = protocore_grpc_web_frame_message,
172 .frame_trailers = protocore_grpc_web_frame_trailers,
173 .parse = protocore_grpc_web_parse,
174 .trailers_status = protocore_grpc_web_trailers_status,
175 .trailers_message = protocore_grpc_web_trailers_message,
176};
177
179
180#endif // PROTOCORE_ENABLE_GRPC_WEB
181
182#endif // PROTOCORE_GRPCWEB_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97