ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_NEED_PROTOBUF
12
13#include <string.h>
14
15void pc_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 pc_pb_write_varint(PbWriter *w, uint64_t v)
24{
25 if (w->error)
26 {
27 return false;
28 }
29 uint8_t tmp[10];
30 size_t n = 0;
31 do
32 {
33 uint8_t b = (uint8_t)(v & 0x7F); // low 7 bits of payload
34 v >>= 7;
35 if (v)
36 {
37 b |= 0x80; // high bit = "more bytes follow" (continuation)
38 }
39 tmp[n++] = b;
40 } while (v);
41 if (w->pos + n > w->cap)
42 {
43 w->error = true;
44 return false;
45 }
46 memcpy(w->buf + w->pos, tmp, n);
47 w->pos += n;
48 return true;
49}
50
51bool pc_pb_write_tag(PbWriter *w, uint32_t field, uint8_t wire_type)
52{
53 return pc_pb_write_varint(w, ((uint64_t)field << 3) | (wire_type & 0x07)); // tag = field<<3 | wire(low 3 bits)
54}
55
56// Append @p n raw little-endian octets of @p v.
57static bool pc_pb_write_le(PbWriter *w, uint64_t v, size_t n)
58{
59 // GCOVR_EXCL_START unreachable: write_le's only callers (fixed32/fixed64) invoke it as the
60 // right operand of `pc_pb_write_tag(...) && pc_pb_write_le(...)`; write_tag returns true only
61 // when its inner write_varint succeeded, which itself guarantees w->error is still false at
62 // that point. So w->error can never already be true on entry here.
63 if (w->error)
64 {
65 return false;
66 }
67 // GCOVR_EXCL_STOP
68 if (w->pos + n > w->cap)
69 {
70 w->error = true;
71 return false;
72 }
73 for (size_t i = 0; i < n; i++)
74 {
75 w->buf[w->pos++] = (uint8_t)(v >> (8 * i));
76 }
77 return true;
78}
79
80bool pc_pb_uint64(PbWriter *w, uint32_t field, uint64_t v)
81{
82 return pc_pb_write_tag(w, field, PB_WT_VARINT) && pc_pb_write_varint(w, v);
83}
84
85bool pc_pb_int64(PbWriter *w, uint32_t field, int64_t v)
86{
87 return pc_pb_uint64(w, field, (uint64_t)v); // two's complement; negatives take 10 bytes
88}
89
90bool pc_pb_sint64(PbWriter *w, uint32_t field, int64_t v)
91{
92 uint64_t zz = ((uint64_t)v << 1) ^ (uint64_t)(v >> 63); // ZigZag
93 return pc_pb_uint64(w, field, zz);
94}
95
96bool pc_pb_bool(PbWriter *w, uint32_t field, bool v)
97{
98 return pc_pb_uint64(w, field, v ? 1 : 0);
99}
100
101bool pc_pb_fixed32(PbWriter *w, uint32_t field, uint32_t v)
102{
103 return pc_pb_write_tag(w, field, PB_WT_I32) && pc_pb_write_le(w, v, 4);
104}
105
106bool pc_pb_fixed64(PbWriter *w, uint32_t field, uint64_t v)
107{
108 return pc_pb_write_tag(w, field, PB_WT_I64) && pc_pb_write_le(w, v, 8);
109}
110
111bool pc_pb_float(PbWriter *w, uint32_t field, float v)
112{
113 uint32_t bits;
114 memcpy(&bits, &v, 4);
115 return pc_pb_fixed32(w, field, bits);
116}
117
118bool pc_pb_double(PbWriter *w, uint32_t field, double v)
119{
120 uint64_t bits;
121 memcpy(&bits, &v, 8);
122 return pc_pb_fixed64(w, field, bits);
123}
124
125bool pc_pb_bytes(PbWriter *w, uint32_t field, const uint8_t *data, size_t len)
126{
127 if (!pc_pb_write_tag(w, field, PB_WT_LEN) || !pc_pb_write_varint(w, len))
128 {
129 return false;
130 }
131 // GCOVR_EXCL_START unreachable: both write_tag and write_varint above just returned true,
132 // which (by the same argument as pc_pb_write_le) guarantees w->error is false here.
133 if (w->error)
134 {
135 return false;
136 }
137 // GCOVR_EXCL_STOP
138 if (len == 0)
139 {
140 return true;
141 }
142 if (!data || w->pos + len > w->cap)
143 {
144 w->error = true;
145 return false;
146 }
147 memcpy(w->buf + w->pos, data, len);
148 w->pos += len;
149 return true;
150}
151
152bool pc_pb_string(PbWriter *w, uint32_t field, const char *s)
153{
154 if (!s)
155 {
156 w->error = true;
157 return false;
158 }
159 return pc_pb_bytes(w, field, (const uint8_t *)s, strnlen(s, w->cap + 1));
160}
161
162size_t pc_pb_writer_finish(PbWriter *w)
163{
164 return w->error ? 0 : w->pos;
165}
166
167bool pc_pb_read_varint(const uint8_t *buf, size_t len, size_t *pos, uint64_t *out)
168{
169 if (!buf || !pos || !out)
170 {
171 return false;
172 }
173 uint64_t v = 0;
174 size_t shift = 0;
175 size_t i = *pos;
176 for (size_t b = 0; b < 10; b++) // a 64-bit varint is at most 10 bytes
177 {
178 if (i >= len)
179 {
180 return false; // truncated
181 }
182 uint8_t c = buf[i++];
183 v |= (uint64_t)(c & 0x7F) << shift; // accumulate 7 payload bits, little-endian
184 if (!(c & 0x80)) // continuation bit clear -> last byte
185 {
186 *out = v;
187 *pos = i;
188 return true;
189 }
190 shift += 7;
191 }
192 return false; // overlong / unterminated
193}
194
195bool pc_pb_read_field(const uint8_t *buf, size_t len, size_t *pos, PbField *out)
196{
197 if (!buf || !pos || !out || *pos >= len)
198 {
199 return false;
200 }
201 uint64_t tag;
202 if (!pc_pb_read_varint(buf, len, pos, &tag))
203 {
204 return false;
205 }
206 out->field_number = (uint32_t)(tag >> 3); // tag high bits = field number
207 out->wire_type = (uint8_t)(tag & 0x07); // tag low 3 bits = wire type
208 out->value = 0;
209 out->data = nullptr;
210 out->len = 0;
211
212 switch (out->wire_type)
213 {
214 case PB_WT_VARINT:
215 return pc_pb_read_varint(buf, len, pos, &out->value);
216 case PB_WT_I64: {
217 if (*pos + 8 > len)
218 {
219 return false;
220 }
221 uint64_t v = 0;
222 for (size_t i = 0; i < 8; i++)
223 {
224 v |= (uint64_t)buf[*pos + i] << (8 * i);
225 }
226 *pos += 8;
227 out->value = v;
228 return true;
229 }
230 case PB_WT_I32: {
231 if (*pos + 4 > len)
232 {
233 return false;
234 }
235 uint32_t v = 0;
236 for (size_t i = 0; i < 4; i++)
237 {
238 v |= (uint32_t)buf[*pos + i] << (8 * i);
239 }
240 *pos += 4;
241 out->value = v;
242 return true;
243 }
244 case PB_WT_LEN: {
245 uint64_t l;
246 if (!pc_pb_read_varint(buf, len, pos, &l))
247 {
248 return false;
249 }
250 if (*pos + l > len)
251 {
252 return false; // payload not fully buffered
253 }
254 out->data = buf + *pos;
255 out->len = (size_t)l;
256 *pos += (size_t)l;
257 return true;
258 }
259 default:
260 return false; // groups (3/4) / reserved (6/7) are not supported
261 }
262}
263
264int64_t pc_pb_zigzag64(uint64_t v)
265{
266 return (int64_t)(v >> 1) ^ -(int64_t)(v & 1);
267}
268
269int32_t pc_pb_zigzag32(uint32_t v)
270{
271 return (int32_t)(v >> 1) ^ -(int32_t)(v & 1);
272}
273
274float pc_pb_float_bits(uint32_t bits)
275{
276 float f;
277 memcpy(&f, &bits, 4);
278 return f;
279}
280
281double pc_pb_double_bits(uint64_t bits)
282{
283 double d;
284 memcpy(&d, &bits, 8);
285 return d;
286}
287
288#endif // PC_NEED_PROTOBUF
Protocol Buffers wire codec (PC_ENABLE_PROTOBUF) - zero-heap streaming writer.