DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
protobuf.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 protobuf.cpp
6 * @brief Protocol Buffers wire codec (pure, host-tested against spec vectors).
7 */
8
10
11#if DETWS_ENABLE_PROTOBUF
12
13#include <string.h>
14
15void pb_writer_init(PbWriter *w, uint8_t *buf, size_t cap)
16{
17 w->buf = buf;
18 w->cap = cap;
19 w->pos = 0;
20 w->error = false;
21}
22
23bool pb_write_varint(PbWriter *w, uint64_t v)
24{
25 if (w->error)
26 return false;
27 uint8_t tmp[10];
28 size_t n = 0;
29 do
30 {
31 uint8_t b = (uint8_t)(v & 0x7F); // low 7 bits of payload
32 v >>= 7;
33 if (v)
34 b |= 0x80; // high bit = "more bytes follow" (continuation)
35 tmp[n++] = b;
36 } while (v);
37 if (w->pos + n > w->cap)
38 {
39 w->error = true;
40 return false;
41 }
42 memcpy(w->buf + w->pos, tmp, n);
43 w->pos += n;
44 return true;
45}
46
47bool pb_write_tag(PbWriter *w, uint32_t field, uint8_t wire_type)
48{
49 return pb_write_varint(w, ((uint64_t)field << 3) | (wire_type & 0x07)); // tag = field<<3 | wire(low 3 bits)
50}
51
52// Append @p n raw little-endian octets of @p v.
53static bool pb_write_le(PbWriter *w, uint64_t v, size_t n)
54{
55 if (w->error)
56 return false;
57 if (w->pos + n > w->cap)
58 {
59 w->error = true;
60 return false;
61 }
62 for (size_t i = 0; i < n; i++)
63 w->buf[w->pos++] = (uint8_t)(v >> (8 * i));
64 return true;
65}
66
67bool pb_uint64(PbWriter *w, uint32_t field, uint64_t v)
68{
69 return pb_write_tag(w, field, PB_WT_VARINT) && pb_write_varint(w, v);
70}
71
72bool pb_int64(PbWriter *w, uint32_t field, int64_t v)
73{
74 return pb_uint64(w, field, (uint64_t)v); // two's complement; negatives take 10 bytes
75}
76
77bool pb_sint64(PbWriter *w, uint32_t field, int64_t v)
78{
79 uint64_t zz = ((uint64_t)v << 1) ^ (uint64_t)(v >> 63); // ZigZag
80 return pb_uint64(w, field, zz);
81}
82
83bool pb_bool(PbWriter *w, uint32_t field, bool v)
84{
85 return pb_uint64(w, field, v ? 1 : 0);
86}
87
88bool pb_fixed32(PbWriter *w, uint32_t field, uint32_t v)
89{
90 return pb_write_tag(w, field, PB_WT_I32) && pb_write_le(w, v, 4);
91}
92
93bool pb_fixed64(PbWriter *w, uint32_t field, uint64_t v)
94{
95 return pb_write_tag(w, field, PB_WT_I64) && pb_write_le(w, v, 8);
96}
97
98bool pb_float(PbWriter *w, uint32_t field, float v)
99{
100 uint32_t bits;
101 memcpy(&bits, &v, 4);
102 return pb_fixed32(w, field, bits);
103}
104
105bool pb_double(PbWriter *w, uint32_t field, double v)
106{
107 uint64_t bits;
108 memcpy(&bits, &v, 8);
109 return pb_fixed64(w, field, bits);
110}
111
112bool pb_bytes(PbWriter *w, uint32_t field, const uint8_t *data, size_t len)
113{
114 if (!pb_write_tag(w, field, PB_WT_LEN) || !pb_write_varint(w, len))
115 return false;
116 if (w->error)
117 return false;
118 if (len == 0)
119 return true;
120 if (!data || w->pos + len > w->cap)
121 {
122 w->error = true;
123 return false;
124 }
125 memcpy(w->buf + w->pos, data, len);
126 w->pos += len;
127 return true;
128}
129
130bool pb_string(PbWriter *w, uint32_t field, const char *s)
131{
132 if (!s)
133 {
134 w->error = true;
135 return false;
136 }
137 return pb_bytes(w, field, (const uint8_t *)s, strnlen(s, w->cap + 1));
138}
139
140size_t pb_writer_finish(PbWriter *w)
141{
142 return w->error ? 0 : w->pos;
143}
144
145bool pb_read_varint(const uint8_t *buf, size_t len, size_t *pos, uint64_t *out)
146{
147 if (!buf || !pos || !out)
148 return false;
149 uint64_t v = 0;
150 size_t shift = 0;
151 size_t i = *pos;
152 for (size_t b = 0; b < 10; b++) // a 64-bit varint is at most 10 bytes
153 {
154 if (i >= len)
155 return false; // truncated
156 uint8_t c = buf[i++];
157 v |= (uint64_t)(c & 0x7F) << shift; // accumulate 7 payload bits, little-endian
158 if (!(c & 0x80)) // continuation bit clear -> last byte
159 {
160 *out = v;
161 *pos = i;
162 return true;
163 }
164 shift += 7;
165 }
166 return false; // overlong / unterminated
167}
168
169bool pb_read_field(const uint8_t *buf, size_t len, size_t *pos, PbField *out)
170{
171 if (!buf || !pos || !out || *pos >= len)
172 return false;
173 uint64_t tag;
174 if (!pb_read_varint(buf, len, pos, &tag))
175 return false;
176 out->field_number = (uint32_t)(tag >> 3); // tag high bits = field number
177 out->wire_type = (uint8_t)(tag & 0x07); // tag low 3 bits = wire type
178 out->value = 0;
179 out->data = nullptr;
180 out->len = 0;
181
182 switch (out->wire_type)
183 {
184 case PB_WT_VARINT:
185 return pb_read_varint(buf, len, pos, &out->value);
186 case PB_WT_I64: {
187 if (*pos + 8 > len)
188 return false;
189 uint64_t v = 0;
190 for (size_t i = 0; i < 8; i++)
191 v |= (uint64_t)buf[*pos + i] << (8 * i);
192 *pos += 8;
193 out->value = v;
194 return true;
195 }
196 case PB_WT_I32: {
197 if (*pos + 4 > len)
198 return false;
199 uint32_t v = 0;
200 for (size_t i = 0; i < 4; i++)
201 v |= (uint32_t)buf[*pos + i] << (8 * i);
202 *pos += 4;
203 out->value = v;
204 return true;
205 }
206 case PB_WT_LEN: {
207 uint64_t l;
208 if (!pb_read_varint(buf, len, pos, &l))
209 return false;
210 if (*pos + l > len)
211 return false; // payload not fully buffered
212 out->data = buf + *pos;
213 out->len = (size_t)l;
214 *pos += (size_t)l;
215 return true;
216 }
217 default:
218 return false; // groups (3/4) / reserved (6/7) are not supported
219 }
220}
221
222int64_t pb_zigzag64(uint64_t v)
223{
224 return (int64_t)(v >> 1) ^ -(int64_t)(v & 1);
225}
226
227int32_t pb_zigzag32(uint32_t v)
228{
229 return (int32_t)(v >> 1) ^ -(int32_t)(v & 1);
230}
231
232float pb_float_bits(uint32_t bits)
233{
234 float f;
235 memcpy(&f, &bits, 4);
236 return f;
237}
238
239double pb_double_bits(uint64_t bits)
240{
241 double d;
242 memcpy(&d, &bits, 8);
243 return d;
244}
245
246#endif // DETWS_ENABLE_PROTOBUF
Protocol Buffers wire codec (DETWS_ENABLE_PROTOBUF) - zero-heap streaming writer.