From accd93be8d5d967b42a114c563888fb12650a828 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Wed, 21 Dec 2011 04:36:10 +0000 Subject: [PATCH] Add an encoder optimized for in-memory buffers. git-svn-id: https://svn.kapsi.fi/jpa/nanopb-dev@1088 e3a754e5-d11d-0410-8d38-ebb782a927b9 --- docs/concepts.rst | 29 +++- docs/index.rst | 14 +- docs/reference.rst | 106 +++++++++++- pb.h | 2 + pb_encode_buffer.c | 362 +++++++++++++++++++++++++++++++++++++++ pb_encode_buffer.h | 43 +++++ tests/Makefile | 8 +- tests/encode_unittests.c | 160 +++++++++++++---- 8 files changed, 676 insertions(+), 48 deletions(-) create mode 100644 pb_encode_buffer.c create mode 100644 pb_encode_buffer.h diff --git a/docs/concepts.rst b/docs/concepts.rst index f1c7cab..6fc37dc 100644 --- a/docs/concepts.rst +++ b/docs/concepts.rst @@ -183,16 +183,20 @@ Encoding callbacks :: bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); + bool (*encode_buffer)(pb_strstream_t *stream, const pb_field_t *field, const void *arg); -When encoding, the callback should write out complete fields, including the wire type and field number tag. It can write as many or as few fields as it likes. For example, if you want to write out an array as *repeated* field, you should do it all in a single call. +When encoding, the callbacks should write out complete fields, including the wire type and field number tag. The callback can write as many or as few fields as it likes. For example, if you want to write out an array as *repeated* field, you should do it all in a single call. -Usually you can use `pb_encode_tag_for_field`_ to encode the wire type and tag number of the field. However, if you want to encode a repeated field as a packed array, you must call `pb_encode_tag`_ instead to specify a wire type of *PB_WT_STRING*. +Usually you can use `pb_encode_tag_for_field`_ (or `pb_encbuf_tag_for_field`_ for the *encode_buffer* callback) to encode the wire type and tag number of the field. However, if you want to encode a repeated field as a packed array, you must call `pb_encode_tag`_ (respectively, `pb_encbuf_tag`_) instead to specify a wire type of *PB_WT_STRING*. -If the callback is used in a submessage, it will be called multiple times during a single call to `pb_encode`_. In this case, it must produce the same amount of data every time. If the callback is directly in the main message, it is called only once. +If the callback is used in a submessage, *encode* will be called multiple times during a single call to `pb_encode`_. In this case, it must produce the same amount of data every time. If the callback is directly in the main message, or if you are using `pb_encode_buffer`_, the callback is called only once. .. _`pb_encode`: reference.html#pb-encode +.. _`pb_encode_buffer`: reference.html#pb-encode-buffer .. _`pb_encode_tag_for_field`: reference.html#pb-encode-tag-for-field +.. _`pb_encbuf_tag_for_field`: reference.html#pb-encbuf-tag-for-field .. _`pb_encode_tag`: reference.html#pb-encode-tag +.. _`pb_encbuf_tag`: reference.html#pb-encbuf-tag This callback writes out a dynamically sized string:: @@ -205,6 +209,17 @@ This callback writes out a dynamically sized string:: return pb_encode_string(stream, (uint8_t*)str, strlen(str)); } +The equivalent for in-memory buffers has to write the elements in the opposite order, because the buffer writers prepend their data:: + + bool write_string_buf(pb_strstream_t *stream, const pb_field_t *field, const void *arg) + { + char *str = get_string_from_somewhere(); + if (!pb_encbuf_string(stream, (uint8_t*)str, strlen(str))) + return false; + + return pb_encbuf_tag_for_field(stream, field)); + } + Decoding callbacks ------------------ :: @@ -234,7 +249,7 @@ This callback reads multiple integers and prints them:: Message descriptor ================== -For using the *pb_encode* and *pb_decode* functions, you need a message descriptor describing the structure you wish to encode. This description is usually autogenerated from .proto file. +For using the *pb_encode*, *pb_encode_buffer* and *pb_decode* functions, you need a message descriptor describing the structure you wish to encode. This description is usually autogenerated from .proto file. For example this submessage in the Person.proto file:: @@ -285,9 +300,9 @@ that array; in the previous example, they are *Person_PhoneNumber_has*, *Person_PhoneNumber_set* and *Person_PhoneNumber_clear*. -For convenience, *pb_encode* only checks these bits for optional -fields. *pb_decode* sets the corresponding bit for every field it -decodes, whether the field is optional or not. +For convenience, *pb_encode* and *pb_encode_buffer* only check these +bits for optional fields. *pb_decode* sets the corresponding bit for +every field it decodes, whether the field is optional or not. .. Should there be a section here on pointer fields? diff --git a/docs/index.rst b/docs/index.rst index 6b90173..cdc7d4e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Overall structure For the runtime program, you always need *pb.h* for type declarations. Depending on whether you want to encode, decode, or both, you also need *pb_encode.h/c* or *pb_decode.h/c*. +If you only encode into in-memory buffers, *pb_decode_buffer.h/c* should be slightly faster and smaller. If your *.proto* file encodes submessages or other fields using pointers, you must compile *pb_decode.c* with a preprocessor macro named *MALLOC_HEADER* that is the name of a header with definitions (either as functions or macros) for *calloc()*, *realloc()* and *free()*. For a typical hosted configuration, this should be **. @@ -27,6 +28,7 @@ So a typical project might include these files: - pb.h - pb_decode.h and pb_decode.c (needed for decoding messages) - pb_encode.h and pb_encode.c (needed for encoding messages) + - pb_encode_buffer.h and pb_encode_buffer.c (for encoding specifically into in-memory buffers) 2) Protocol description (you can have many): - person.proto (just an example) - person.pb.c (autogenerated, contains initializers for message descriptors) @@ -89,6 +91,17 @@ Now in your main program do this to encode a message:: After that, buffer will contain the encoded message. The number of bytes in the message is stored in *stream.bytes_written*. + +Using *pb_encode_buffer.h/c* interface is very similar:: + + Example mymessage = {42}; + uint8_t buffer[10]; + pb_strstream_t stream = pb_str_from_buffer(buffer, sizeof(buffer)); + pb_encode_buffer(&stream, Example_msg, &mymessage); + +The encoded message will start at *stream.last* and continue until the +end of *buffer* (that is, it has length *buffer - stream.last*). + You can feed the message to *protoc --decode=Example message.proto* to verify its validity. For complete examples of the simple cases, see *tests/test_decode1.c* and *tests/test_encode1.c*. For an example with network interface, see the *example* subdirectory. @@ -112,6 +125,5 @@ This also generates a file called *breakpoints* which includes all lines returni Wishlist ======== -#) A specialized encoder for encoding to a memory buffer. Should serialize in reverse order to avoid having to determine submessage size beforehand. #) A cleaner rewrite of the Python-based source generator. #) Better performance for 16- and 8-bit platforms: use smaller datatypes where possible. diff --git a/docs/reference.rst b/docs/reference.rst index f481881..831e521 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -102,6 +102,7 @@ Part of a message structure, for fields with type PB_HTYPE_CALLBACK:: union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); + bool (*encode_buffer)(pb_strstream_t *stream, const pb_field_t *field, const void *arg); } funcs; void *arg; @@ -109,11 +110,11 @@ Part of a message structure, for fields with type PB_HTYPE_CALLBACK:: The *arg* is passed to the callback when calling. It can be used to store any information that the callback might need. -When calling `pb_encode`_, *funcs.encode* is used, and similarly when calling `pb_decode`_, *funcs.decode* is used. The function pointers are stored in the same memory location but are of incompatible types. You can set the function pointer to NULL to skip the field. +When calling `pb_encode`_, *funcs.encode* is used, and similarly when calling `pb_encode_buffer`_, *funcs.encode_buffer* is used, and when calling `pb_decode`_, *funcs.decode* is used. The function pointers are stored in the same memory location but are of incompatible types. You can set the function pointer to NULL to skip the field. pb_wire_type_t -------------- -Protocol Buffers wire types. These are used with `pb_encode_tag`_. :: +Protocol Buffers wire types. These are used with `pb_encode_tag`_ and `pb_encbuf_tag`_. :: typedef enum { PB_WT_VARINT = 0, @@ -311,6 +312,107 @@ In Protocol Buffers format, the submessage size must be written before the subme If the submessage contains callback fields, the callback function might misbehave and write out a different amount of data on the second call. This situation is recognized and *false* is returned, but it is up to the caller to ensure that the receiver of the message does not interpret it as valid data. +pb_encode_buffer.h +================== + +An important note about this module is that data is written from the +back of the buffer to the front. That is, when you call +*pb_buf_write()*, it will place the bytes (in the order you provide +them) before the data currently in the buffer. + +pb_strstream_from_buffer +------------------------ +Constructs a buffer descriptor. This is just a helper function, it doesn't do anything you couldn't do yourself in a callback function. :: + + pb_strstream_t pb_strstream_from_buffer(uint8_t *buf, size_t bufsize); + +:buf: Memory buffer to write into. +:bufsize: Maximum number of bytes to write. +:returns: The buffer descriptor. + +The descriptor only tracks the amount of space left; it does not count how many bytes have been written. + +pb_buf_write +------------ +Prepends data to an in-memory buffer. Always use this function, instead of trying to manage the pointers inside the buffer descriptor. :: + + bool pb_buf_write(pb_strstream_t *stream, const uint8_t *buf, size_t count); + +:stream: Descriptor for buffer to write to. +:buf: Pointer to buffer with the data to be written. +:count: Number of bytes to write. +:returns: True on success, false if maximum length is exceeded. + +If there is not enough space, *stream* is not modified. + +pb_encode_buffer +---------------- +Encodes the contents of a structure as a protocol buffers message and writes it to a buffer. :: + + bool pb_encode_buffer(pb_strstream_t *stream, const pb_message_t *msg, const void *src_struct); + +:stream: Descriptor for buffer to write to. +:msg: A message descriptor, usually autogenerated. +:src_struct: Pointer to the data that will be serialized. +:returns: True on success, false if the buffer is too small or if a field encoder returns false. + +pb_encbuf_varint +---------------- +Encodes an unsigned integer in the varint_ format. :: + + bool pb_encbuf_varint(pb_strstream_t *stream, uint64_t value); + +:stream: Descriptor for buffer to write to. 1-10 bytes will be written. +:value: Value to encode. +:returns: True on success, false on IO error. + +.. _varint: http://code.google.com/apis/protocolbuffers/docs/encoding.html#varints + +pb_encbuf_tag +------------- +Finishes a field in the Protocol Buffers binary format: encodes the field number and the wire type of the data. :: + + bool pb_encbuf_tag(pb_strstream_t *stream, pb_wire_type_t wiretype, int field_number); + +:stream: Descriptor for buffer to write to. 1-5 bytes will be written. +:wiretype: PB_WT_VARINT, PB_WT_64BIT, PB_WT_STRING or PB_WT_32BIT +:field_number: Identifier for the field, defined in the .proto file. +:returns: True on success, false on IO error. + +pb_encbuf_tag_for_field +----------------------- +Same as `pb_encbuf_tag`_, except takes the parameters from a *pb_field_t* structure. :: + + bool pb_encbuf_tag_for_field(pb_strstream_t *stream, const pb_field_t *field); + +:stream: Descriptor for buffer to write to. 1-5 bytes will be written. +:field: Field description structure. Usually autogenerated. +:returns: True on success, false on IO error or unknown field type. + +This function only considers the LTYPE of the field. You can use it from your field callbacks, because the source generator writes correct LTYPE also for callback type fields. + +Wire type mapping is as follows: + +========================= ============ +LTYPEs Wire type +========================= ============ +VARINT, SVARINT PB_WT_VARINT +FIXED64 PB_WT_64BIT +STRING, BYTES, SUBMESSAGE PB_WT_STRING +FIXED32 PB_WT_32BIT +========================= ============ + +pb_encbuf_string +---------------- +Writes the length of a string as varint and then contents of the string. Used for writing fields with wire type PB_WT_STRING. :: + + bool pb_encbuf_string(pb_strstream_t *stream, const uint8_t *buffer, size_t size); + +:stream: Descriptor for buffer to write to. +:buffer: Pointer to string data. +:size: Number of bytes in the string. +:returns: True on success, false on IO error. + pb_decode.h =========== diff --git a/pb.h b/pb.h index 9ad9adc..71c76f0 100644 --- a/pb.h +++ b/pb.h @@ -177,11 +177,13 @@ typedef PB_MSG_STRUCT(1) pb_message_t; */ typedef struct _pb_istream_t pb_istream_t; typedef struct _pb_ostream_t pb_ostream_t; +typedef struct _pb_strstream_t pb_strstream_t; typedef struct _pb_callback_t pb_callback_t; struct _pb_callback_t { union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); + bool (*encode_buffer)(pb_strstream_t *stream, const pb_field_t *field, const void *arg); } funcs; /* Free arg for use by callback */ diff --git a/pb_encode_buffer.c b/pb_encode_buffer.c new file mode 100644 index 0000000..87bca82 --- /dev/null +++ b/pb_encode_buffer.c @@ -0,0 +1,362 @@ +/* pb_encode_buffer.c - encode a protobuf to an in-memory array + * + * 2011 Michael Poole + * Part of nanopb, 2011 Petteri Aimonen + */ + +#include "pb_encode_buffer.h" +#include + +#ifdef __GNUC__ +/* Verify that we remember to check all return values for proper error propagation */ +#define checkreturn __attribute__((warn_unused_result)) +#else +#define checkreturn +#endif + +typedef bool (*pb_encoder_t)(pb_strstream_t *stream, const pb_field_t *field, const void *src) checkreturn; + +static bool pb_encb_varint(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_svarint(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_fixed32(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_fixed64(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_bytes(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_string(pb_strstream_t *stream, const pb_field_t *field, const void *src); +static bool pb_encb_submessage(pb_strstream_t *stream, const pb_field_t *field, const void *src); + +/* --- Function pointers to field encoders --- + * Order in the array must match pb_action_t LTYPE numbering. + */ +static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = { + &pb_encb_varint, + &pb_encb_svarint, + &pb_encb_fixed32, + &pb_encb_fixed64, + + &pb_encb_bytes, + &pb_encb_string, + &pb_encb_submessage +}; + +/* pb_strstream_t implementation */ + +pb_strstream_t pb_strstream_from_buffer(uint8_t *buf, size_t bufsize) +{ + pb_strstream_t stream; + stream.buffer = buf; + stream.last = buf + bufsize; + return stream; +} + +bool pb_buf_write(pb_strstream_t *stream, const uint8_t *buf, size_t count) +{ + if (stream->buffer + count > stream->last) + return false; + stream->last -= count; + memcpy(stream->last, buf, count); + return true; +} + +/* Main encoding stuff */ + +/* This function is static for the same reason as the version in + * pb_encode.c. + */ +static bool checkreturn encode_array(pb_strstream_t *stream, const pb_field_t *field, + const void *pData, size_t count, pb_encoder_t func) +{ + const void *p; + uint8_t *start; + size_t size; + int i; + + if (count == 0) + return true; + + start = stream->last; + p = (const char*)pData + field->data_size * count; + + if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Write the data (in reverse order). */ + for (i = 0; i < count; i++) + { + p = (const char*)p - field->data_size; + if (!func(stream, field, p)) + return false; + } + + /* Write the size. */ + size = start - stream->last; + if (!pb_encbuf_varint(stream, size)) + return false; + if (!pb_encbuf_tag(stream, PB_WT_STRING, field->tag)) + return false; + } + else + { + for (i = 0; i < count; i++) + { + p = (const char*)p - field->data_size; + if (!func(stream, field, p)) + return false; + if (!pb_encbuf_tag_for_field(stream, field)) + return false; + } + } + + return true; +} + +bool checkreturn pb_encode_buffer(pb_strstream_t *stream, const pb_message_t *msg, const void *src_struct) +{ + const char *has_fields = src_struct; + const uint8_t *pData = src_struct; + const pb_field_t *field; + pb_encoder_t func; + size_t size; + unsigned int i; + + /* msg->size includes trailing padding, so we must calculate the + * offset of the last field by counting forward from the start. + */ + for (i = 0; i < msg->field_count; i++) + { + field = &msg->fields[i]; + pData += field->data_offset; + size = field->data_size; + if (PB_HTYPE(field->type) == PB_HTYPE_ARRAY) + size *= field->array_size; + pData += size; + } + + /* Iterate through the fields in reverse order. Because we write + * from the end of the buffer, the result is in canonical order. + */ + for (i = msg->field_count; i > 0; pData -= field->data_offset) + { + field = &msg->fields[--i]; + func = PB_ENCODERS[PB_LTYPE(field->type)]; + size = field->data_size; + if (PB_HTYPE(field->type) == PB_HTYPE_ARRAY) + size *= field->array_size; + pData -= size; + + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_OPTIONAL: + if (!(has_fields[i/8] & (1 << i%8))) + break; + if (PB_POINTER(field->type) + && (PB_LTYPE(field->type) == PB_LTYPE_STRING + || PB_LTYPE(field->type) == PB_LTYPE_SUBMESSAGE) + && *(void**)pData == NULL) + break; + /* else fall through to required case */ + + case PB_HTYPE_REQUIRED: + if (!func(stream, field, pData)) + return false; + if (!pb_encbuf_tag_for_field(stream, field)) + return false; + break; + + case PB_HTYPE_ARRAY: + size = *(size_t*)(pData + field->size_offset); + if (!encode_array(stream, field, pData, size, func)) + return false; + break; + + case PB_HTYPE_CALLBACK: + { + pb_callback_t *callback = (pb_callback_t*)pData; + if (callback->funcs.encode_buffer != NULL + && !callback->funcs.encode_buffer(stream, field, callback->arg)) + return false; + break; + } + } + } + + return true; +} + +/* Helper functions */ + +bool checkreturn pb_encbuf_varint(pb_strstream_t *stream, uint64_t value) +{ + if (stream->last == stream->buffer) + { + return false; + } + else if (value < 128) + { + *--stream->last = value; + return true; + } + else + { + uint8_t buffer[10]; + int i = 0; + + while (value) + { + buffer[i] = (value & 0x7F) | 0x80; + value >>= 7; + i++; + } + buffer[i-1] &= 0x7F; + return pb_buf_write(stream, buffer, i); + } +} + +bool checkreturn pb_encbuf_tag(pb_strstream_t *stream, pb_wire_type_t wiretype, int field_number) +{ + int tag = wiretype | (field_number << 3); + return pb_encbuf_varint(stream, tag); +} + +bool checkreturn pb_encbuf_tag_for_field(pb_strstream_t *stream, const pb_field_t *field) +{ + pb_wire_type_t wiretype; + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_VARINT: + case PB_LTYPE_SVARINT: + wiretype = PB_WT_VARINT; + break; + + case PB_LTYPE_FIXED32: + wiretype = PB_WT_32BIT; + break; + + case PB_LTYPE_FIXED64: + wiretype = PB_WT_64BIT; + break; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + wiretype = PB_WT_STRING; + break; + + default: + return false; + } + + return pb_encbuf_tag(stream, wiretype, field->tag); +} + +bool checkreturn pb_encbuf_string(pb_strstream_t *stream, const uint8_t *buffer, size_t size) +{ + if (!pb_buf_write(stream, buffer, size)) + return false; + return pb_encbuf_varint(stream, size); +} + +/* Field encoders */ + +/* Copy srcsize bytes of integer from src so that values are casted properly. + * On little endian machine, copy to start of dest + * On big endian machine, copy to end of dest + * destsize must always be larger than srcsize + * + * Note: This is the reverse of the endian_copy in pb_decode.c. + */ +static void endian_copy(uint64_t *dest, const void *src, size_t srcsize) +{ +#ifdef __BIG_ENDIAN__ + memcpy((char*)dest + sizeof(*dest) - srcsize, src, srcsize); +#else + memcpy(dest, src, srcsize); +#endif +} + +static bool pb_encb_varint(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ + uint64_t value = 0; + endian_copy(&value, src, field->data_size); + return pb_encbuf_varint(stream, value); +} + +static bool pb_encb_svarint(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ + uint64_t value = 0; + uint64_t zigzagged; + uint64_t signbitmask = (uint64_t)0x80 << (field->data_size * 8 - 8); + uint64_t xormask = ((uint64_t)-1) >> (64 - field->data_size * 8); + + endian_copy(&value, src, field->data_size); + if (value & signbitmask) + zigzagged = ((value ^ xormask) << 1) | 1; + else + zigzagged = value << 1; + + return pb_encbuf_varint(stream, zigzagged); +} + +static bool pb_encb_fixed32(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ +#ifdef __BIG_ENDIAN__ + const uint8_t *bytes = (const uint8_t*)src; + uint8_t lebytes[4] = {bytes[3], bytes[2], bytes[1], bytes[0]}; + src = lebytes; +#endif + return pb_buf_write(stream, src, 4); +} + +static bool pb_encb_fixed64(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ +#ifdef __BIG_ENDIAN__ + const uint8_t *bytes = (const uint8_t*)src; + uint8_t lebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], + bytes[3], bytes[2], bytes[1], bytes[0]}; + src = lebytes; +#endif + return pb_buf_write(stream, src, 8); +} + +static bool pb_encb_bytes(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ + if ((field != NULL) && PB_POINTER(field->type)) { + pb_bytes_t *bytes = (pb_bytes_t*)src; + return pb_encbuf_string(stream, bytes->bytes, bytes->size); + } else { + pb_bytes_array_t *bytes = (pb_bytes_array_t*)src; + return pb_encbuf_string(stream, bytes->bytes, bytes->size); + } +} + +static bool pb_encb_string(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ + size_t len; + if ((field != NULL) && PB_POINTER(field->type)) + src = *(char**)src; + len = src ? strlen((char*)src) : 0; + return pb_encbuf_string(stream, (uint8_t*)src, len); +} + +static bool pb_encb_submessage(pb_strstream_t *stream, const pb_field_t *field, const void *src) +{ + uint8_t *start; + size_t size; + + if (field->ptr == NULL) + return false; + + if (PB_POINTER(field->type)) { + src = *(void**)src; + if (src == NULL) + return false; + } + + start = stream->last; + if (!pb_encode_buffer(stream, (const pb_message_t*)field->ptr, src)) + return false; + + size = start - stream->last; + if (!pb_encbuf_varint(stream, size)) + return false; + + return true; +} diff --git a/pb_encode_buffer.h b/pb_encode_buffer.h new file mode 100644 index 0000000..a373e5f --- /dev/null +++ b/pb_encode_buffer.h @@ -0,0 +1,43 @@ +#ifndef _PB_ENCODE_BUFFER_H_ +#define _PB_ENCODE_BUFFER_H_ + +/* pb_encode_buffer.h: Functions to encode protocol buffers to an + * in-memory array of bytes. Depends on pb_encode_buffer.c. The main + * function is pb_encode_buffer. You also need an output buffer, + * structures and their field descriptions (just like with pb_decode + * or pb_encode). + */ + +#include +#include "pb.h" + +/* Output stream for an in-memory buffer. + */ +struct _pb_strstream_t +{ + uint8_t *buffer; + uint8_t *last; +}; + +pb_strstream_t pb_strstream_from_buffer(uint8_t *buf, size_t bufsize); +bool pb_buf_write(pb_strstream_t *stream, const uint8_t *buf, size_t count); + +/* --- Helper functions --- + * You may want to use these from your caller or callbacks. + */ + +bool pb_encbuf_varint(pb_strstream_t *stream, uint64_t value); +bool pb_encbuf_tag(pb_strstream_t *stream, pb_wire_type_t wiretype, int field_number); +/* Encode tag based on LTYPE and field number defined in the field structure. */ +bool pb_encbuf_tag_for_field(pb_strstream_t *stream, const pb_field_t *field); +/* Write length as varint and then the contents of buffer. */ +bool pb_encbuf_string(pb_strstream_t *stream, const uint8_t *buffer, size_t size); + +/* Encode struct to given output stream. + * Returns true on success, false on any failure. + * The actual struct pointed to by src_struct must match the description in msg. + * All required fields in the struct are assumed to have been filled in. + */ +bool pb_encode_buffer(pb_strstream_t *stream, const pb_message_t *msg, const void *src_struct); + +#endif diff --git a/tests/Makefile b/tests/Makefile index 229b6a9..e66d831 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ CFLAGS=-ansi -Wall -Werror -I .. -g -O0 --coverage LDFLAGS=--coverage -DEPS=../pb_decode.h ../pb_encode.h ../pb.h person.pb.h callbacks.pb.h unittests.h unittestproto.pb.h +DEPS=../pb_decode.h ../pb_encode.h ../pb_encode_buffer.h ../pb.h person.pb.h callbacks.pb.h unittests.h unittestproto.pb.h TESTS=test_decode1 test_encode1 test_decode_callbacks test_encode_callbacks decode_unittests decode_ptr_unittests encode_unittests all: breakpoints $(TESTS) run_unittests @@ -14,6 +14,8 @@ clean: pb_encode.o: ../pb_encode.c $(DEPS) $(CC) $(CFLAGS) -c -o $@ $< +pb_encode_buffer.o: ../pb_encode_buffer.c $(DEPS) + $(CC) $(CFLAGS) -c -o $@ $< pb_decode.o: ../pb_decode.c $(DEPS) $(CC) $(CFLAGS) -c -o $@ $< pb_ptr_decode.o: ../pb_decode.c $(DEPS) @@ -28,7 +30,7 @@ test_encode_callbacks: test_encode_callbacks.o pb_encode.o callbacks.pb.o decode_unittests: decode_unittests.o pb_decode.o unittestproto.pb.o pb_ptr_decode.o decode_ptr_unittests.o: CFLAGS += -DMALLOC_HEADER="" decode_ptr_unittests: decode_ptr_unittests.o pb_ptr_decode.o unittestproto.pb.o -encode_unittests: encode_unittests.o pb_encode.o unittestproto.pb.o +encode_unittests: encode_unittests.o pb_encode.o pb_encode_buffer.o unittestproto.pb.o %.pb: %.proto protoc -I. -I../generator -I/usr/include -o$@ $< @@ -41,7 +43,9 @@ breakpoints: ../*.c *.c coverage: run_unittests gcov pb_encode.gcda + gcov pb_encode_buffer.gcda gcov pb_decode.gcda + gcov pb_ptr_decode.gcda run_unittests: decode_unittests decode_ptr_unittests encode_unittests test_encode1 test_decode1 test_encode_callbacks test_decode_callbacks rm -f *.gcda diff --git a/tests/encode_unittests.c b/tests/encode_unittests.c index bf332e4..99a156b 100644 --- a/tests/encode_unittests.c +++ b/tests/encode_unittests.c @@ -1,6 +1,7 @@ #include #include #include "pb_encode.h" +#include "pb_encode_buffer.h" #include "unittests.h" #include "unittestproto.pb.h" @@ -23,6 +24,14 @@ bool fieldcallback(pb_ostream_t *stream, const pb_field_t *field, const void *ar return pb_encode_varint(stream, value); } +bool fieldcallback_buf(pb_strstream_t *stream, const pb_field_t *field, const void *arg) +{ + int value = 0x55; + if (!pb_encbuf_varint(stream, value)) + return false; + return pb_encbuf_tag_for_field(stream, field); +} + bool crazyfieldcallback(pb_ostream_t *stream, const pb_field_t *field, const void *arg) { /* This callback writes different amount of data the second time. */ @@ -33,6 +42,16 @@ bool crazyfieldcallback(pb_ostream_t *stream, const pb_field_t *field, const voi return pb_encode_varint(stream, *state); } +bool crazyfieldcallback_buf(pb_strstream_t *stream, const pb_field_t *field, const void *arg) +{ + /* This callback writes different amount of data the second time. */ + uint32_t *state = (uint32_t*)arg; + *state <<= 8; + if (!pb_encbuf_varint(stream, *state)) + return false; + return pb_encbuf_tag_for_field(stream, field); +} + /* Check that expression x writes data y. * Y is a string, which may contain null bytes. Null terminator is ignored. */ @@ -43,6 +62,20 @@ s = pb_ostream_from_buffer(buffer, sizeof(buffer)), \ memcmp(buffer, y, sizeof(y) - 1) == 0 && \ buffer[sizeof(y) - 1] == 0xAA +/* Check that expression x writes data y into s2. + * Y is a string, which may contain null bytes. Null terminator is ignored. + */ +#define WRITES_BUF(x, y) \ +memset(buffer, 0xAA, sizeof(buffer)), \ +s2 = pb_strstream_from_buffer(buffer, sizeof(buffer)), \ +(x) && \ +memcmp(s2.last, y, sizeof(y) - 1) == 0 && \ +s2.last - buffer == sizeof(buffer) - (sizeof(y) - 1) + +/* Check that expression x and y write data z into s and s2, respectively. + */ +#define WRITES_BOTH(x, y, z) (WRITES(x, z)) && (WRITES_BUF(y, z)) + int main() { int status = 0; @@ -72,51 +105,74 @@ int main() { uint8_t buffer[30]; pb_ostream_t s; + pb_strstream_t s2; COMMENT("Test pb_encode_varint") - TEST(WRITES(pb_encode_varint(&s, 0), "\0")); - TEST(WRITES(pb_encode_varint(&s, 1), "\1")); - TEST(WRITES(pb_encode_varint(&s, 0x7F), "\x7F")); - TEST(WRITES(pb_encode_varint(&s, 0x80), "\x80\x01")); - TEST(WRITES(pb_encode_varint(&s, UINT32_MAX), "\xFF\xFF\xFF\xFF\x0F")); - TEST(WRITES(pb_encode_varint(&s, UINT64_MAX), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); + TEST(WRITES_BOTH(pb_encode_varint(&s, 0), + pb_encbuf_varint(&s2, 0), "\0")); + TEST(WRITES_BOTH(pb_encode_varint(&s, 1), + pb_encbuf_varint(&s2, 1), "\1")); + TEST(WRITES_BOTH(pb_encode_varint(&s, 0x7F), + pb_encbuf_varint(&s2, 0x7F), "\x7F")); + TEST(WRITES_BOTH(pb_encode_varint(&s, 0x80), + pb_encbuf_varint(&s2, 0x80), "\x80\x01")); + TEST(WRITES_BOTH(pb_encode_varint(&s, UINT32_MAX), + pb_encbuf_varint(&s2, UINT32_MAX), + "\xFF\xFF\xFF\xFF\x0F")); + TEST(WRITES_BOTH(pb_encode_varint(&s, UINT64_MAX), + pb_encbuf_varint(&s2, UINT64_MAX), + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); } { uint8_t buffer[30]; pb_ostream_t s; + pb_strstream_t s2; COMMENT("Test pb_encode_tag") - TEST(WRITES(pb_encode_tag(&s, PB_WT_STRING, 5), "\x2A")); - TEST(WRITES(pb_encode_tag(&s, PB_WT_VARINT, 99), "\x98\x06")); + TEST(WRITES_BOTH(pb_encode_tag(&s, PB_WT_STRING, 5), + pb_encbuf_tag(&s2, PB_WT_STRING, 5), "\x2A")); + TEST(WRITES_BOTH(pb_encode_tag(&s, PB_WT_VARINT, 99), + pb_encbuf_tag(&s2, PB_WT_VARINT, 99), "\x98\x06")); } { uint8_t buffer[30]; pb_ostream_t s; + pb_strstream_t s2; pb_field_t field = {10, PB_LTYPE_SVARINT}; COMMENT("Test pb_encode_tag_for_field") - TEST(WRITES(pb_encode_tag_for_field(&s, &field), "\x50")); + TEST(WRITES_BOTH(pb_encode_tag_for_field(&s, &field), + pb_encbuf_tag_for_field(&s2, &field), "\x50")); field.type = PB_LTYPE_FIXED64; - TEST(WRITES(pb_encode_tag_for_field(&s, &field), "\x51")); + TEST(WRITES_BOTH(pb_encode_tag_for_field(&s, &field), + pb_encbuf_tag_for_field(&s2, &field), "\x51")); field.type = PB_LTYPE_STRING; - TEST(WRITES(pb_encode_tag_for_field(&s, &field), "\x52")); + TEST(WRITES_BOTH(pb_encode_tag_for_field(&s, &field), + pb_encbuf_tag_for_field(&s2, &field), "\x52")); field.type = PB_LTYPE_FIXED32; - TEST(WRITES(pb_encode_tag_for_field(&s, &field), "\x55")); + TEST(WRITES_BOTH(pb_encode_tag_for_field(&s, &field), + pb_encbuf_tag_for_field(&s2, &field), "\x55")); } { uint8_t buffer[30]; pb_ostream_t s; + pb_strstream_t s2; COMMENT("Test pb_encode_string") - TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"abcd", 4), "\x04""abcd")); - TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"abcd\x00", 5), "\x05""abcd\x00")); - TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"", 0), "\x00")); + TEST(WRITES_BOTH(pb_encode_string(&s, (const uint8_t*)"abcd", 4), + pb_encbuf_string(&s2, (const uint8_t*)"abcd", 4), + "\x04""abcd")); + TEST(WRITES_BOTH(pb_encode_string(&s, (const uint8_t*)"abcd\x00", 5), + pb_encbuf_string(&s2, (const uint8_t*)"abcd\x00", 5), + "\x05""abcd\x00")); + TEST(WRITES_BOTH(pb_encode_string(&s, (const uint8_t*)"", 0), + pb_encbuf_string(&s2, (const uint8_t*)"", 0), "\x00")); } { @@ -192,72 +248,91 @@ int main() { uint8_t buffer[10]; pb_ostream_t s; + pb_strstream_t s2; IntegerArray msg = {{0}, 5, {1, 2, 3, 4, 5}}; COMMENT("Test pb_encode with int32 array") - TEST(WRITES(pb_encode(&s, IntegerArray_msg, &msg), "\x0A\x05\x01\x02\x03\x04\x05")) + TEST(WRITES_BOTH(pb_encode(&s, IntegerArray_msg, &msg), + pb_encode_buffer(&s2, IntegerArray_msg, &msg), + "\x0A\x05\x01\x02\x03\x04\x05")) msg.data_count = 0; - TEST(WRITES(pb_encode(&s, IntegerArray_msg, &msg), "")) + TEST(WRITES_BOTH(pb_encode(&s, IntegerArray_msg, &msg), + pb_encode_buffer(&s2, IntegerArray_msg, &msg), "")) msg.data_count = 10; TEST(!pb_encode(&s, IntegerArray_msg, &msg)) + TEST(!pb_encode_buffer(&s2, IntegerArray_msg, &msg)) } { uint8_t buffer[10]; pb_ostream_t s; + pb_strstream_t s2; FloatArray msg = {{0}, 1, {99.0f}}; COMMENT("Test pb_encode with float array") - TEST(WRITES(pb_encode(&s, FloatArray_msg, &msg), - "\x0A\x04\x00\x00\xc6\x42")) + TEST(WRITES_BOTH(pb_encode(&s, FloatArray_msg, &msg), + pb_encode_buffer(&s2, FloatArray_msg, &msg), + "\x0A\x04\x00\x00\xc6\x42")) msg.data_count = 0; - TEST(WRITES(pb_encode(&s, FloatArray_msg, &msg), "")) + TEST(WRITES_BOTH(pb_encode(&s, FloatArray_msg, &msg), + pb_encode_buffer(&s2, FloatArray_msg, &msg), "")) msg.data_count = 3; TEST(!pb_encode(&s, FloatArray_msg, &msg)) + TEST(!pb_encode_buffer(&s2, FloatArray_msg, &msg)) } { uint8_t buffer[10]; pb_ostream_t s; + pb_strstream_t s2; CallbackArray msg; - msg.data.funcs.encode = &fieldcallback; - COMMENT("Test pb_encode with callback field.") + msg.data.funcs.encode = &fieldcallback; TEST(WRITES(pb_encode(&s, CallbackArray_msg, &msg), "\x08\x55")) + msg.data.funcs.encode_buffer = &fieldcallback_buf; + TEST(WRITES_BUF(pb_encode_buffer(&s2, CallbackArray_msg, &msg), "\x08\x55")) } { uint8_t buffer[10]; pb_ostream_t s; + pb_strstream_t s2; IntegerContainer msg = {{0}, {{0}, 5, {1,2,3,4,5}}}; COMMENT("Test pb_encode with packed array in a submessage.") - TEST(WRITES(pb_encode(&s, IntegerContainer_msg, &msg), - "\x0A\x07\x0A\x05\x01\x02\x03\x04\x05")) + TEST(WRITES_BOTH(pb_encode(&s, IntegerContainer_msg, &msg), + pb_encode_buffer(&s2, IntegerContainer_msg, &msg), + "\x0A\x07\x0A\x05\x01\x02\x03\x04\x05")) } { uint8_t buffer[10]; pb_ostream_t s; + pb_strstream_t s2; CallbackContainer msg; CallbackContainerContainer msg2; uint32_t state = 1; + COMMENT("Test pb_encode with callback field in a submessage.") msg.submsg.data.funcs.encode = &fieldcallback; msg2.submsg.submsg.data.funcs.encode = &fieldcallback; - - COMMENT("Test pb_encode with callback field in a submessage.") TEST(WRITES(pb_encode(&s, CallbackContainer_msg, &msg), "\x0A\x02\x08\x55")) TEST(WRITES(pb_encode(&s, CallbackContainerContainer_msg, &msg2), "\x0A\x04\x0A\x02\x08\x55")) + msg.submsg.data.funcs.encode_buffer = &fieldcallback_buf; + msg2.submsg.submsg.data.funcs.encode_buffer = &fieldcallback_buf; + TEST(WRITES_BUF(pb_encode_buffer(&s2, CallbackContainer_msg, &msg), "\x0A\x02\x08\x55")) + TEST(WRITES_BUF(pb_encode_buffer(&s2, CallbackContainerContainer_msg, &msg2), + "\x0A\x04\x0A\x02\x08\x55")) + /* Misbehaving callback: varying output between calls */ msg.submsg.data.funcs.encode = &crazyfieldcallback; msg.submsg.data.arg = &state; @@ -267,11 +342,21 @@ int main() TEST(!pb_encode(&s, CallbackContainer_msg, &msg)) state = 1; TEST(!pb_encode(&s, CallbackContainerContainer_msg, &msg2)) + + msg.submsg.data.funcs.encode_buffer = &crazyfieldcallback_buf; + msg.submsg.data.arg = &state; + msg2.submsg.submsg.data.funcs.encode_buffer = &crazyfieldcallback_buf; + msg2.submsg.submsg.data.arg = &state; + + TEST(!pb_encode_buffer(&s2, CallbackContainer_msg, &msg)) + state = 1; + TEST(!pb_encode_buffer(&s2, CallbackContainerContainer_msg, &msg2)) } { - uint8_t buffer[128]; + uint8_t buffer[30]; pb_ostream_t s; + pb_strstream_t s2; PointerContainer msg; DefaultContainer msg2; IntegerArray msg3; @@ -284,8 +369,9 @@ int main() msg.blob.size = 1; msg.blob.bytes = (uint8_t*)"b"; msg.submsg = &msg2; - TEST(WRITES(pb_encode(&s, PointerContainer_msg, &msg), - "\x0A\x01\x61\x12\x01\x62\x1A\x00")) + TEST(WRITES_BOTH(pb_encode(&s, PointerContainer_msg, &msg), + pb_encode_buffer(&s2, PointerContainer_msg, &msg), + "\x0A\x01\x61\x12\x01\x62\x1A\x00")) memset(&msg3, 0, sizeof(msg3)); msg.rtext_count = 1; @@ -295,19 +381,21 @@ int main() msg.rblob[0].bytes = (uint8_t*)"f"; msg.rsubmsg_count = 1; msg.rsubmsg[0] = &msg3; - TEST(WRITES(pb_encode(&s, PointerContainer_msg, &msg), - "\x0A\x01\x61\x12\x01\x62\x1A\x00" - "\x2A\x01\x65\x32\x01\x66\x3A\x00")); + TEST(WRITES_BOTH(pb_encode(&s, PointerContainer_msg, &msg), + pb_encode_buffer(&s2, PointerContainer_msg, &msg), + "\x0A\x01\x61\x12\x01\x62\x1A\x00" + "\x2A\x01\x65\x32\x01\x66\x3A\x00")); PointerContainer_set(msg, otext); msg.otext = "c"; PointerContainer_set(msg, oblob); msg.oblob.size = 1; msg.oblob.bytes = (uint8_t*)"d"; - TEST(WRITES(pb_encode(&s, PointerContainer_msg, &msg), - "\x0A\x01\x61\x12\x01\x62\x1A\x00" - "\x2A\x01\x65\x32\x01\x66\x3A\x00" - "\x42\x01\x63\x4A\x01\x64")); + TEST(WRITES_BOTH(pb_encode(&s, PointerContainer_msg, &msg), + pb_encode_buffer(&s2, PointerContainer_msg, &msg), + "\x0A\x01\x61\x12\x01\x62\x1A\x00" + "\x2A\x01\x65\x32\x01\x66\x3A\x00" + "\x42\x01\x63\x4A\x01\x64")); } if (status != 0)