Line data Source code
1 : #ifndef HEADER_fd_src_ballet_pb_fd_pb_encode_h
2 : #define HEADER_fd_src_ballet_pb_fd_pb_encode_h
3 :
4 : /* fd_pb_encode.h is a header-only Protobuf wire format encoder. It
5 : is schema agnostic and designed for embedded use. Data is written
6 : out in a streaming manner, with occasional fixups of length prefixes
7 : when writing submessages.
8 :
9 : Using this API requires understanding how Protobuf schemas map to the
10 : wire format: https://protobuf.dev/programming-guides/encoding/
11 :
12 : Here is how fd_pb_encode compares to a typical Protobuf encoding
13 : library:
14 : - No dependencies (no protoc)
15 : - No code generation (user hand-writes encoders)
16 : - No memory allocator needed
17 : - No streaming support (message written into a contiguous buffer)
18 : - User error can result in corrupt messages (duplicate or incorrect
19 : tags, etc)
20 : - Space efficiently is slightly worse (larger length-prefixes for
21 : submessages) */
22 :
23 : #include "fd_pb_wire.h"
24 : #include "../../util/log/fd_log.h"
25 :
26 : /* FD_PB_ENCODE_DEPTH_MAX specifies the max submessage depth. */
27 :
28 0 : #define FD_PB_ENCODER_DEPTH_MAX (63UL)
29 :
30 : /* The pb_encoder class is used to serialize Protobuf messages. The
31 : user calls fd_pb_encode_* with each field, top down. */
32 :
33 : struct fd_pb_encoder {
34 :
35 : /* [buf0,buf1) is the encode buffer */
36 : uchar * buf0;
37 : uchar * buf1;
38 :
39 : /* cur points to where the next field is placed */
40 : uchar * cur;
41 :
42 : /* Sub-message nesting depth of new fields (0==topmost) */
43 : uint depth;
44 :
45 : /* offset to length prefix of each depth
46 : lp_off[0] points to the length prefix of a LEN field at depth 0
47 : (the LEN field's submessage is at depth 1) */
48 : uint lp_off[ FD_PB_ENCODER_DEPTH_MAX ];
49 :
50 : };
51 :
52 : typedef struct fd_pb_encoder fd_pb_encoder_t;
53 :
54 : FD_PROTOTYPES_BEGIN
55 :
56 : /* fd_pb_encoder_init creates a Protobuf encoder. Data is written to
57 : the buffer at out (up to out_sz bytes). Returns the initialized
58 : encoder object (has a mutable borrow on out). On return, the
59 : encoder's current message is the topmost message of the encode op.
60 :
61 : out_sz should be slightly overallocated (by 32 bytes) since encoder
62 : bounds checks use overly conservative upper bounds for performance
63 : (i.e. the encoder might fail to add a field with "out of space" if
64 : less than 32 bytes of space is remaining, even if the field would
65 : still fit). */
66 :
67 : static inline fd_pb_encoder_t *
68 : fd_pb_encoder_init( fd_pb_encoder_t * encoder,
69 : uchar * out,
70 130172 : ulong out_sz ) {
71 130172 : encoder->buf0 = out;
72 130172 : encoder->buf1 = out + out_sz;
73 130172 : encoder->cur = out;
74 130172 : encoder->depth = 0U;
75 130172 : return encoder;
76 130172 : }
77 :
78 : /* fd_pb_encoder_fini clears the encoder state. */
79 :
80 : static inline void *
81 130265 : fd_pb_encoder_fini( fd_pb_encoder_t * encoder ) {
82 130265 : encoder->buf0 = NULL;
83 130265 : encoder->buf1 = NULL;
84 130265 : encoder->cur = NULL;
85 130265 : encoder->depth = 0;
86 130265 : return encoder;
87 130265 : }
88 :
89 : /* fd_pb_encoder_out returns a pointer to the first byte of the encoded
90 : output. There is a valid serialized Protobuf message behind this
91 : pointer when the following conditions are true:
92 : - At least one field was fully written at the topmost message
93 : - No write is currently inflight (submessage, length-prefixed, etc)
94 : - No write failed */
95 :
96 : static inline uchar *
97 0 : fd_pb_encoder_out( fd_pb_encoder_t * encoder ) {
98 0 : return encoder->buf0;
99 0 : }
100 :
101 : /* fd_pb_encoder_out_sz returns the number of bytes produced so far. */
102 :
103 : static inline ulong
104 130273 : fd_pb_encoder_out_sz( fd_pb_encoder_t * encoder ) {
105 130273 : FD_CHECK_CRIT( encoder->cur >= encoder->buf0, "corrupt encoder state" );
106 130273 : return (ulong)encoder->cur - (ulong)encoder->buf0;
107 130273 : }
108 :
109 : /* fd_pb_encoder_space returns the number of encoded field bytes that
110 : can be appended to the current submessage. */
111 :
112 : static inline ulong
113 6493570 : fd_pb_encoder_space( fd_pb_encoder_t const * encoder ) {
114 6493570 : FD_CHECK_CRIT( encoder->cur <= encoder->buf1, "corrupt encoder state" );
115 6493570 : return (ulong)encoder->buf1 - (ulong)encoder->cur;
116 6493570 : }
117 :
118 : /* fd_pb_lp_open adds a new unknown-sized LEN field to the encoder's
119 : current message. Returns encoder on success. On failure (no space),
120 : returns NULL. Every successful call must be paired with a call to
121 : fd_pb_lp_close. Use fd_pb_encoder_space to check how much free
122 : space the frame has, and fd_pb_encoder_push to append data.
123 :
124 : This method is useful for serializing Protobuf submessages with
125 : another library, moving encoded submessages without a deserialize/
126 : serialize pass, streaming out packed repated fields, iterating over
127 : an unknown-sized sequence, etc. */
128 :
129 : static FD_FN_UNUSED fd_pb_encoder_t *
130 : fd_pb_lp_open( fd_pb_encoder_t * encoder,
131 1179977 : uint field_id ) {
132 1179977 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
133 1179977 : (fd_pb_varint32_sz_max + fd_pb_varint32_sz_max) ) ) {
134 0 : return NULL;
135 0 : }
136 1179977 : uint depth = encoder->depth;
137 1179977 : if( FD_UNLIKELY( depth >= FD_PB_ENCODER_DEPTH_MAX ) ) {
138 : /* unreachable for well-written clients */
139 0 : FD_LOG_WARNING(( "pb_encode failed: submessage nesting depth exceeded" ));
140 0 : return NULL;
141 0 : }
142 1179977 : encoder->depth++;
143 : /* Add tag, reserve worst-case space for length-prefix */
144 1179977 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_LEN, field_id );
145 1179977 : encoder->cur = fd_pb_append_tag( encoder->cur, tag );
146 1179977 : encoder->lp_off[ depth ] = (uint)( encoder->cur - encoder->buf0 );
147 1179977 : encoder->cur += fd_pb_varint32_sz_max;
148 1179977 : return encoder;
149 1179977 : }
150 :
151 : /* fd_pb_lp_close closes the current LEN field (opened with
152 : fd_pb_lp_open) Returns encoder on success and NULL on failure
153 : (LEN field exceeds max size). */
154 :
155 : static FD_FN_UNUSED fd_pb_encoder_t *
156 1183438 : fd_pb_lp_close( fd_pb_encoder_t * encoder ) {
157 1183438 : FD_CHECK_CRIT( encoder->depth, "unmatched lp_close" );
158 1183438 : FD_CHECK_CRIT( encoder->depth<=FD_PB_ENCODER_DEPTH_MAX, "corrupt submessage depth" );
159 1183438 : uint depth = --encoder->depth;
160 1183438 : uchar * lp = encoder->buf0 + encoder->lp_off[ depth ];
161 1183438 : uchar * sub0 = lp + fd_pb_varint32_sz_max;
162 1183438 : uchar * sub1 = encoder->cur;
163 1183438 : ulong sz = (ulong)( sub1-sub0 );
164 1183438 : FD_CHECK_CRIT( sub0<=sub1, "corrupt submessage state" );
165 1183438 : FD_CHECK_CRIT( sub1<=encoder->buf1, "out-of-bounds write" );
166 1183438 : if( FD_UNLIKELY( sz>UINT_MAX ) ) {
167 0 : FD_LOG_WARNING(( "pb_encode failed: submessage is too large" ));
168 0 : }
169 1183438 : fd_pb_append_varint32_sz5( lp, (uint)sz );
170 1183438 : return encoder;
171 1183438 : }
172 :
173 : /* fd_pb_submsg_open adds a new submessage to the encoder's current
174 : message, then pivots the current message to the newly created
175 : submessage. Returns encoder on success, NULL on failure (no space
176 : remaining). Every successful call must be paired with a call to
177 : fd_pb_submsg_close. */
178 :
179 : static inline fd_pb_encoder_t *
180 : fd_pb_submsg_open( fd_pb_encoder_t * encoder,
181 1177301 : uint field_id ) {
182 1177301 : return fd_pb_lp_open( encoder, field_id );
183 1177301 : }
184 :
185 : /* fd_pb_submsg_close finishes the encoder's current submessage. Sets
186 : the encoder's current message to the parent message. Returns
187 : encoder on success and NULL on failure (submessage exceeds max msg
188 : size). */
189 :
190 : static inline fd_pb_encoder_t *
191 0 : fd_pb_submsg_close( fd_pb_encoder_t * encoder ) {
192 0 : return fd_pb_lp_close( encoder );
193 0 : }
194 :
195 : /* The below fd_pb_push_<type> methods append a field to the encoder's
196 : current message. type and field_id are the field's parameters as
197 : defined in the schema. value depends on the message type. Note that
198 : the provided type MUST match the schema (e.g. encoding uint32 where
199 : the schema says sint32 would result in memory corruption). Returns
200 : encoder on success, or NULL on failure (out of space). On any write
201 : failure, discard the entire message.
202 :
203 : fd_pb_push_bool adds a boolean field. value==0 implies false,
204 : otherwise implies true.
205 :
206 : fd_pb_push_{int32,int64} add a signed integer field (optimized for
207 : small unsigned numbers).
208 :
209 : fd_pb_push_{uint32,uint64} add an unsigned integer field (optimized
210 : for small numbers).
211 :
212 : fd_pb_push_{sint32,sint64} add a signed integer field (optimized
213 : for numbers close to zero).
214 :
215 : fd_pb_push_{fixed32,fixed64} add an unsigned integer field
216 : (optimized for very large numbers).
217 :
218 : fd_pb_push_{sfixed32,sfixed64} add a signed integer field
219 : (optimized for very small or large numbers).
220 :
221 : fd_pb_push_{float,double} add a {32,64} bit precision floating-
222 : point field.
223 :
224 : fd_pb_push_bytes adds a byte array field.
225 :
226 : fd_pb_push_string adds a UTF-8 string field.
227 :
228 : fd_pb_push_cstr adds a UTF-8 string field from a NULL-delimited C
229 : string. */
230 :
231 : static inline fd_pb_encoder_t *
232 : fd_pb_push_bool( fd_pb_encoder_t * encoder,
233 : uint field_id,
234 292688 : int value ) {
235 292688 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
236 292688 : (fd_pb_varint32_sz_max + 1) ) ) {
237 0 : return NULL;
238 0 : }
239 292688 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
240 292688 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
241 292688 : encoder->cur = fd_pb_append_bool( encoder->cur, value );
242 292688 : return encoder;
243 292688 : }
244 :
245 : static inline fd_pb_encoder_t *
246 : fd_pb_push_int32( fd_pb_encoder_t * encoder,
247 : uint field_id,
248 61203 : int value ) {
249 61203 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
250 61203 : (fd_pb_varint32_sz_max + fd_pb_varint32_sz_max) ) ) {
251 0 : return NULL;
252 0 : }
253 61203 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
254 61203 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
255 61203 : encoder->cur = fd_pb_append_int32( encoder->cur, value );
256 61203 : return encoder;
257 61203 : }
258 :
259 : static inline fd_pb_encoder_t *
260 : fd_pb_push_int64( fd_pb_encoder_t * encoder,
261 : uint field_id,
262 7592 : long value ) {
263 7592 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
264 7592 : (fd_pb_varint32_sz_max + fd_pb_varint64_sz_max) ) ) {
265 0 : return NULL;
266 0 : }
267 7592 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
268 7592 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
269 7592 : encoder->cur = fd_pb_append_int64( encoder->cur, value );
270 7592 : return encoder;
271 7592 : }
272 :
273 : static inline fd_pb_encoder_t *
274 : fd_pb_push_uint32( fd_pb_encoder_t * encoder,
275 : uint field_id,
276 47429 : uint value ) {
277 47429 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
278 47429 : (fd_pb_varint32_sz_max + fd_pb_varint32_sz_max) ) ) {
279 0 : return NULL;
280 0 : }
281 47429 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
282 47429 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
283 47429 : encoder->cur = fd_pb_append_uint32( encoder->cur, value );
284 47429 : return encoder;
285 47429 : }
286 :
287 : static inline fd_pb_encoder_t *
288 : fd_pb_push_uint64( fd_pb_encoder_t * encoder,
289 : uint field_id,
290 1755311 : ulong value ) {
291 1755311 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
292 1755311 : (fd_pb_varint32_sz_max + fd_pb_varint64_sz_max) ) ) {
293 0 : return NULL;
294 0 : }
295 1755311 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
296 1755311 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
297 1755311 : encoder->cur = fd_pb_append_uint64( encoder->cur, value );
298 1755311 : return encoder;
299 1755311 : }
300 :
301 : static inline fd_pb_encoder_t *
302 : fd_pb_push_sint32( fd_pb_encoder_t * encoder,
303 : uint field_id,
304 0 : int value ) {
305 0 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
306 0 : (fd_pb_varint32_sz_max + fd_pb_varint32_sz_max) ) ) {
307 0 : return NULL;
308 0 : }
309 0 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
310 0 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
311 0 : encoder->cur = fd_pb_append_sint32( encoder->cur, value );
312 0 : return encoder;
313 0 : }
314 :
315 : static inline fd_pb_encoder_t *
316 : fd_pb_push_sint64( fd_pb_encoder_t * encoder,
317 : uint field_id,
318 0 : long value ) {
319 0 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
320 0 : (fd_pb_varint32_sz_max + fd_pb_varint64_sz_max) ) ) {
321 0 : return NULL;
322 0 : }
323 0 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_VARINT, field_id );
324 0 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
325 0 : encoder->cur = fd_pb_append_sint64( encoder->cur, value );
326 0 : return encoder;
327 0 : }
328 :
329 : static inline fd_pb_encoder_t *
330 : fd_pb_push_fixed32( fd_pb_encoder_t * encoder,
331 : uint field_id,
332 88 : uint value ) {
333 88 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
334 88 : (fd_pb_varint32_sz_max + sizeof(uint)) ) ) {
335 0 : return NULL;
336 0 : }
337 88 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_I32, field_id );
338 88 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
339 88 : encoder->cur = fd_pb_append_fixed32( encoder->cur, value );
340 88 : return encoder;
341 88 : }
342 :
343 : static inline fd_pb_encoder_t *
344 : fd_pb_push_fixed64( fd_pb_encoder_t * encoder,
345 : uint field_id,
346 1018793 : ulong value ) {
347 1018793 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
348 1018793 : (fd_pb_varint32_sz_max + sizeof(ulong)) ) ) {
349 0 : return NULL;
350 0 : }
351 1018793 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_I64, field_id );
352 1018793 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
353 1018793 : encoder->cur = fd_pb_append_fixed64( encoder->cur, value );
354 1018793 : return encoder;
355 1018793 : }
356 :
357 : static inline fd_pb_encoder_t *
358 : fd_pb_push_float( fd_pb_encoder_t * encoder,
359 : uint field_id,
360 0 : float value ) {
361 0 : union { float f; uint u; } cast;
362 0 : cast.f = value;
363 0 : return fd_pb_push_fixed32( encoder, field_id, cast.u );
364 0 : }
365 :
366 : static inline fd_pb_encoder_t *
367 : fd_pb_push_double( fd_pb_encoder_t * encoder,
368 : uint field_id,
369 0 : double value ) {
370 0 : union { double d; ulong v; } cast;
371 0 : cast.d = value;
372 0 : return fd_pb_push_fixed64( encoder, field_id, cast.v );
373 0 : }
374 :
375 : static inline fd_pb_encoder_t *
376 : fd_pb_push_bytes( fd_pb_encoder_t * encoder,
377 : uint field_id,
378 : void const * buf,
379 1920766 : ulong sz ) {
380 1920766 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) <
381 1920766 : (fd_pb_varint32_sz_max + fd_pb_varint32_sz_max + sz) ||
382 1920766 : sz>UINT_MAX ) ) { /* max message size, overflow protect */
383 0 : return NULL;
384 0 : }
385 1920766 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_LEN, field_id );
386 1920766 : encoder->cur = fd_pb_append_tag ( encoder->cur, tag );
387 1920766 : encoder->cur = fd_pb_append_uint32( encoder->cur, (uint)sz );
388 1920766 : fd_memcpy( encoder->cur, buf, sz );
389 1920766 : encoder->cur += sz;
390 1920766 : return encoder;
391 1920766 : }
392 :
393 : static inline fd_pb_encoder_t *
394 : fd_pb_push_string( fd_pb_encoder_t * encoder,
395 : uint field_id,
396 : char const * str,
397 0 : ulong len ) {
398 0 : return fd_pb_push_bytes( encoder, field_id, str, len );
399 0 : }
400 :
401 : static inline fd_pb_encoder_t *
402 : fd_pb_push_cstr( fd_pb_encoder_t * encoder,
403 : uint field_id,
404 0 : char const * cstr ) {
405 : /* FIXME could do this in a single pass */
406 0 : return fd_pb_push_string( encoder, field_id, cstr, strlen( cstr ) );
407 0 : }
408 :
409 : /* The below fd_pb_push_packed_<type> methods append a packed repeated
410 : field (a sequence of scalars). These are analogous to the above
411 : encoders, except that they encode multiple values. values points to
412 : a contiguous array of values. cnt is the number of values (zero is
413 : fine).
414 :
415 : On the wire, a packed repeated field is a LEN type field with the
416 : content set to the concatenated serializations of each element.
417 :
418 : These are all equivalent to the following pattern:
419 :
420 : fd_pb_lp_open()
421 : for( each element ) fd_pb_append_type()
422 : fd_pb_lp_close()
423 :
424 : fd_pb_push_packed_bool is deliberately omitted. */
425 :
426 : #define FD_PB_PUSH_PACKED( type, ctype ) \
427 : FD_FN_UNUSED static fd_pb_encoder_t * \
428 : fd_pb_push_packed_##type( fd_pb_encoder_t * encoder, \
429 : uint field_id, \
430 : ctype const * values, \
431 0 : ulong cnt ) { \
432 0 : /* would exceed max message size? */ \
433 0 : if( FD_UNLIKELY( cnt>UINT_MAX ) ) return NULL; \
434 0 : if( FD_UNLIKELY( !fd_pb_lp_open( encoder, field_id ) ) ) return NULL; \
435 0 : if( FD_LIKELY( cnt*fd_pb_##type##_sz_max < \
436 0 : fd_pb_encoder_space( encoder ) ) ) { \
437 0 : /* optimize for fast append */ \
438 0 : for( ulong i=0UL; i<cnt; i++ ) { \
439 0 : encoder->cur = fd_pb_append_##type( encoder->cur, values[ i ] ); \
440 0 : } \
441 0 : } else { \
442 0 : /* cold code */ \
443 0 : for( ulong i=0UL; i<cnt; i++ ) { \
444 0 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) < \
445 0 : fd_pb_##type##_sz_max ) ) return NULL; \
446 0 : encoder->cur = fd_pb_append_##type( encoder->cur, values[ i ] ); \
447 0 : } \
448 0 : } \
449 0 : fd_pb_lp_close( encoder ); \
450 0 : return encoder; \
451 0 : }
452 :
453 : FD_PB_PUSH_PACKED( int32, int )
454 : FD_PB_PUSH_PACKED( int64, long )
455 : FD_PB_PUSH_PACKED( uint32, uint )
456 : FD_PB_PUSH_PACKED( uint64, ulong )
457 : FD_PB_PUSH_PACKED( sint32, int )
458 : FD_PB_PUSH_PACKED( sint64, long )
459 :
460 : #define FD_PB_PUSH_PACKED_STATIC( type, ctype ) \
461 : FD_FN_UNUSED static fd_pb_encoder_t * \
462 : fd_pb_push_packed_##type( fd_pb_encoder_t * encoder, \
463 : uint field_id, \
464 : ctype const * values, \
465 0 : ulong cnt ) { \
466 0 : /* write header */ \
467 0 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) < \
468 0 : (fd_pb_varint32_sz_max + fd_pb_varint64_sz_max) ) ) {\
469 0 : return NULL; \
470 0 : } \
471 0 : uint tag = fd_pb_tag( FD_PB_WIRE_TYPE_LEN, field_id ); \
472 0 : if( FD_UNLIKELY( cnt>fd_pb_encoder_space( encoder ) / \
473 0 : fd_pb_##type##_sz_max ) ) return NULL; \
474 0 : ulong data_sz = cnt*fd_pb_##type##_sz_max; \
475 0 : encoder->cur = fd_pb_append_uint32 ( encoder->cur, tag ); \
476 0 : encoder->cur = fd_pb_append_uint64 ( encoder->cur, data_sz ); \
477 0 : /* write data */ \
478 0 : if( FD_UNLIKELY( fd_pb_encoder_space( encoder ) < data_sz ) ) { \
479 0 : return NULL; \
480 0 : } \
481 0 : fd_memcpy( encoder->cur, values, data_sz ); \
482 0 : encoder->cur += data_sz; \
483 0 : return encoder; \
484 0 : }
485 :
486 : FD_PB_PUSH_PACKED_STATIC( fixed32, uint )
487 : FD_PB_PUSH_PACKED_STATIC( fixed64, ulong )
488 :
489 : #undef FD_PB_PUSH_PACKED
490 :
491 : static inline fd_pb_encoder_t *
492 : fd_pb_push_packed_float( fd_pb_encoder_t * encoder,
493 : uint field_id,
494 : float const * value,
495 0 : ulong cnt ) {
496 0 : return fd_pb_push_packed_fixed32( encoder, field_id, fd_type_pun_const( value ), cnt );
497 0 : }
498 :
499 : static inline fd_pb_encoder_t *
500 : fd_pb_push_packed_double( fd_pb_encoder_t * encoder,
501 : uint field_id,
502 : double const * value,
503 0 : ulong cnt ) {
504 0 : return fd_pb_push_packed_fixed64( encoder, field_id, fd_type_pun_const( value ), cnt );
505 0 : }
506 :
507 : FD_PROTOTYPES_END
508 :
509 : #endif /* HEADER_fd_src_ballet_pb_fd_pb_encode_h */
|