Line | Count | Source |
1 | | /* pb_encode.c -- encode a protobuf using minimal resources |
2 | | * |
3 | | * 2011 Petteri Aimonen <jpa@kapsi.fi> |
4 | | */ |
5 | | |
6 | | #include "pb.h" |
7 | | #include "pb_encode.h" |
8 | | #include "pb_common.h" |
9 | | |
10 | | /* Use the GCC warn_unused_result attribute to check that all return values |
11 | | * are propagated correctly. On other compilers, gcc before 3.4.0 and iar |
12 | | * before 9.40.1 just ignore the annotation. |
13 | | */ |
14 | | #if (defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) || \ |
15 | | (defined(__IAR_SYSTEMS_ICC__) && (__VER__ >= 9040001)) |
16 | | #define checkreturn __attribute__((warn_unused_result)) |
17 | | #else |
18 | | #define checkreturn |
19 | | #endif |
20 | | |
21 | | /************************************** |
22 | | * Declarations internal to this file * |
23 | | **************************************/ |
24 | | static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); |
25 | | static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field); |
26 | | static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field); |
27 | | static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field); |
28 | | static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field); |
29 | | static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field); |
30 | | static pb_noinline bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field); |
31 | | static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); |
32 | | static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high); |
33 | | static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field); |
34 | | static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field); |
35 | | static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field); |
36 | | static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); |
37 | | static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field); |
38 | | static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field); |
39 | | static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); |
40 | | |
41 | | #ifdef PB_WITHOUT_64BIT |
42 | | #define pb_int64_t int32_t |
43 | | #define pb_uint64_t uint32_t |
44 | | #else |
45 | 17.0k | #define pb_int64_t int64_t |
46 | 40.8k | #define pb_uint64_t uint64_t |
47 | | #endif |
48 | | |
49 | | /******************************* |
50 | | * pb_ostream_t implementation * |
51 | | *******************************/ |
52 | | |
53 | | static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) |
54 | 56.5k | { |
55 | 56.5k | pb_byte_t *dest = (pb_byte_t*)stream->state; |
56 | 56.5k | stream->state = dest + count; |
57 | | |
58 | | /* Skip the copy if buf is NULL. Callers should not invoke this with NULL, |
59 | | * but pb_write may pass NULL for sizing passes. Some compilers |
60 | | * (e.g. picolibc/arm-zephyr-eabi GCC 12.2) emit a -Wnonnull warning |
61 | | * against memcpy's nonnull argument even though count would be 0 in |
62 | | * that case. See #1141. |
63 | | */ |
64 | 56.5k | if (buf != NULL) |
65 | 56.5k | { |
66 | 56.5k | memcpy(dest, buf, count * sizeof(pb_byte_t)); |
67 | 56.5k | } |
68 | | |
69 | 56.5k | return true; |
70 | 56.5k | } |
71 | | |
72 | | pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) |
73 | 2.85k | { |
74 | 2.85k | pb_ostream_t stream; |
75 | | #ifdef PB_BUFFER_ONLY |
76 | | /* In PB_BUFFER_ONLY configuration the callback pointer is just int*. |
77 | | * NULL pointer marks a sizing field, so put a non-NULL value to mark a buffer stream. |
78 | | */ |
79 | | static const int marker = 0; |
80 | | stream.callback = ▮ |
81 | | #else |
82 | 2.85k | stream.callback = &buf_write; |
83 | 2.85k | #endif |
84 | 2.85k | stream.state = buf; |
85 | 2.85k | stream.max_size = bufsize; |
86 | 2.85k | stream.bytes_written = 0; |
87 | 2.85k | #ifndef PB_NO_ERRMSG |
88 | 2.85k | stream.errmsg = NULL; |
89 | 2.85k | #endif |
90 | 2.85k | return stream; |
91 | 2.85k | } |
92 | | |
93 | | bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) |
94 | 71.4k | { |
95 | 71.4k | if (count > 0 && stream->callback != NULL) |
96 | 56.5k | { |
97 | 56.5k | if (stream->bytes_written + count < stream->bytes_written || |
98 | 56.5k | stream->bytes_written + count > stream->max_size) |
99 | 0 | { |
100 | 0 | PB_RETURN_ERROR(stream, "stream full"); |
101 | 0 | } |
102 | | |
103 | | #ifdef PB_BUFFER_ONLY |
104 | | if (!buf_write(stream, buf, count)) |
105 | | PB_RETURN_ERROR(stream, "io error"); |
106 | | #else |
107 | 56.5k | if (!stream->callback(stream, buf, count)) |
108 | 0 | PB_RETURN_ERROR(stream, "io error"); |
109 | 56.5k | #endif |
110 | 56.5k | } |
111 | | |
112 | 71.4k | stream->bytes_written += count; |
113 | 71.4k | return true; |
114 | 71.4k | } |
115 | | |
116 | | /************************* |
117 | | * Encode a single field * |
118 | | *************************/ |
119 | | |
120 | | /* Read a bool value without causing undefined behavior even if the value |
121 | | * is invalid. See issue #434 and |
122 | | * https://stackoverflow.com/questions/27661768/weird-results-for-conditional |
123 | | */ |
124 | | static bool safe_read_bool(const void *pSize) |
125 | 6.20k | { |
126 | 6.20k | const char *p = (const char *)pSize; |
127 | 6.20k | size_t i; |
128 | 11.5k | for (i = 0; i < sizeof(bool); i++) |
129 | 6.20k | { |
130 | 6.20k | if (p[i] != 0) |
131 | 828 | return true; |
132 | 6.20k | } |
133 | 5.38k | return false; |
134 | 6.20k | } |
135 | | |
136 | | /* Encode a static array. Handles the size calculations and possible packing. */ |
137 | | static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field) |
138 | 54.3k | { |
139 | 54.3k | pb_size_t i; |
140 | 54.3k | pb_size_t count; |
141 | 54.3k | #ifndef PB_ENCODE_ARRAYS_UNPACKED |
142 | 54.3k | size_t size; |
143 | 54.3k | #endif |
144 | | |
145 | 54.3k | count = *(pb_size_t*)field->pSize; |
146 | | |
147 | 54.3k | if (count == 0) |
148 | 50.7k | return true; |
149 | | |
150 | 3.59k | if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) |
151 | 0 | PB_RETURN_ERROR(stream, "array max size exceeded"); |
152 | | |
153 | 3.59k | #ifndef PB_ENCODE_ARRAYS_UNPACKED |
154 | | /* We always pack arrays if the datatype allows it. */ |
155 | 3.59k | if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) |
156 | 2.23k | { |
157 | 2.23k | if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) |
158 | 0 | return false; |
159 | | |
160 | | /* Determine the total size of packed array. */ |
161 | 2.23k | if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) |
162 | 332 | { |
163 | 332 | size = 4 * (size_t)count; |
164 | 332 | } |
165 | 1.90k | else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) |
166 | 254 | { |
167 | 254 | size = 8 * (size_t)count; |
168 | 254 | } |
169 | 1.65k | else |
170 | 1.65k | { |
171 | 1.65k | pb_ostream_t sizestream = PB_OSTREAM_SIZING; |
172 | 1.65k | void *pData_orig = field->pData; |
173 | 9.84k | for (i = 0; i < count; i++) |
174 | 8.19k | { |
175 | 8.19k | if (!pb_enc_varint(&sizestream, field)) |
176 | 0 | PB_RETURN_ERROR(stream, PB_GET_ERROR(&sizestream)); |
177 | 8.19k | field->pData = (char*)field->pData + field->data_size; |
178 | 8.19k | } |
179 | 1.65k | field->pData = pData_orig; |
180 | 1.65k | size = sizestream.bytes_written; |
181 | 1.65k | } |
182 | | |
183 | 2.23k | if (!pb_encode_varint(stream, (pb_uint64_t)size)) |
184 | 0 | return false; |
185 | | |
186 | 2.23k | if (stream->callback == NULL) |
187 | 0 | return pb_write(stream, NULL, size); /* Just sizing.. */ |
188 | | |
189 | | /* Write the data */ |
190 | 13.2k | for (i = 0; i < count; i++) |
191 | 10.9k | { |
192 | 10.9k | if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32 || PB_LTYPE(field->type) == PB_LTYPE_FIXED64) |
193 | 2.78k | { |
194 | 2.78k | if (!pb_enc_fixed(stream, field)) |
195 | 0 | return false; |
196 | 2.78k | } |
197 | 8.19k | else |
198 | 8.19k | { |
199 | 8.19k | if (!pb_enc_varint(stream, field)) |
200 | 0 | return false; |
201 | 8.19k | } |
202 | | |
203 | 10.9k | field->pData = (char*)field->pData + field->data_size; |
204 | 10.9k | } |
205 | 2.23k | } |
206 | 1.36k | else /* Unpacked fields */ |
207 | 1.36k | #endif |
208 | 1.36k | { |
209 | 6.49k | for (i = 0; i < count; i++) |
210 | 5.12k | { |
211 | | /* Normally the data is stored directly in the array entries, but |
212 | | * for pointer-type string and bytes fields, the array entries are |
213 | | * actually pointers themselves also. So we have to dereference once |
214 | | * more to get to the actual data. */ |
215 | 5.12k | if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && |
216 | 0 | (PB_LTYPE(field->type) == PB_LTYPE_STRING || |
217 | 0 | PB_LTYPE(field->type) == PB_LTYPE_BYTES)) |
218 | 0 | { |
219 | 0 | bool status; |
220 | 0 | void *pData_orig = field->pData; |
221 | 0 | field->pData = *(void* const*)field->pData; |
222 | |
|
223 | 0 | if (!field->pData) |
224 | 0 | { |
225 | | /* Null pointer in array is treated as empty string / bytes */ |
226 | 0 | status = pb_encode_tag_for_field(stream, field) && |
227 | 0 | pb_encode_varint(stream, 0); |
228 | 0 | } |
229 | 0 | else |
230 | 0 | { |
231 | 0 | status = encode_basic_field(stream, field); |
232 | 0 | } |
233 | |
|
234 | 0 | field->pData = pData_orig; |
235 | |
|
236 | 0 | if (!status) |
237 | 0 | return false; |
238 | 0 | } |
239 | 5.12k | else |
240 | 5.12k | { |
241 | 5.12k | if (!encode_basic_field(stream, field)) |
242 | 0 | return false; |
243 | 5.12k | } |
244 | 5.12k | field->pData = (char*)field->pData + field->data_size; |
245 | 5.12k | } |
246 | 1.36k | } |
247 | | |
248 | 3.59k | return true; |
249 | 3.59k | } |
250 | | |
251 | | /* In proto3, all fields are optional and are only encoded if their value is "non-zero". |
252 | | * This function implements the check for the zero value. */ |
253 | | static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field) |
254 | 97.4k | { |
255 | 97.4k | pb_type_t type = field->type; |
256 | | |
257 | 97.4k | if (PB_ATYPE(type) == PB_ATYPE_STATIC) |
258 | 97.4k | { |
259 | 97.4k | if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) |
260 | 0 | { |
261 | | /* Required proto2 fields inside proto3 submessage, pretty rare case */ |
262 | 0 | return false; |
263 | 0 | } |
264 | 97.4k | else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) |
265 | 0 | { |
266 | | /* Repeated fields inside proto3 submessage: present if count != 0 */ |
267 | 0 | return *(const pb_size_t*)field->pSize == 0; |
268 | 0 | } |
269 | 97.4k | else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) |
270 | 0 | { |
271 | | /* Oneof fields */ |
272 | 0 | return *(const pb_size_t*)field->pSize == 0; |
273 | 0 | } |
274 | 97.4k | else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) |
275 | 0 | { |
276 | | /* Proto2 optional fields inside proto3 message, or proto3 |
277 | | * submessage fields. */ |
278 | 0 | return safe_read_bool(field->pSize) == false; |
279 | 0 | } |
280 | 97.4k | else if (field->descriptor->default_value) |
281 | 0 | { |
282 | | /* Proto3 messages do not have default values, but proto2 messages |
283 | | * can contain optional fields without has_fields (generator option 'proto3'). |
284 | | * In this case they must always be encoded, to make sure that the |
285 | | * non-zero default value is overwritten. |
286 | | */ |
287 | 0 | return false; |
288 | 0 | } |
289 | | |
290 | | /* Rest is proto3 singular fields */ |
291 | 97.4k | if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) |
292 | 82.1k | { |
293 | | /* Simple integer / float fields */ |
294 | 82.1k | pb_size_t i; |
295 | 82.1k | const char *p = (const char*)field->pData; |
296 | 471k | for (i = 0; i < field->data_size; i++) |
297 | 398k | { |
298 | 398k | if (p[i] != 0) |
299 | 9.85k | { |
300 | 9.85k | return false; |
301 | 9.85k | } |
302 | 398k | } |
303 | | |
304 | 72.3k | return true; |
305 | 82.1k | } |
306 | 15.2k | else if (PB_LTYPE(type) == PB_LTYPE_BYTES) |
307 | 2.85k | { |
308 | 2.85k | const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData; |
309 | 2.85k | return bytes->size == 0; |
310 | 2.85k | } |
311 | 12.3k | else if (PB_LTYPE(type) == PB_LTYPE_STRING) |
312 | 6.67k | { |
313 | 6.67k | return *(const char*)field->pData == '\0'; |
314 | 6.67k | } |
315 | 5.71k | else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) |
316 | 2.85k | { |
317 | | /* Fixed length bytes is only empty if its length is fixed |
318 | | * as 0. Which would be pretty strange, but we can check |
319 | | * it anyway. */ |
320 | 2.85k | return field->data_size == 0; |
321 | 2.85k | } |
322 | 2.85k | else if (PB_LTYPE_IS_SUBMSG(type)) |
323 | 2.85k | { |
324 | | /* Check all fields in the submessage to find if any of them |
325 | | * are non-zero. The comparison cannot be done byte-per-byte |
326 | | * because the C struct may contain padding bytes that must |
327 | | * be skipped. Note that usually proto3 submessages have |
328 | | * a separate has_field that is checked earlier in this if. |
329 | | */ |
330 | 2.85k | pb_field_iter_t iter; |
331 | 2.85k | if (pb_field_iter_begin(&iter, field->submsg_desc, field->pData)) |
332 | 2.85k | { |
333 | 2.85k | do |
334 | 26.5k | { |
335 | 26.5k | if (!pb_check_proto3_default_value(&iter)) |
336 | 254 | { |
337 | 254 | return false; |
338 | 254 | } |
339 | 26.5k | } while (pb_field_iter_next(&iter)); |
340 | 2.85k | } |
341 | 2.60k | return true; |
342 | 2.85k | } |
343 | 97.4k | } |
344 | 0 | else if (PB_ATYPE(type) == PB_ATYPE_POINTER) |
345 | 0 | { |
346 | 0 | return field->pData == NULL; |
347 | 0 | } |
348 | 0 | else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) |
349 | 0 | { |
350 | 0 | if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) |
351 | 0 | { |
352 | 0 | const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; |
353 | 0 | return extension == NULL; |
354 | 0 | } |
355 | 0 | else if (field->descriptor->field_callback == pb_default_field_callback) |
356 | 0 | { |
357 | 0 | pb_callback_t *pCallback = (pb_callback_t*)field->pData; |
358 | 0 | return pCallback->funcs.encode == NULL; |
359 | 0 | } |
360 | 0 | else |
361 | 0 | { |
362 | 0 | return field->descriptor->field_callback == NULL; |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | 0 | return false; /* Not typically reached, safe default for weird special cases. */ |
367 | 97.4k | } |
368 | | |
369 | | /* Encode a field with static or pointer allocation, i.e. one whose data |
370 | | * is available to the encoder directly. */ |
371 | | static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field) |
372 | 20.1k | { |
373 | 20.1k | if (!field->pData) |
374 | 0 | { |
375 | | /* Missing pointer field */ |
376 | 0 | return true; |
377 | 0 | } |
378 | | |
379 | 20.1k | if (!pb_encode_tag_for_field(stream, field)) |
380 | 0 | return false; |
381 | | |
382 | 20.1k | switch (PB_LTYPE(field->type)) |
383 | 20.1k | { |
384 | 492 | case PB_LTYPE_BOOL: |
385 | 492 | return pb_enc_bool(stream, field); |
386 | | |
387 | 3.90k | case PB_LTYPE_VARINT: |
388 | 5.61k | case PB_LTYPE_UVARINT: |
389 | 6.83k | case PB_LTYPE_SVARINT: |
390 | 6.83k | return pb_enc_varint(stream, field); |
391 | | |
392 | 1.66k | case PB_LTYPE_FIXED32: |
393 | 2.27k | case PB_LTYPE_FIXED64: |
394 | 2.27k | return pb_enc_fixed(stream, field); |
395 | | |
396 | 1.01k | case PB_LTYPE_BYTES: |
397 | 1.01k | return pb_enc_bytes(stream, field); |
398 | | |
399 | 2.16k | case PB_LTYPE_STRING: |
400 | 2.16k | return pb_enc_string(stream, field); |
401 | | |
402 | 3.22k | case PB_LTYPE_SUBMESSAGE: |
403 | 3.22k | case PB_LTYPE_SUBMSG_W_CB: |
404 | 3.22k | return pb_enc_submessage(stream, field); |
405 | | |
406 | 4.19k | case PB_LTYPE_FIXED_LENGTH_BYTES: |
407 | 4.19k | return pb_enc_fixed_length_bytes(stream, field); |
408 | | |
409 | 0 | default: |
410 | 0 | PB_RETURN_ERROR(stream, "invalid field type"); |
411 | 20.1k | } |
412 | 20.1k | } |
413 | | |
414 | | /* Encode a field with callback semantics. This means that a user function is |
415 | | * called to provide and encode the actual data. */ |
416 | | static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field) |
417 | 0 | { |
418 | 0 | if (field->descriptor->field_callback != NULL) |
419 | 0 | { |
420 | 0 | if (!field->descriptor->field_callback(NULL, stream, field)) |
421 | 0 | PB_RETURN_ERROR(stream, "callback error"); |
422 | 0 | } |
423 | 0 | return true; |
424 | 0 | } |
425 | | |
426 | | /* Encode a single field of any callback, pointer or static type. */ |
427 | | static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field) |
428 | 139k | { |
429 | | /* Check field presence */ |
430 | 139k | if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) |
431 | 8.57k | { |
432 | 8.57k | if (*(const pb_size_t*)field->pSize != field->tag) |
433 | 8.21k | { |
434 | | /* Different type oneof field */ |
435 | 8.21k | return true; |
436 | 8.21k | } |
437 | 8.57k | } |
438 | 130k | else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) |
439 | 76.5k | { |
440 | 76.5k | if (field->pSize) |
441 | 5.71k | { |
442 | 5.71k | if (safe_read_bool(field->pSize) == false) |
443 | 5.38k | { |
444 | | /* Missing optional field */ |
445 | 5.38k | return true; |
446 | 5.38k | } |
447 | 5.71k | } |
448 | 70.8k | else if (PB_ATYPE(field->type) == PB_ATYPE_STATIC) |
449 | 70.8k | { |
450 | | /* Proto3 singular field */ |
451 | 70.8k | if (pb_check_proto3_default_value(field)) |
452 | 56.4k | return true; |
453 | 70.8k | } |
454 | 76.5k | } |
455 | | |
456 | 69.3k | if (!field->pData) |
457 | 0 | { |
458 | 0 | if (PB_HTYPE(field->type) == PB_HTYPE_REQUIRED) |
459 | 0 | PB_RETURN_ERROR(stream, "missing required field"); |
460 | | |
461 | | /* Pointer field set to NULL */ |
462 | 0 | return true; |
463 | 0 | } |
464 | | |
465 | | /* Then encode field contents */ |
466 | 69.3k | if (PB_ATYPE(field->type) == PB_ATYPE_CALLBACK) |
467 | 0 | { |
468 | 0 | return encode_callback_field(stream, field); |
469 | 0 | } |
470 | 69.3k | else if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) |
471 | 54.3k | { |
472 | 54.3k | return encode_array(stream, field); |
473 | 54.3k | } |
474 | 15.0k | else |
475 | 15.0k | { |
476 | 15.0k | return encode_basic_field(stream, field); |
477 | 15.0k | } |
478 | 69.3k | } |
479 | | |
480 | | /* Default handler for extension fields. Expects to have a pb_msgdesc_t |
481 | | * pointer in the extension->type->arg field, pointing to a message with |
482 | | * only one field in it. */ |
483 | | static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension) |
484 | 0 | { |
485 | 0 | pb_field_iter_t iter; |
486 | |
|
487 | 0 | if (!pb_field_iter_begin_extension_const(&iter, extension)) |
488 | 0 | PB_RETURN_ERROR(stream, "invalid extension"); |
489 | | |
490 | 0 | return encode_field(stream, &iter); |
491 | 0 | } |
492 | | |
493 | | |
494 | | /* Walk through all the registered extensions and give them a chance |
495 | | * to encode themselves. */ |
496 | | static pb_noinline bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field) |
497 | 0 | { |
498 | 0 | const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; |
499 | |
|
500 | 0 | while (extension) |
501 | 0 | { |
502 | 0 | bool status; |
503 | 0 | if (extension->type->encode) |
504 | 0 | status = extension->type->encode(stream, extension); |
505 | 0 | else |
506 | 0 | status = default_extension_encoder(stream, extension); |
507 | |
|
508 | 0 | if (!status) |
509 | 0 | return false; |
510 | | |
511 | 0 | extension = extension->next; |
512 | 0 | } |
513 | | |
514 | 0 | return true; |
515 | 0 | } |
516 | | |
517 | | /********************* |
518 | | * Encode all fields * |
519 | | *********************/ |
520 | | |
521 | | bool checkreturn pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) |
522 | 9.30k | { |
523 | 9.30k | pb_field_iter_t iter; |
524 | 9.30k | if (!pb_field_iter_begin_const(&iter, fields, src_struct)) |
525 | 2.12k | return true; /* Empty message type */ |
526 | | |
527 | 139k | do { |
528 | 139k | if (PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) |
529 | 0 | { |
530 | | /* Special case for the extension field placeholder */ |
531 | 0 | if (!encode_extension_field(stream, &iter)) |
532 | 0 | return false; |
533 | 0 | } |
534 | 139k | else |
535 | 139k | { |
536 | | /* Regular field */ |
537 | 139k | if (!encode_field(stream, &iter)) |
538 | 0 | return false; |
539 | 139k | } |
540 | 139k | } while (pb_field_iter_next(&iter)); |
541 | | |
542 | 7.17k | return true; |
543 | 7.17k | } |
544 | | |
545 | | bool checkreturn pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags) |
546 | 0 | { |
547 | 0 | if ((flags & PB_ENCODE_DELIMITED) != 0) |
548 | 0 | { |
549 | 0 | return pb_encode_submessage(stream, fields, src_struct); |
550 | 0 | } |
551 | 0 | else if ((flags & PB_ENCODE_NULLTERMINATED) != 0) |
552 | 0 | { |
553 | 0 | const pb_byte_t zero = 0; |
554 | |
|
555 | 0 | if (!pb_encode(stream, fields, src_struct)) |
556 | 0 | return false; |
557 | | |
558 | 0 | return pb_write(stream, &zero, 1); |
559 | 0 | } |
560 | 0 | else |
561 | 0 | { |
562 | 0 | return pb_encode(stream, fields, src_struct); |
563 | 0 | } |
564 | 0 | } |
565 | | |
566 | | bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct) |
567 | 0 | { |
568 | 0 | pb_ostream_t stream = PB_OSTREAM_SIZING; |
569 | | |
570 | 0 | if (!pb_encode(&stream, fields, src_struct)) |
571 | 0 | return false; |
572 | | |
573 | 0 | *size = stream.bytes_written; |
574 | 0 | return true; |
575 | 0 | } |
576 | | |
577 | | /******************** |
578 | | * Helper functions * |
579 | | ********************/ |
580 | | |
581 | | /* This function avoids 64-bit shifts as they are quite slow on many platforms. */ |
582 | | static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high) |
583 | 22.4k | { |
584 | 22.4k | size_t i = 0; |
585 | 22.4k | pb_byte_t buffer[10]; |
586 | 22.4k | pb_byte_t byte = (pb_byte_t)(low & 0x7F); |
587 | 22.4k | low >>= 7; |
588 | | |
589 | 70.2k | while (i < 4 && (low != 0 || high != 0)) |
590 | 47.8k | { |
591 | 47.8k | byte |= 0x80; |
592 | 47.8k | buffer[i++] = byte; |
593 | 47.8k | byte = (pb_byte_t)(low & 0x7F); |
594 | 47.8k | low >>= 7; |
595 | 47.8k | } |
596 | | |
597 | 22.4k | if (high) |
598 | 6.92k | { |
599 | 6.92k | byte = (pb_byte_t)(byte | ((high & 0x07) << 4)); |
600 | 6.92k | high >>= 3; |
601 | | |
602 | 34.9k | while (high) |
603 | 28.0k | { |
604 | 28.0k | byte |= 0x80; |
605 | 28.0k | buffer[i++] = byte; |
606 | 28.0k | byte = (pb_byte_t)(high & 0x7F); |
607 | 28.0k | high >>= 7; |
608 | 28.0k | } |
609 | 6.92k | } |
610 | | |
611 | 22.4k | buffer[i++] = byte; |
612 | | |
613 | 22.4k | return pb_write(stream, buffer, i); |
614 | 22.4k | } |
615 | | |
616 | | bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) |
617 | 58.9k | { |
618 | 58.9k | if (value <= 0x7F) |
619 | 36.4k | { |
620 | | /* Fast path: single byte */ |
621 | 36.4k | pb_byte_t byte = (pb_byte_t)value; |
622 | 36.4k | return pb_write(stream, &byte, 1); |
623 | 36.4k | } |
624 | 22.4k | else |
625 | 22.4k | { |
626 | | #ifdef PB_WITHOUT_64BIT |
627 | | return pb_encode_varint_32(stream, value, 0); |
628 | | #else |
629 | 22.4k | return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32)); |
630 | 22.4k | #endif |
631 | 22.4k | } |
632 | 58.9k | } |
633 | | |
634 | | bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) |
635 | 6.13k | { |
636 | 6.13k | pb_uint64_t zigzagged; |
637 | 6.13k | pb_uint64_t mask = ((pb_uint64_t)-1) >> 1; /* Satisfy clang -fsanitize=integer */ |
638 | 6.13k | if (value < 0) |
639 | 2.75k | zigzagged = ~(((pb_uint64_t)value & mask) << 1); |
640 | 3.38k | else |
641 | 3.38k | zigzagged = (pb_uint64_t)value << 1; |
642 | | |
643 | 6.13k | return pb_encode_varint(stream, zigzagged); |
644 | 6.13k | } |
645 | | |
646 | | bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) |
647 | 3.38k | { |
648 | 3.38k | #if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 |
649 | | /* Fast path if we know that we're on little endian */ |
650 | 3.38k | return pb_write(stream, (const pb_byte_t*)value, 4); |
651 | | #else |
652 | | uint32_t val = *(const uint32_t*)value; |
653 | | pb_byte_t bytes[4]; |
654 | | bytes[0] = (pb_byte_t)(val & 0xFF); |
655 | | bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); |
656 | | bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); |
657 | | bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); |
658 | | return pb_write(stream, bytes, 4); |
659 | | #endif |
660 | 3.38k | } |
661 | | |
662 | | #ifndef PB_WITHOUT_64BIT |
663 | | bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) |
664 | 1.68k | { |
665 | 1.68k | #if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 |
666 | | /* Fast path if we know that we're on little endian */ |
667 | 1.68k | return pb_write(stream, (const pb_byte_t*)value, 8); |
668 | | #else |
669 | | uint64_t val = *(const uint64_t*)value; |
670 | | pb_byte_t bytes[8]; |
671 | | bytes[0] = (pb_byte_t)(val & 0xFF); |
672 | | bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); |
673 | | bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); |
674 | | bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); |
675 | | bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); |
676 | | bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); |
677 | | bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); |
678 | | bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); |
679 | | return pb_write(stream, bytes, 8); |
680 | | #endif |
681 | 1.68k | } |
682 | | #endif |
683 | | |
684 | | bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) |
685 | 22.4k | { |
686 | 22.4k | pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; |
687 | 22.4k | return pb_encode_varint(stream, tag); |
688 | 22.4k | } |
689 | | |
690 | | bool pb_encode_tag_for_field ( pb_ostream_t* stream, const pb_field_iter_t* field ) |
691 | 20.1k | { |
692 | 20.1k | pb_wire_type_t wiretype; |
693 | 20.1k | switch (PB_LTYPE(field->type)) |
694 | 20.1k | { |
695 | 492 | case PB_LTYPE_BOOL: |
696 | 4.39k | case PB_LTYPE_VARINT: |
697 | 6.10k | case PB_LTYPE_UVARINT: |
698 | 7.32k | case PB_LTYPE_SVARINT: |
699 | 7.32k | wiretype = PB_WT_VARINT; |
700 | 7.32k | break; |
701 | | |
702 | 1.66k | case PB_LTYPE_FIXED32: |
703 | 1.66k | wiretype = PB_WT_32BIT; |
704 | 1.66k | break; |
705 | | |
706 | 608 | case PB_LTYPE_FIXED64: |
707 | 608 | wiretype = PB_WT_64BIT; |
708 | 608 | break; |
709 | | |
710 | 1.01k | case PB_LTYPE_BYTES: |
711 | 3.17k | case PB_LTYPE_STRING: |
712 | 6.40k | case PB_LTYPE_SUBMESSAGE: |
713 | 6.40k | case PB_LTYPE_SUBMSG_W_CB: |
714 | 10.5k | case PB_LTYPE_FIXED_LENGTH_BYTES: |
715 | 10.5k | wiretype = PB_WT_STRING; |
716 | 10.5k | break; |
717 | | |
718 | 0 | default: |
719 | 0 | PB_RETURN_ERROR(stream, "invalid field type"); |
720 | 20.1k | } |
721 | | |
722 | 20.1k | return pb_encode_tag(stream, wiretype, field->tag); |
723 | 20.1k | } |
724 | | |
725 | | bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) |
726 | 7.37k | { |
727 | 7.37k | if (!pb_encode_varint(stream, (pb_uint64_t)size)) |
728 | 0 | return false; |
729 | | |
730 | 7.37k | return pb_write(stream, buffer, size); |
731 | 7.37k | } |
732 | | |
733 | | bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) |
734 | 3.22k | { |
735 | | /* First calculate the message size using a non-writing substream. */ |
736 | 3.22k | pb_ostream_t substream = PB_OSTREAM_SIZING; |
737 | 3.22k | #if !defined(PB_NO_ENCODE_SIZE_CHECK) || PB_NO_ENCODE_SIZE_CHECK == 0 |
738 | 3.22k | bool status; |
739 | 3.22k | size_t size; |
740 | 3.22k | #endif |
741 | | |
742 | 3.22k | if (!pb_encode(&substream, fields, src_struct)) |
743 | 0 | { |
744 | 0 | #ifndef PB_NO_ERRMSG |
745 | 0 | stream->errmsg = substream.errmsg; |
746 | 0 | #endif |
747 | 0 | return false; |
748 | 0 | } |
749 | | |
750 | 3.22k | if (!pb_encode_varint(stream, (pb_uint64_t)substream.bytes_written)) |
751 | 0 | return false; |
752 | | |
753 | 3.22k | if (stream->callback == NULL) |
754 | 0 | return pb_write(stream, NULL, substream.bytes_written); /* Just sizing */ |
755 | | |
756 | 3.22k | if (stream->bytes_written + substream.bytes_written > stream->max_size) |
757 | 0 | PB_RETURN_ERROR(stream, "stream full"); |
758 | | |
759 | | #if defined(PB_NO_ENCODE_SIZE_CHECK) && PB_NO_ENCODE_SIZE_CHECK == 1 |
760 | | return pb_encode(stream, fields, src_struct); |
761 | | #else |
762 | 3.22k | size = substream.bytes_written; |
763 | | /* Use a substream to verify that a callback doesn't write more than |
764 | | * what it did the first time. */ |
765 | 3.22k | substream.callback = stream->callback; |
766 | 3.22k | substream.state = stream->state; |
767 | 3.22k | substream.max_size = size; |
768 | 3.22k | substream.bytes_written = 0; |
769 | 3.22k | #ifndef PB_NO_ERRMSG |
770 | 3.22k | substream.errmsg = NULL; |
771 | 3.22k | #endif |
772 | | |
773 | 3.22k | status = pb_encode(&substream, fields, src_struct); |
774 | | |
775 | 3.22k | stream->bytes_written += substream.bytes_written; |
776 | 3.22k | stream->state = substream.state; |
777 | 3.22k | #ifndef PB_NO_ERRMSG |
778 | 3.22k | stream->errmsg = substream.errmsg; |
779 | 3.22k | #endif |
780 | | |
781 | 3.22k | if (substream.bytes_written != size) |
782 | 0 | PB_RETURN_ERROR(stream, "submsg size changed"); |
783 | | |
784 | 3.22k | return status; |
785 | 3.22k | #endif |
786 | 3.22k | } |
787 | | |
788 | | /* Field encoders */ |
789 | | |
790 | | static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field) |
791 | 492 | { |
792 | 492 | uint32_t value = safe_read_bool(field->pData) ? 1 : 0; |
793 | 492 | PB_UNUSED(field); |
794 | 492 | return pb_encode_varint(stream, value); |
795 | 492 | } |
796 | | |
797 | | static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field) |
798 | 23.2k | { |
799 | 23.2k | if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) |
800 | 6.18k | { |
801 | | /* Perform unsigned integer extension */ |
802 | 6.18k | pb_uint64_t value = 0; |
803 | | |
804 | 6.18k | if (field->data_size == sizeof(uint_least8_t)) |
805 | 0 | value = *(const uint_least8_t*)field->pData; |
806 | 6.18k | else if (field->data_size == sizeof(uint_least16_t)) |
807 | 0 | value = *(const uint_least16_t*)field->pData; |
808 | 6.18k | else if (field->data_size == sizeof(uint32_t)) |
809 | 3.88k | value = *(const uint32_t*)field->pData; |
810 | 2.30k | else if (field->data_size == sizeof(pb_uint64_t)) |
811 | 2.30k | value = *(const pb_uint64_t*)field->pData; |
812 | 0 | else |
813 | 0 | PB_RETURN_ERROR(stream, "invalid data_size"); |
814 | | |
815 | 6.18k | return pb_encode_varint(stream, value); |
816 | 6.18k | } |
817 | 17.0k | else |
818 | 17.0k | { |
819 | | /* Perform signed integer extension */ |
820 | 17.0k | pb_int64_t value = 0; |
821 | | |
822 | 17.0k | if (field->data_size == sizeof(int_least8_t)) |
823 | 1.54k | value = *(const int_least8_t*)field->pData; |
824 | 15.4k | else if (field->data_size == sizeof(int_least16_t)) |
825 | 0 | value = *(const int_least16_t*)field->pData; |
826 | 15.4k | else if (field->data_size == sizeof(int32_t)) |
827 | 8.21k | value = *(const int32_t*)field->pData; |
828 | 7.28k | else if (field->data_size == sizeof(pb_int64_t)) |
829 | 7.28k | value = *(const pb_int64_t*)field->pData; |
830 | 0 | else |
831 | 0 | PB_RETURN_ERROR(stream, "invalid data_size"); |
832 | | |
833 | 17.0k | if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) |
834 | 6.13k | return pb_encode_svarint(stream, value); |
835 | | #ifdef PB_WITHOUT_64BIT |
836 | | else if (value < 0) |
837 | | return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1); |
838 | | #endif |
839 | 10.9k | else |
840 | 10.9k | return pb_encode_varint(stream, (pb_uint64_t)value); |
841 | | |
842 | 17.0k | } |
843 | 23.2k | } |
844 | | |
845 | | static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field) |
846 | 5.06k | { |
847 | | #ifdef PB_CONVERT_DOUBLE_FLOAT |
848 | | if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64) |
849 | | { |
850 | | return pb_encode_float_as_double(stream, *(float*)field->pData); |
851 | | } |
852 | | #endif |
853 | | |
854 | 5.06k | if (field->data_size == sizeof(uint32_t)) |
855 | 3.38k | { |
856 | 3.38k | return pb_encode_fixed32(stream, field->pData); |
857 | 3.38k | } |
858 | 1.68k | #ifndef PB_WITHOUT_64BIT |
859 | 1.68k | else if (field->data_size == sizeof(uint64_t)) |
860 | 1.68k | { |
861 | 1.68k | return pb_encode_fixed64(stream, field->pData); |
862 | 1.68k | } |
863 | 0 | #endif |
864 | 0 | else |
865 | 0 | { |
866 | 0 | PB_RETURN_ERROR(stream, "invalid data_size"); |
867 | 0 | } |
868 | 5.06k | } |
869 | | |
870 | | static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) |
871 | 1.01k | { |
872 | 1.01k | const pb_bytes_array_t *bytes = NULL; |
873 | | |
874 | 1.01k | bytes = (const pb_bytes_array_t*)field->pData; |
875 | | |
876 | 1.01k | if (bytes == NULL) |
877 | 0 | { |
878 | | /* Treat null pointer as an empty bytes field */ |
879 | 0 | return pb_encode_string(stream, NULL, 0); |
880 | 0 | } |
881 | | |
882 | 1.01k | if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && |
883 | 1.01k | bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes)) |
884 | 0 | { |
885 | 0 | PB_RETURN_ERROR(stream, "bytes size exceeded"); |
886 | 0 | } |
887 | | |
888 | 1.01k | return pb_encode_string(stream, bytes->bytes, (size_t)bytes->size); |
889 | 1.01k | } |
890 | | |
891 | | static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field) |
892 | 2.16k | { |
893 | 2.16k | size_t size = 0; |
894 | 2.16k | size_t max_size = (size_t)field->data_size; |
895 | 2.16k | const char *str = (const char*)field->pData; |
896 | | |
897 | 2.16k | if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) |
898 | 0 | { |
899 | 0 | max_size = (size_t)-1; |
900 | 0 | } |
901 | 2.16k | else |
902 | 2.16k | { |
903 | | /* pb_dec_string() assumes string fields end with a null |
904 | | * terminator when the type isn't PB_ATYPE_POINTER, so we |
905 | | * shouldn't allow more than max-1 bytes to be written to |
906 | | * allow space for the null terminator. |
907 | | */ |
908 | 2.16k | if (max_size == 0) |
909 | 0 | PB_RETURN_ERROR(stream, "zero-length string"); |
910 | | |
911 | 2.16k | max_size -= 1; |
912 | 2.16k | } |
913 | | |
914 | | |
915 | 2.16k | if (str == NULL) |
916 | 0 | { |
917 | 0 | size = 0; /* Treat null pointer as an empty string */ |
918 | 0 | } |
919 | 2.16k | else |
920 | 2.16k | { |
921 | 2.16k | const char *p = str; |
922 | | |
923 | | /* strnlen() is not always available, so just use a loop */ |
924 | 29.4k | while (size < max_size && *p != '\0') |
925 | 27.3k | { |
926 | 27.3k | size++; |
927 | 27.3k | p++; |
928 | 27.3k | } |
929 | | |
930 | 2.16k | if (*p != '\0') |
931 | 0 | { |
932 | 0 | PB_RETURN_ERROR(stream, "unterminated string"); |
933 | 0 | } |
934 | 2.16k | } |
935 | | |
936 | | #ifdef PB_VALIDATE_UTF8 |
937 | | if (!pb_validate_utf8(str)) |
938 | | PB_RETURN_ERROR(stream, "invalid utf8"); |
939 | | #endif |
940 | | |
941 | 2.16k | return pb_encode_string(stream, (const pb_byte_t*)str, size); |
942 | 2.16k | } |
943 | | |
944 | | static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field) |
945 | 3.22k | { |
946 | 3.22k | if (field->submsg_desc == NULL) |
947 | 0 | PB_RETURN_ERROR(stream, "invalid field descriptor"); |
948 | | |
949 | 3.22k | if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) |
950 | 0 | { |
951 | | /* Message callback is stored right before pSize. */ |
952 | 0 | pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; |
953 | 0 | if (callback->funcs.encode) |
954 | 0 | { |
955 | 0 | if (!callback->funcs.encode(stream, field, &callback->arg)) |
956 | 0 | return false; |
957 | 0 | } |
958 | 0 | } |
959 | | |
960 | 3.22k | return pb_encode_submessage(stream, field->submsg_desc, field->pData); |
961 | 3.22k | } |
962 | | |
963 | | static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) |
964 | 4.19k | { |
965 | 4.19k | return pb_encode_string(stream, (const pb_byte_t*)field->pData, (size_t)field->data_size); |
966 | 4.19k | } |
967 | | |
968 | | #ifdef PB_CONVERT_DOUBLE_FLOAT |
969 | | bool pb_encode_float_as_double(pb_ostream_t *stream, float value) |
970 | | { |
971 | | union { float f; uint32_t i; } in; |
972 | | uint_least8_t sign; |
973 | | int exponent; |
974 | | uint64_t mantissa; |
975 | | |
976 | | in.f = value; |
977 | | |
978 | | /* Decompose input value */ |
979 | | sign = (uint_least8_t)((in.i >> 31) & 1); |
980 | | exponent = (int)((in.i >> 23) & 0xFF) - 127; |
981 | | mantissa = in.i & 0x7FFFFF; |
982 | | |
983 | | if (exponent == 128) |
984 | | { |
985 | | /* Special value (NaN etc.) */ |
986 | | exponent = 1024; |
987 | | } |
988 | | else if (exponent == -127) |
989 | | { |
990 | | if (!mantissa) |
991 | | { |
992 | | /* Zero */ |
993 | | exponent = -1023; |
994 | | } |
995 | | else |
996 | | { |
997 | | /* Denormalized */ |
998 | | mantissa <<= 1; |
999 | | while (!(mantissa & 0x800000)) |
1000 | | { |
1001 | | mantissa <<= 1; |
1002 | | exponent--; |
1003 | | } |
1004 | | mantissa &= 0x7FFFFF; |
1005 | | } |
1006 | | } |
1007 | | |
1008 | | /* Combine fields */ |
1009 | | mantissa <<= 29; |
1010 | | mantissa |= (uint64_t)(exponent + 1023) << 52; |
1011 | | mantissa |= (uint64_t)sign << 63; |
1012 | | |
1013 | | return pb_encode_fixed64(stream, &mantissa); |
1014 | | } |
1015 | | #endif |